initial commit

This commit is contained in:
2024-07-08 23:31:33 +02:00
commit 791e1a9e97
8 changed files with 529 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
EXTRA_CFLAGS = -Wall -g
obj-m += tictactoe.o
tictactoe-objs := tictactoe_main.o tictactoe_game.o
+18
View File
@@ -0,0 +1,18 @@
Copyright © 2024 Niklas Elsbrock
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the “Software”), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+6
View File
@@ -0,0 +1,6 @@
KDIR = /lib/modules/`uname -r`/build
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
+65
View File
@@ -0,0 +1,65 @@
# /dev/tictactoe
A Linux driver for a character device which can be used to play
[Tic-tac-toe](https://en.wikipedia.org/wiki/Tic-tac-toe).
## Usage
*Disclaimer:* I make no guarantees about the soundness of the driver code.
Use with caution (i.e. only in a virtual machine).
### Building
Clone this repository, `cd` to it, then run `make`.
### Loading
After building the module, run `insmod tictactoe.ko` as root to load it.
### Using the device
After loading the module, player X can write grid coordinates
(ranging from 1 to 3, format `XY`) to `/dev/tictactoe` to make their first move:
```console
# echo 12 > /dev/tictactoe
```
After player X has made his move, player O can do the same:
```console
# echo 22 > /dev/tictactoe
```
To inspect the current state of the game, simply read from `/dev/tictactoe`:
```console
# cat /dev/tictactoe
```
```
#####
# X #
# O #
# #
#####
It's X's turn!
```
Alternatively, you can `watch` the file in a separate terminal for automatic
updating:
```console
# watch -n 0.5 cat /dev/tictactoe
```
To start a new game, write `reset` to `/dev/tictactoe`:
```console
# echo reset > /dev/tictactoe
```
### Unloading
Run `rmmod tictactoe` as root to unload the module.
+145
View File
@@ -0,0 +1,145 @@
#include "tictactoe_game.h"
#include "tictactoe_internal.h"
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/printk.h>
int tictactoe_game_init(ttt_game_t *game)
{
game->next_turn = 'X';
game->winner = 0;
for (int x = 0; x < 3; x++) {
for (int y = 0; y < 3; y++) {
game->board[x][y] = ' ';
}
}
return 0;
}
// returns 0 if there is no winner yet, -1 if it's a tie, and the respective player if they win
char tictactoe_game_check_winner(ttt_game_t *game)
{
char winner;
// check columns
for (int x = 0; x < 3; x++) {
winner = game->board[x][0];
if (winner != ' ') {
for (int y = 1; y < 3; y++) {
if (game->board[x][y] != winner) {
winner = 0;
break;
}
}
if (winner != 0)
return winner;
}
}
// check rows
for (int y = 0; y < 3; y++) {
winner = game->board[0][y];
if (winner != ' ') {
for (int x = 1; x < 3; x++) {
if (game->board[x][y] != winner) {
winner = 0;
break;
}
}
if (winner != 0)
return winner;
}
}
// check diagonal top-left <-> bottom-right
winner = game->board[0][0];
if (winner != ' ') {
for (int i = 0; i < 3; i++) {
if (game->board[i][i] != winner) {
winner = 0;
break;
}
}
if (winner != 0)
return winner;
}
// check diagonal bottom-left <-> top-right
winner = game->board[2][0];
if (winner != ' ') {
for (int i = 0; i < 3; i++) {
if (game->board[2 - i][i] != winner) {
winner = 0;
break;
}
}
if (winner != 0)
return winner;
}
// check for tie
winner = -1;
for (int x = 0; x < 3; x++) {
for (int y = 0; y < 3; y++) {
if (game->board[x][y] == ' ') {
winner = 0;
break;
}
}
}
return winner;
}
int tictactoe_game_make_turn(ttt_game_t *game, size_t x, size_t y)
{
if (game->board[x][y] != ' ') {
pr_notice("position already occupied\n");
return -1;
}
game->board[x][y] = game->next_turn;
game->winner = tictactoe_game_check_winner(game);
if (game->next_turn == 'X')
game->next_turn = 'O';
else
game->next_turn = 'X';
return 0;
}
int tictactoe_game_snprint(ttt_game_t *game, char *buf, size_t count)
{
int pos;
int ret;
pos = 0;
ret = snprintf(buf + pos, count - pos,
"#####\n#%.3s#\n#%.3s#\n#%.3s#\n#####\n\n",
game->board[0], game->board[1], game->board[2]);
if (ret < 0)
goto fail;
pos += ret;
if (game->winner == 0) {
ret = snprintf(buf + pos, count - pos, "It's %c's turn!\n",
game->next_turn);
} else if (game->winner == -1) {
ret = snprintf(buf + pos, count - pos, "It's a tie!\n");
} else {
ret = snprintf(buf + pos, count - pos, "Player %c won!\n",
game->winner);
}
if (ret < 0)
goto fail;
pos += ret;
return pos;
fail:
tictactoe_error("snprintf failed\n");
return -1;
}
+17
View File
@@ -0,0 +1,17 @@
#ifndef __TICTACTOE_GAME_H__
#define __TICTACTOE_GAME_H__
#include <linux/types.h>
typedef struct tictactoe_game {
char next_turn;
char winner;
char board[3][3];
} ttt_game_t;
int tictactoe_game_init(ttt_game_t *game);
char tictactoe_game_check_winner(ttt_game_t *game);
int tictactoe_game_make_turn(ttt_game_t *game, size_t x, size_t y);
int tictactoe_game_snprint(ttt_game_t *game, char *buf, size_t len);
#endif // __TICTACTOE_GAME_H__
+19
View File
@@ -0,0 +1,19 @@
#ifndef __TICTACTOE_INTERNAL_H__
#define __TICTACTOE_INTERNAL_H__
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/types.h>
#include <linux/printk.h>
#include <linux/mutex.h>
#define tictactoe_error(format, ...) \
pr_err("%s: " format, __func__, ##__VA_ARGS__)
typedef struct annotated_string {
struct mutex lock;
size_t len;
char *buf;
} ttt_astring_t;
#endif // __TICTACTOE_INTERNAL_H__
+255
View File
@@ -0,0 +1,255 @@
#include "tictactoe_internal.h"
#include "tictactoe_game.h"
#include <linux/types.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/printk.h>
#include <linux/minmax.h>
#include <linux/mutex.h>
#define TICTACTOE_DEVICE_NAME "tictactoe"
#define TICTACTOE_MODULE_NAME "tictactoe"
#define TICTACTOE_MODULE_DESC TICTACTOE_DEVICE_NAME " device driver"
#define TICTACTOE_PRINT_GAME_BUF_SIZE 256
#define TICTACTOE_RESET_COMMAND "reset"
#define TICTACTOE_RESET_COMMAND_LEN (sizeof(TICTACTOE_RESET_COMMAND) - 1)
MODULE_AUTHOR("Niklas Elsbrock");
MODULE_DESCRIPTION(TICTACTOE_MODULE_DESC);
MODULE_LICENSE("Dual MIT/GPL");
static ttt_game_t *tictactoe_current_game;
DEFINE_MUTEX(tictactoe_mutex_current_game);
static int tictactoe_init_annotated_game_string(ttt_astring_t *string)
{
int out_len;
string->buf = kmalloc(TICTACTOE_PRINT_GAME_BUF_SIZE, GFP_KERNEL);
if (!string->buf) {
tictactoe_error("failed to allocate buffer memory\n");
return -1;
}
mutex_lock(&tictactoe_mutex_current_game);
out_len = tictactoe_game_snprint(tictactoe_current_game, string->buf,
TICTACTOE_PRINT_GAME_BUF_SIZE);
mutex_unlock(&tictactoe_mutex_current_game);
if (out_len < 0) {
kfree(string->buf);
tictactoe_error("failed to print game\n");
return -1;
}
string->len = out_len;
return 0;
}
static ssize_t tictactoe_read(struct file *file, char __user *buf, size_t count,
loff_t *ppos)
{
ttt_astring_t *string;
size_t read_len;
if (count == 0)
return 0;
if (*ppos < 0)
return -EINVAL;
string = (ttt_astring_t *)file->private_data;
mutex_lock(&string->lock);
if (!string->buf) {
if (tictactoe_init_annotated_game_string(string)) {
mutex_unlock(&string->lock);
return -EFAULT;
}
}
if (*ppos >= string->len) {
mutex_unlock(&string->lock);
return 0;
}
read_len = min(count, string->len - (size_t)*ppos);
if (copy_to_user(buf, string->buf + *ppos, read_len)) {
mutex_unlock(&string->lock);
tictactoe_error("copy_to_user failed\n");
return -EFAULT;
}
mutex_unlock(&string->lock);
*ppos += read_len;
return read_len;
}
static ssize_t tictactoe_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
char *command_buf;
size_t count_without_lf;
size_t human_x, human_y;
if (count == 0)
return 0;
if (*ppos < 0)
return -EINVAL;
command_buf = kmalloc(count, GFP_KERNEL);
if (!command_buf) {
tictactoe_error("failed to allocate buffer memory\n");
return -EFAULT;
}
if (copy_from_user(command_buf, buf + *ppos, count)) {
kfree(command_buf);
tictactoe_error("failed to copy write buffer\n");
return -EFAULT;
}
if (command_buf[count - 1] == '\n') {
count_without_lf = count - 1;
} else {
count_without_lf = count;
}
// reset command
if (count_without_lf == TICTACTOE_RESET_COMMAND_LEN &&
strncmp(TICTACTOE_RESET_COMMAND, command_buf,
TICTACTOE_RESET_COMMAND_LEN) == 0) {
mutex_lock(&tictactoe_mutex_current_game);
tictactoe_game_init(tictactoe_current_game);
mutex_unlock(&tictactoe_mutex_current_game);
kfree(command_buf);
return count;
}
if (count_without_lf != 2) {
kfree(command_buf);
pr_notice("invalid command length\n");
return -EINVAL;
}
for (int i = 0; i < 2; i++) {
if (command_buf[i] < '1' || command_buf[i] > '3') {
kfree(command_buf);
pr_notice("invalid coordinates\n");
return -EINVAL;
}
}
human_x = (size_t)(command_buf[0] - '0');
human_y = (size_t)(command_buf[1] - '0');
kfree(command_buf);
mutex_lock(&tictactoe_mutex_current_game);
if (tictactoe_current_game->winner) {
#ifdef CONFIG_TICTACTOE_AUTO_RESET
tictactoe_game_init(tictactoe_current_game);
#else
mutex_unlock(&tictactoe_mutex_current_game);
pr_notice("the game is finished, use '" TICTACTOE_RESET_COMMAND
"' to start a new one\n");
return -EINVAL;
#endif
}
if (tictactoe_game_make_turn(tictactoe_current_game, human_x - 1,
human_y - 1)) {
mutex_unlock(&tictactoe_mutex_current_game);
return -EINVAL;
}
mutex_unlock(&tictactoe_mutex_current_game);
return count;
}
static int tictactoe_open(struct inode *inode, struct file *file)
{
ttt_astring_t *string;
file->private_data = kmalloc(sizeof(ttt_astring_t), GFP_KERNEL);
if (!file->private_data) {
tictactoe_error(
"failed to allocate private data buffer memory\n");
return -EFAULT;
}
string = (ttt_astring_t *)file->private_data;
mutex_init(&string->lock);
string->buf = NULL;
return 0;
}
static int tictactoe_release(struct inode *inode, struct file *file)
{
ttt_astring_t *string;
string = (ttt_astring_t *)file->private_data;
if (string->buf)
kfree(string->buf);
kfree(string);
return 0;
}
static const struct file_operations tictactoe_fops = {
.owner = THIS_MODULE,
.read = tictactoe_read,
.write = tictactoe_write,
.open = tictactoe_open,
.release = tictactoe_release,
};
static struct miscdevice tictactoe_dev = {
MISC_DYNAMIC_MINOR,
TICTACTOE_DEVICE_NAME,
&tictactoe_fops,
};
static int __init tictactoe_init(void)
{
int ret;
mutex_lock(&tictactoe_mutex_current_game);
tictactoe_current_game = kmalloc(sizeof(ttt_game_t), GFP_KERNEL);
if (!tictactoe_current_game) {
mutex_unlock(&tictactoe_mutex_current_game);
tictactoe_error("failed to allocate game memory\n");
return -EFAULT;
}
ret = tictactoe_game_init(tictactoe_current_game);
if (ret) {
mutex_unlock(&tictactoe_mutex_current_game);
tictactoe_error("failed to initialize game\n");
return -EFAULT;
}
mutex_unlock(&tictactoe_mutex_current_game);
ret = misc_register(&tictactoe_dev);
if (ret)
tictactoe_error("Unable to register misc device\n");
else
pr_info(TICTACTOE_MODULE_DESC "\n");
return ret;
}
static void __exit tictactoe_exit(void)
{
mutex_lock(&tictactoe_mutex_current_game);
kfree(tictactoe_current_game);
mutex_unlock(&tictactoe_mutex_current_game);
misc_deregister(&tictactoe_dev);
}
module_init(tictactoe_init);
module_exit(tictactoe_exit);