# kxo Code Write-Up
## What is cdev?
## Code Tracing
### [main.c](https://github.com/sysprog21/kxo/blob/main/main.c)
重要的變數包含:
-
#### [kxo_init](https://github.com/sysprog21/kxo/blob/main/main.c#L450-L537)
kxo 模組第一個被呼叫的函式,呼叫時機是在使用者使用 `sudo insmod` 時。流程圖如下:
```graphviz
digraph g {
rankdir=TD
node [shape = record style="filled"];
overlap=false;
// splines = ortho
// start node
kxo_init [label="kxo_init" shape=ellipse fillcolor=mediumturquoise]
// function nodes
node [shape = record style="filled" fillcolor="springgreen1"]
kfifo_alloc
alloc_chrdev_region
cdev_init
cdev_add
kobject_put
class_create
device_create
device_create_file
vmalloc
vfree
device_destroy
class_destroy
alloc_workqueue
negamax_init
mcts_init
init_attr_lock
init_timer
init_open_cnt
// macro nodes
node [shape = record style="filled" fillcolor="orange"]
MAJOR
// goto node
subgraph cluster_goto {
node [shape = record style="filled" fillcolor="grey"]
label = "Error";
labelloc = "t";
labeljust = "l";
style = "filled, rounded";
fillcolor = pink;
color = black;
goto [label="{<f0>error_cdev | <f1>error_region | <f2>error_alloc}"];
}
// if nodes
node [shape=diamond style="filled" fillcolor="darkslategray1"]
if_kfifo_failed [label="ret \< 0"]
if_chrdev_region_failed [label="ret == 0"]
if_cdev_add_failed [label="ret == 0"]
if_class_create_failed [label="IS_ERR(kxo_class)"]
if_device_create_file_failed [label="ret \< 0"]
if_vmalloc_failed [label="!fast_buf.buf"]
if_alloc_workqueue_failed [label="!kxo_workqueue"]
// exits nodes
out [shape=record fillcolor=springgreen1]
ret [shape=ellipse fillcolor=mediumturquoise]
/* link */
edge[weight=10]
kxo_init -> kfifo_alloc
kfifo_alloc -> if_kfifo_failed
if_kfifo_failed -> alloc_chrdev_region [taillabel="false" labeldistance=2 labelangle=300]
alloc_chrdev_region -> if_chrdev_region_failed [shape=diamond]
if_chrdev_region_failed -> MAJOR [taillabel="true" label=" dev_id" labeldistance=2 labelangle=70]
MAJOR -> cdev_init [label=" major"]
cdev_init -> cdev_add
cdev_add -> if_cdev_add_failed
if_cdev_add_failed -> class_create [taillabel="true" labeldistance=2 labelangle=70]
class_create -> if_class_create_failed
if_class_create_failed -> device_create [taillabel="false" labeldistance=2 labelangle=70]
device_create -> device_create_file
device_create_file -> if_device_create_file_failed
if_device_create_file_failed -> vmalloc [taillabel="false" labeldistance=2 labelangle=290]
vmalloc -> if_vmalloc_failed
if_vmalloc_failed -> alloc_workqueue [taillabel="false" labeldistance=2 labelangle=70]
alloc_workqueue -> if_alloc_workqueue_failed
if_alloc_workqueue_failed -> negamax_init [taillabel="false" labeldistance=2 labelangle=305]
negamax_init -> mcts_init
mcts_init -> init_attr_lock
init_attr_lock -> init_timer
init_timer -> init_open_cnt
init_open_cnt -> ret
/* link */
edge[weight=3]
goto:f2 -> out
edge[weight=2]
if_kfifo_failed -> ret [taillabel="true" label=" -ENOMEM" labeldistance=2]
if_chrdev_region_failed -> goto:f2 [taillabel="false" label=" ret" labeldistance=5]
if_cdev_add_failed -> kobject_put [taillabel="false" label=" &kxo_cdev.kobj" labeldistance=2]
kobject_put -> goto:f1
if_class_create_failed -> goto:f0 [taillabel="true" labeldistance=2 label=" PTR_ERR(kxo_class)"]
out -> ret
if_device_create_file_failed -> goto:f0 [taillabel="true" labeldistance=2]
if_vmalloc_failed -> device_destroy [taillabel="true" labeldistance=2]
device_destroy -> class_destroy
class_destroy -> goto:f0
if_alloc_workqueue_failed -> vfree [taillabel="true" labeldistance=3 labelangle=30]
vfree -> device_destroy
}
```
參考以下連結:
- [linux fs char_dev.c](https://github.com/torvalds/linux/blob/master/fs/char_dev.c)
- [linux drivers class.c](https://github.com/torvalds/linux/blob/master/drivers/base/class.c)
- [linux drivers core.c](https://github.com/torvalds/linux/blob/master/drivers/base/core.c)
- [linux kernel workqueue.c](https://github.com/torvalds/linux/blob/master/kernel/workqueue.c)
1. 呼叫 `kfifo_alloc` 配置 `fifo` 記憶體
- 返回值 < 0 : 失敗, 結束並返回 `-ENOMEM`,第一個離開點
- 其他 : do nothing
2. 呼叫 `alloc_chrdev_region`。我們不在意 `major` number 所以交給內核處理,如果在意則要使用 `register_chrdev_region`。
```c
/**
* alloc_chrdev_region() - register a range of char device numbers
* @dev: output parameter for first assigned number
* @baseminor: first of the requested range of minor numbers
* @count: the number of minor numbers required
* @name: the name of the associated device or driver
*
* Allocates a range of char device numbers. The major number will be
* chosen dynamically, and returned (along with the first minor number)
* in @dev. Returns zero or a negative error code.
*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
```
此處配置名為 `kxo` 的裝置數量為 `NR_KMLDRV` (1)。
- 返回值 0 : 正確,獲取主裝置號
- 其他 : `goto error_alloc`
3. 呼叫 `cdev_init` 初始化 `kxo_cdev` 結構體。
```c
/**
* cdev_init() - initialize a cdev structure
* @cdev: the structure to initialize
* @fops: the file_operations for this device
*
* Initializes @cdev, remembering @fops, making it ready to add to the
* system with cdev_add().
*/
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
```
4. 呼叫 `cdev_add` 嘗試添加 char device,以 `dev_id` 作為第一個設備號,連續增加 `NR_KMLDRV` 個同類型設備。
```c
/**
* cdev_add() - add a char device to the system
* @p: the cdev structure for the device
* @dev: the first device number for which this device is responsible
* @count: the number of consecutive minor numbers corresponding to this
* device
*
* cdev_add() adds the device represented by @p to the system, making it
* live immediately. A negative error code is returned on failure.
*/
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
```
- 返回值 0 : do nothing
- 其他 : 先用 `kobject_put` 將 `kxo_cdev.kobj` 參考記數減去 1,再 goto error region
5. 呼叫 `class_create`
- class 能讓用戶方便管理同類型的裝置
- 會在 `sys/class/<class-name>` 下建立目錄
```
/**
* class_create - create a struct class structure
* @name: pointer to a string for the name of this class.
*
* This is used to create a struct class pointer that can then be used
* in calls to device_create().
*
* Returns &struct class pointer on success, or ERR_PTR() on error.
*
* Note, the pointer created here is to be destroyed when finished by
* making a call to class_destroy().
*/
struct class *class_create(const char *name)
```
- 返回值是無效指標 (`IS_ERR`) : 先獲取錯誤碼後,`goto error_cdev`
6. 呼叫 `device_create` 建立 `kxo_dev`
該函式負責註冊設備到 sysfs 中,並在 /dev 建立設備節點。
此處沒有檢查返回值,是將檢查步驟與以下合併。
7. 呼叫 `device_create_file` 在對應 `/sys/class/<class-name>` 建立文件。
- 返回值 < 0 : `goto error_cdev`
- 其他 : do nothing
8. 呼叫 `vmalloc`,大小為一個頁表 (4K)。
在 Linux 核心中可以透過 `kmalloc` 或 `vmalloc` 配置記憶體。在老師的[共筆](https://hackmd.io/@sysprog/linux-memory#kmalloc-vs-vmalloc)中也有談到。
>[!Note] 但建議是 128 KiB 以上才使用 kmalloc?
- 返回值為空指標 : `device_destroy` -> `class_destroy` -> `goto error_cdev`。
- 其他
9. 呼叫 `alloc_workqueue` 建立 `kxo_workqueue`
workqueue 允許不同的 `work_struct` 實例並行,但對於同一個 `work_struct` 實例同時間僅會由最多一個 CPU 運行。
注意目前的 `WQ_MAX_ACTIVE` (512) 是偏好值。
- 返回值為空指標: 釋放以配置的虛擬記憶體 (`vfree`) -> `device_destroy` -> `class_destroy` -> `goto error_cdev`
- 其他: do nothing
10. 初始化演算法 `negamax` 和 `mcts`
11. 初始化棋盤 `table` 和相關變數
由於建立字符設備的步驟是層層推進的,這裡使用 `goto` 語法,只要將 `error` 的釋放處理和前面初始化的順序相反,便可以優雅達成釋放與 char device 相關資源也是程式的第二個離開點。
```c
out:
return ret;
error_cdev:
cdev_del(&kxo_cdev);
error_region:
unregister_chrdev_region(dev_id, NR_KMLDRV);
error_alloc:
kfifo_free(&rx_fifo);
goto out;
```
>[!Note] 或許能將所有釋放資源部分移動到 goto?
### [kxo_state_show](https://github.com/sysprog21/kxo/blob/b98a4c957524ff5b241d7703c377775e33281c0d/main.c#L46C1-L55C2) & [kxo_state_store](https://github.com/sysprog21/kxo/blob/b98a4c957524ff5b241d7703c377775e33281c0d/main.c#L57C1-L67C2)
這兩個函式是透過:
```c
static DEVICE_ATTR_RW(kxo_state);
```
被註冊到:
```c
struct device_attribute dev_attr_kxo_state
```
後者也是包含在 `DEVICE_ATTR_RW` 中。RW 指的是擁有者的權限,該設備權限可被寫為 0644。
### game.[ch]
### mcts.[ch]
### negamax.[ch]
### util.h
### xo-user.c
### xoroshiro.[ch]
### zobrist.[ch]