# Linux 核心專題: 虛擬攝影機裝置驅動程式
> 執行人: yinghuaxia
> [GitHub](https://github.com/yinghuaxia/vcam)
> [專題解說錄影](https://youtu.be/cdDee9U-n14)
### reviewed by 'dcciou'
> v4l2的架構圖在深色模式時會看不到內容,是否可以換張圖呢?
> [name=Yinghua Yeh]
> 已解決
## 任務簡介
研究 [vcam](https://github.com/sysprog21/vcam) 並改進。
Reference:
* [2020 年開發紀錄](https://hackmd.io/@eecheng/B16rQ3GjU) / [GitHub](https://github.com/eecheng87/vcam)
* [2021 年開發紀錄](https://hackmd.io/@WayneLin1992/HkDBmLUDO) / [GitHub](https://github.com/WayneLin1992/vcam)
* [2022 年開發紀錄](https://hackmd.io/@Masamaloka/linux2022-vcam) / [GitHub](https://github.com/kevinshieh0225/vcam)
* [vcam 測試記錄](https://hackmd.io/@bentu/SkUFns9XI?fbclid=IwAR0dip-LYX8zLmaOWMcCdEu5w8fCWPtjBLh9TV7vH0FRQG1jmfNboDlIvI4#%E6%B8%AC%E8%A9%A6-vcam)
* [hungyuhang 開發紀錄](https://hackmd.io/7-XO3QBHRYyXT6Qmn68vPw?view)
## TODO: vcam 所需的背景知識回顧
> 探討 V4L2 和 Linux framebuffer
### vcam 運作原理
**v4l2 (Video4Linux2)**
[v4l2](https://android.googlesource.com/kernel/msm/+/android-msm-bullhead-3.10-marshmallow-dr/Documentation/video4linux/v4l2-framework.txt) 是 Linux 下關於影像收集相關裝置的驅動框架,也是一個統一的介面規範。其架構如下圖所示:
![image](https://hackmd.io/_uploads/Sy_cR6MwC.png)
:::danger
用 Graphviz 重新繪製圖片,避免品質低劣的圖表。
使用向量繪圖,不是點陣圖!
:::
Reference: [第一章 v4l2 簡介](https://work-blog.readthedocs.io/en/latest/v4l2%20intro.html#fig-v4l2-arch)
從架構圖中,我們可以將 v4l2 分為三個層級,分別為 User Space、Kernel Space 和 Hardware space。在 User Space 的包含 `/dev/videoX` 模組;在 Kernel Space 的包含 `cdev`、`video_device`、`v4l2 device` 及 `v4l2_subdev` 模組;在 Hardware space 的包含 `Camera sensor hardware` 模組。而當中的參數設置主要是透過 `ioctl` 進行設定的。
* `/dev/videoX`
User space 是使用者可以存取到的 device 節點,在圖中為 `/dev` 底下的 videoX,從 Kernel space 來看,這個 device 的主裝置編號及次裝置編號分別為 81 和 X。
:::danger
注意用語:
* character 是「字元」
* driver 在本文是 device driver 的簡稱,應翻譯為「裝置驅動程式」,避免過度簡化的「驅動」
:::
* 其連接到的 `cdev` 是個由字元驅動的核心模組,功能為連接 `video_device` 和 `/dev` 底下的 device 節點(此圖中 `/dev/videoX`),這個模組讓 User space 的使用者可以透過一些操作(像是 open、 read、 write 等)來存取 `video_device`。
* 與 `cdev` 連接的 `video device` 主要是負責影像裝置的模組,他將影像裝置的各種功能(像是 open、read、write 等)提供給上層應用的介面,從 `v4l2_subdev` 中獲得影像的資料和他的控制訊息。
* `v4l2_subdev` 代表的是一個 V4L2 的裝置,透過這個模組,應用程式就可以配置及控制影像裝置。而 `video_device` 可以藉由 `v4l2_subdev` 和具體的子裝置 (如攝影機感測器) 進行溝通。
:::danger
注意用語:
* via, through 不該翻譯為「通過」,否則你無法區隔 "pass"
:::
* `Camera Sensor Hardware` 是實際的攝影機感測器硬體,可以實際的捕捉影像的資料。
:::info
:bulb: 在 Linux Kernel 中,裝置驅動程式分為三種類型:字元裝置(Character Devices)、區塊裝置(Block Devices)和網路裝置(Network Devices)。這些裝置各自有不同的功能和應用場景,而 `cdev` 模組為字元裝置提供了基本的操作介面,並將字元裝置與內核中的設備號關聯起來。字元裝置是一種按字元為單位進行存取的裝置,使用者可以透過 read 和 write 等指令與裝置進行交互。常見的字元裝置包括鍵盤、滑鼠等,這些裝置的共同特徵就是每次操作時,電腦都會直接進行處理(像是按下滑鼠的按鍵、鍵盤輸入一個字元等)。
:::
從==使用者==的角度來對 V4L2 控制流程做觀察:
1. 使用者使用 [open()](https://man7.org/linux/man-pages/man2/open.2.html) 在 US(User Space) 通過 `/dev/videoX` 存取 影像裝置。並可以使用 [ioctl](https://man7.org/linux/man-pages/man2/ioctl.2.html) 來查詢裝置相關的參數設定,並進行裝置的配置。
2. 協商 (Negotiate) 資料格式
3. 協商 (Negotiate) 輸入 / 輸出方式,像是:
* read / write: 應用程式透過 read 和 write 的系統來讀取和寫入影像的資料,如第一點所提到的,應用程式會先使用 `open()` 打開 `/dev/video0` 裝置文件,透過 read 就可以呼叫裝置讀取資料,透過 write 就可以呼叫裝置寫入資料。
* Stream I / O: 這個方式允許應用程式自己分配自己的 buffer 區域,並將這些 buffer 區域的位址告訴裝置驅動程式,裝置驅動程式會直接將資料寫到用戶的 buffer 區域。
* overlay: 是一種 memory 的 mapping,裝置驅動程式會將一組內核的 buffer 區域映射到 User space,這樣就可以讓使用者透過應用程式直接存取這些 buffer 區域進行讀寫操作。而應用程式使用的是 [mmap](https://man7.org/linux/man-pages/man2/mmap.2.html) 的巨集來對 buffer 區域進行存取。
4. 最後需要使用 [close()](https://man7.org/linux/man-pages/man2/close.2.html) 將資源釋放並將裝置關閉。
:::danger
Negotiate 的作用是什麼?
:::
如果使用==編寫 driver==的角度對 V4L2 做觀察,編寫者只需要實作硬體相關的程式碼,並且註冊相關裝置即可,其餘部分 V4L2 已經完成配置 。除了編寫具體裝置的程式碼之外,另一個主要的操作是將程式碼與 V4L2 的框架進行綁定,包含將我們的結構與 V4L2 框架中相關結構綁定在一起,以及將 V4L2 所定義的空函式指標與自己的函式 `v4l2_device` 綁定。V4L2 中主要的結構體如下圖中橘色長方體所示:
:::danger
注意用語,務必詳閱 https://hackmd.io/@sysprog/it-vocabulary
使用本課程教材的術語,區分「函式」和「函數」。
避免使用「設置」一詞,其意思不清晰。
:::
![image](https://hackmd.io/_uploads/rkm4tNWIR.png)
如果要將我們的結構與 V4L2 框架中相關結構綁定在一起,我們需要先了解以下幾個重要的結構:
> 參考資料:
> [1.4 Video device's internal representation](https://www.kernel.org/doc/html/v4.11/media/kapi/v4l2-dev.html)
:::danger
更新上方超連結
:::
:::danger
注意用語:
* file 是「檔案」,而非「文件」(document)
:::
* `struct video_device`:這是主要的裝置結構體,用來表示一個像 `/dev/videoX` 的 V4L2 裝置,負責向 kernel 註冊字元裝置以讓 User Space 的的應用程式可以透過標準的檔案操作(如 `open`、`read`、`write`、`ioctl` )來與裝置交互。他包含指向裝置操作的指針( `v4l2_file_operations` 及 `v4l2_ioctl_ops` )、控制處理器的 pointer ( `v4l2_ctrl_handler` )之外,還有一個親代裝置指針指向 `v4l2_device`。在 vcam 程式碼中配置 `video_deive` 空間的方式如下所示:
```c
struct video_device *vdev = video_device_alloc();
if (vdev == NULL)
return -ENOMEM;
vdev->release = video_device_release;
```
:::danger
減少非必要的縮排
:::
而開發者實際註冊一個 `video_device` 可以使用 [video_register_device](https://www.kernel.org/doc/html/v4.11/media/kapi/v4l2-dev.html#c.video_register_device) 巨集;解除註冊則是使用 [video_unregister_device](https://www.kernel.org/doc/html/v4.11/media/kapi/v4l2-dev.html#c.video_unregister_device)。註冊時的程式碼如下,這個步驟會在初始化裝置之後進行:
```c
err = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
if (err) {
video_device_release(vdev); /* or kfree(my_vdev); */
return err;
}
```
從程式碼中我們可以看到需要傳入的參數包含 `vdev`,也就是我們幫 `video_device` 配置的空間;`VFL_TYPE_GRABBER`,也就是這一個 `video_device` 的種類;第三個參數是這個 `video_device` 可以控制的節點編號,`-1` 代表讓 v4l2 framework 自行選擇第一個閒置的節點編號。
`video_device` 的種類包含:
* VFL_TYPE_GRABBER: /dev/videoX for video input/output devices
* VFL_TYPE_VBI: /dev/vbiX for vertical blank data (i.e. closed captions, teletext)
* VFL_TYPE_RADIO: /dev/radioX for radio tuners
* VFL_TYPE_SDR: /dev/swradioX for Software Defined Radio tuners
* VFL_TYPE_TOUCH: /dev/v4l-touchX for touch sensors
當要對 `video_device` 進行初始化時,需要將其 `v4l2_dev` 的成員指向已經初始化的 `v4l2_deice` 結構體,以定義這個 `video_deive` 是被哪一個 `v4l2_devive` 所管理。
:::danger
struct 是「結構體」,而非「結構」
:::
:::danger
高維度?你在說線性代數嗎?
用清晰的漢語書寫!
:::
* `struct v4l2_device`:一個更高層級的裝置結構體,負責管理多個子裝置。其中包含一個指向 `v4l2_ctrl_handler` 的指標,也有一個 `list_head` 用來鏈接所有的子裝置。
:::danger
避免用低劣的機器翻譯,用你的意思去書寫,搭配指定的詞彙規範。
:::
在 vcam 專案中的 `device.c` 中可以找到初始化一個 `v4l2_deice` 的程式碼,使用的是 `v4l2_deice_register` 來進行初始化的動作:
```c
/* Register V4L2 device */
snprintf(vcam->v4l2_dev.name, sizeof(vcam->v4l2_dev.name), "%s-%d",
vcam_dev_name, (int) idx);
ret = v4l2_device_register(NULL, &vcam->v4l2_dev);
if (ret) {
pr_err("v4l2 registration failure\n");
goto v4l2_registration_failure;
```
* `struct v4l2_subdev`:具體的子裝置結構體,包含多種操作 ( `v4l2_subdev_ops` ),而這些操作會再被進一步劃分為不同的類別。
上面三個結構體均包含 [v4l2_ctrl_handler](https://www.kernel.org/doc/html/v4.11/media/kapi/v4l2-controls.html#objects-in-the-framework) 的指標,這是一個負責處理裝置的控制命令,負責持續追蹤控制操作,其包含一個他擁有的 `v4l2_ctrl` 物件清單,代表具體的控制項,像是裝置的類型、名稱、步長等訊息。透過 `v4l2_strl_handler`,裝置驅動程式可以統一管理裝置的控制項,處理來自 User Space 的控制命令,並將這些轉換成對於硬體的具體操作。
### 何以 vcam 能夠符合預期地註冊為 V4L2 裝置驅動程式?
`vcam` 可以符合預期的註冊為 V4L2 裝置驅動程式的原因是因為他遵守了 V4L2 API 的設計規範,以下包含幾個具體的操作步驟:
1. 定義 V4L2 的 video_device,並配置相關的操作。
2. 進行初始化和資源的分配。
3. 使用 `video_register_device` 將 video device 裝置註冊到 v4l2_device。
4. 實作必要的 `ioctl` 操作,像是 `VIDIOC_QUERYCAP`、`VIDIOC_ENUM_FMT`、`VIDIOC_G_FMT`、`VIDIOC_S_FMT` 等。
5. 進行錯誤處理及回報,以避免系統崩潰。
### Framebuffer 簡介
> 參考資料:
> [1.13 Video Framework](https://www.kernel.org/doc/html/v4.11/media/kapi/v4l2-videobuf.html)
> [Wikipedia Framebuffer](https://en.wikipedia.org/wiki/Linux_framebuffer)
<s>Framebuffer 是 RAM 中的一部分</s>
[Framebuffer](https://en.wikipedia.org/wiki/Linux_framebuffer) 是系統 RAM 中的一個專門區域,用來儲存和更新顯示裝置上呈現的影像。
:::danger
使用 Linux 核心內附的文件 (Documentations 目錄) 和程式碼來解說,不要淪落為「搬運工」。注意用語!
:::
,可以視為一個 V4L2 driver 和 User Space 的連接,專門儲存裝置驅動影像播放的 bitmap,是一個包含影像中所有 pixel 的 memory buffer。當螢幕的顯示需要更新時,應用程式可以透過 <s>I / O 技術</s> 將新的圖像資料寫入 framebuffer 的顯示卡記憶體區域,顯示器則會從 framebuffer 中讀取資料並輸出到裝置上。
:::danger
注意用語。
memory 要看場景來翻譯,不要偷懶標示「(主)」
:::
而並不是所有的裝置都使用相同的的 buffer,buffer 有三種常見的種類,我們可以依照需求選擇適當的 buffer 類型作為 framebuffer:
* Buffers which are scattered in both the physical and (kernel) virtual address. 幾乎所有的 User Space buffer 是這種類型,這種 buffer 的配置及使用較靈活,但這種類型的 buffer 會需要可以支持 scatter/gather [DMA](https://en.wikipedia.org/wiki/Direct_memory_access) 操作的硬體。
:::danger
何謂 scatter/gather DMA?
:::
* Buffers which are physically scattered, but which are virtually contigous. 這種 buffer 可以被用於 DMA 操作不可用的情形,其 virtually contigous 的特性讓其相對較容易操作。
* Buffers which are physically contigous. 這種 buffer 會被使用在 DMA 沒有支援 scatter / gather 的功能時。
使用以下表格統整三種 buffer 的使用時機,並概述 framebuffer 在甚麼時機應該選用哪種 buffer 類型:
| Buffer type | 適用情形 |
|:---------------------------------------:|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|
| Both scattered | 通常適用於高性能的 video captured 裝置,這些裝置可以處理分散的(主) 記憶體區塊。這種方式在(主) 記憶體碎片較多的系統中特別有用,因為它不需要大塊連續的(主) 記憶體。 |
| Physically scattered, virtual contigous | 這類 buffer 在 kernel space 中的操作方便,適合那些需要 kernel 層面進行大量處理的影像應用,例如影片解碼或圖像處理等。但由於硬體位址上不連續,不適合簡單的 DMA 操作。 |
| Physically contigous | 這是傳統 video captured 和顯示裝置最常用的 buffer 類型,特別是在 DMA 操作必須非常高效的情況下。這些 buffer 確保資料在硬體和(主) 記憶體之間的快速傳輸,但在(主) 記憶體管理上需要更加小心以避免碎片問題。 |
### vcam 的 Colorspace
> 參考資料:
> [2.15 Defining Colorspaces in V4L2](https://www.kernel.org/doc/html/v4.15/media/uapi/v4l/colorspaces-defs.html)
> [Video4Linux2 part 5a: colors and formats](https://lwn.net/Articles/218798/)
Colorspace 的意思是用來描述顏色的座標空間,V4L2 中定義了不同種類的 colorspace,但只有兩種較常被使用,分別是
1. `V4L2_COLORSPACE_SRGB`:使用 [red, green, blue] tuple 來儲存顏色,這個 colorspace 同時也可與 [YUV](https://zh.wikipedia.org/zh-tw/YUV) 和 [YCbCr](https://en.wikipedia.org/wiki/YCbCr) colorspace 做轉換。
2. `V4L2_COLORSPACE_SMPTE170M`:用來表示類比訊號,常用於 NTSC 或 PAL 電視中。
在 vcam 裝置驅動程式中需要使用不同的 colorspace 的原因是因為要確保虛擬相機能夠更正確的模擬真實裝置的顏色和顯示。根據具體的應用需求和顯示裝置選擇適當的 colorspace,可以提高顏色準確性和一致性,滿足不同應用場景的需求。
### vcam 的 I / O
> 參考資料:[Video4Linux2 part 4: inputs and outputs](https://lwn.net/Articles/213798/)
:::danger
注意用語。
:::
在 V4L2 的裝置驅動程式中,I / O 負責處理應用程式與驅動程式間的資料傳輸。在很多情況下,一個 video adapter 不會提供多種 I / O 的選項,比如說相機控制器可以提供相機介面,但幾乎沒有提供其他選項。有一些較複雜的 video adapter,像是電視卡,會有多個輸入介面來連接到不同的連接器,這些不同的介面與對應的調節器 ([tuner](https://en.wikipedia.org/wiki/TV_tuner_card)) 可以讓輸入的介面有不同的特性。
:::danger
注意用語!
詳閱 https://hackmd.io/@sysprog/it-vocabulary
:::
:::info
:bulb: Video adapter 指的是在電腦中的圖形卡,用來連接顯示器與電腦,讓電腦可以輸出文字或是影像到顯示器上,通常由 GPU (圖形處理單元)、顯示卡記憶體 (VRAM) 、輸出介面 (像是 HDMI、VGA 等) 和散熱系統組成。他的工作流程為:
1. CPU 將要算繪的圖形資料傳送到 GPU,GPU 負責執行算繪的動作。
2. GPU 將這些算繪過的圖像資料儲存在記憶體當中,這些圖像相關的資料組成最終顯示的圖像。
3. 最後再將顯示卡記憶體中儲存的圖像資料通過輸出介面轉換成影像的訊號,傳送到顯示裝置上進行顯示。
:::
:::danger
務必尊重台灣資訊科技前輩的篳路藍縷,使用存在數十年的經典用語,避免濫用「共製漢字」詞彙。
:::
以下對輸入命令進行說明:
* 使用 `VIDIOC_ENUMINPUT` 將所有的輸入源列出
```c
int (*vidioc_enum_input)(struct file *file, void *private_data,
struct v4l2_input *input);
```
`file` 對應的是要開啟的影像裝置,`private_data` 指的是 driver 設定的 private field,`input` 則是真實資訊通過的區域,其中列出幾點:
* `__u32 index`: 唯一一個可以在 User Space 設置的參數,指的是使用者有興趣的 index number inputs。
* `__u8 name`: input 的名稱。
* `__u32 type`: input 的類別。
* `__u32 audioset`: 描述哪一個音訊的 input 可以與影像的 input 結合。
* `__u32 status`: 印出 input 的狀態。
### vcam 測試紀錄
測試環境:
```shell
$ uname -a
Linux yinghuaxia-Altos-P30-F6 6.5.0-35-generic #35~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Tue May 7 09:00:52 UTC 2 x86_64 x86_64 x86_64 GNU/Linux
```
在執行 `make` 時遇到以下問題:
```shell=
yinghuaxia@yinghuaxia-Altos-P30-F6:~/vcam$ make
make -C /lib/modules/6.5.0-35-generic/build M=/home/yinghuaxia/vcam modules
make[1]: Entering directory '/usr/src/linux-headers-6.5.0-35-generic'
warning: the compiler differs from the one used to build the kernel
The kernel was built by: x86_64-linux-gnu-gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0
You are using: gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0
CC [M] /home/yinghuaxia/vcam/module.o
CC [M] /home/yinghuaxia/vcam/control.o
In file included from ./include/linux/linkage.h:7,
from ./arch/x86/include/asm/cache.h:5,
from ./include/linux/cache.h:6,
from ./arch/x86/include/asm/current.h:9,
from ./include/linux/sched.h:12,
from ./include/linux/ratelimit.h:6,
from ./include/linux/dev_printk.h:16,
from ./include/linux/device.h:15,
from /home/yinghuaxia/vcam/control.c:3:
/home/yinghuaxia/vcam/control.c: In function ‘create_control_device’:
./include/linux/export.h:29:22: error: passing argument 1 of ‘class_create’ from incompatible pointer type [-Werror=incompatible-pointer-types]
29 | #define THIS_MODULE (&__this_module)
| ~^~~~~~~~~~~~~~~
| |
| struct module *
/home/yinghuaxia/vcam/control.c:303:38: note: in expansion of macro ‘THIS_MODULE’
303 | ctldev->dev_class = class_create(THIS_MODULE, dev_name);
| ^~~~~~~~~~~
In file included from ./include/linux/device.h:31:
./include/linux/device/class.h:230:54: note: expected ‘const char *’ but argument is of type ‘struct module *’
230 | struct class * __must_check class_create(const char *name);
| ~~~~~~~~~~~~^~~~
/home/yinghuaxia/vcam/control.c:303:25: error: too many arguments to function ‘class_create’
303 | ctldev->dev_class = class_create(THIS_MODULE, dev_name);
| ^~~~~~~~~~~~
./include/linux/device/class.h:230:29: note: declared here
230 | struct class * __must_check class_create(const char *name);
| ^~~~~~~~~~~~
cc1: some warnings being treated as errors
make[3]: *** [scripts/Makefile.build:251: /home/yinghuaxia/vcam/control.o] Error 1
make[2]: *** [/usr/src/linux-headers-6.5.0-35-generic/Makefile:2039: /home/yinghuaxia/vcam] Error 2
make[1]: *** [Makefile:234: __sub-make] Error 2
make[1]: Leaving directory '/usr/src/linux-headers-6.5.0-35-generic'
make: *** [Makefile:14: kmod] Error 2
```
從第 35 行中,我們可以看到 `class_create` 這個函式只接受一個參數,且該參數為 `const char *` 類別,但我們從第 31 行的錯誤訊息中可以看到 `too many arguments to function 'class_create'` 且在 `control.c` 中的 `ctldev->dev_class = class_create(MY_MODULE, dev_name)` 中傳入了兩個參數,其中 `MY_MODULE` 在錯誤訊息第 20 行中是 `struct module *`,`dev_name` 是呼叫 `create_control_device` 時傳入的 `const char *` 類別,因此推測不應該出現的參數為 `MY_MODULE`。將 `control.c` 檔案中的 `ctldev->dev_class = class_create(MY_MODULE, dev_name);` 中的 `MY_MODULE` 刪除。
```diff
- ctldev->dev_class = class_create(THIS_MODULE, dev_name);
+ ctldev->dev_class = class_create(dev_name);
```
:::danger
將心比心,你希望被人叫做「有人」嗎?明確標注 GitHub 帳號名稱。
:::
在 [vcam](https://github.com/sysprog21/vcam) 的專案上 [hungyuhang](https://github.com/hungyuhang) 也已經提交變更:[vcam commit a7e6f0e](https://github.com/sysprog21/vcam/commit/a7e6f0ea18ee3eb49e3db9649ca5b94cc7f2563b)
重新執行 `make` 命令,會發生以下錯誤:
```shell
make -C /lib/modules/6.5.0-35-generic/build M=/home/yinghuaxia/vcam modules
make[1]: Entering directory '/usr/src/linux-headers-6.5.0-35-generic'
warning: the compiler differs from the one used to build the kernel
The kernel was built by: x86_64-linux-gnu-gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0
You are using: gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0
CC [M] /home/yinghuaxia/vcam/control.o
CC [M] /home/yinghuaxia/vcam/device.o
CC [M] /home/yinghuaxia/vcam/videobuf.o
CC [M] /home/yinghuaxia/vcam/fb.o
/home/yinghuaxia/vcam/fb.c: In function ‘vcamfb_open’:
/home/yinghuaxia/vcam/fb.c:22:31: error: implicit declaration of function ‘PDE_DATA’; did you mean ‘NODE_DATA’? [-Werror=implicit-function-declaration]
22 | struct vcam_device *dev = PDE_DATA(ind);
| ^~~~~~~~
| NODE_DATA
/home/yinghuaxia/vcam/fb.c:22:31: warning: initialization of ‘struct vcam_device *’ from ‘int’ makes pointer from integer without a cast [-Wint-conversion]
/home/yinghuaxia/vcam/fb.c: In function ‘vcamfb_release’:
/home/yinghuaxia/vcam/fb.c:44:31: warning: initialization of ‘struct vcam_device *’ from ‘int’ makes pointer from integer without a cast [-Wint-conversion]
44 | struct vcam_device *dev = PDE_DATA(ind);
| ^~~~~~~~
cc1: some warnings being treated as errors
make[3]: *** [scripts/Makefile.build:251: /home/yinghuaxia/vcam/fb.o] Error 1
make[2]: *** [/usr/src/linux-headers-6.5.0-35-generic/Makefile:2039: /home/yinghuaxia/vcam] Error 2
make[1]: *** [Makefile:234: __sub-make] Error 2
make[1]: Leaving directory '/usr/src/linux-headers-6.5.0-35-generic'
make: *** [Makefile:14: kmod] Error 2
```
在 [extensions: replace \`PDE_DATA\`](https://lore.kernel.org/all/20220204132643.1212741-2-jeremy@azazel.net/) 中提到:
```
The `PDE_DATA` function for retrieving private data from a procfs inode
has been replaced by `pde_data` in 5.17. Replace all instances of the
former with the latter, but add a macro to xtables_compat.h in order to
preserve compatibility with older kernels.
```
而在 [proc: remove PDE_DATA() completely](https://lore.kernel.org/all/20220122061423.HMq1py5Q0%25akpm@linux-foundation.org/) 也有寫到以下的更動:
```
Remove PDE_DATA() completely and replace it with pde_data().
```
因此將 `fb.c` 中的 `PDE_DATA` 改成 `pde_data` 即可解決此問題。
接著執行 `$ sudo ./vcam-util -l` 可以得到以下結果:
```shell
Available virtual V4L2 compatible devices:
1. fb1(640,480,rgb24) -> /dev/video0
```
查看 `vcam-util.c` 檔案,`-l` 命令會執行 `list_devices()` 函式,先使用 [ioctl](https://man7.org/linux/man-pages/man2/ioctl.2.html) 函式來獲得現在有的裝置訊息,並將結果存在 `dev` 中,隨後將 `dev.idx`, `dev.fb_node`, `dev.width`, `dev.pix_fmt` 以及 `dev.video_node` 的結果印出。`dev` 的架構如下所示:
```c
struct vcam_device_spec {
unsigned int idx;
unsigned short width, height;
pixfmt_t pix_fmt;
char video_node[64];
char fb_node[64];
};
```
內容包含索引值、framebuffer 的節點、虛擬攝影機裝備的寬度與高度(此虛擬裝置的解析度)、像素的格式 (rgb24 或 yuyv) 以及影片的節點。
透過 `ls /dev/video*` 來確認具體的裝置號為 `/dev/video0` 以進行後續對 driver 的測試:
```shell
yinghuaxia@yinghuaxia-Altos-P30-F6:~/vcam$ ls /dev/video*
/dev/video0
```
[v4l2-compliance](https://manpages.ubuntu.com/manpages/bionic/man1/v4l2-compliance.1.html) 命令是用來測試 video4linux 裝置(包含輸入及輸出)的命令,且幾乎覆蓋到全部的 v4l2 ioctl。這個命令測試的不僅是裝飾是否遵守 v4l2 API,也測試了 driver 是否使用正確的框架。如果使用者想要提交一個新的 v4l2 driver,那該 driver 必須通過 `c4l2-compliance` 的命令且不出錯。以下針對幾個常用的命令參數進行解說:
```
-d, --device=<dev>
Use device <dev> as the video device. If <dev> is a number, then /dev/video<dev> is used.
-f, --stream-all-formats
Test whether all available formats can be streamed. This attempts to stream using MMAP mode or read/write (if V4L2_MEMORY_MMAP is not available) for one second for all formats, at all sizes, at all intervals and with all field values. In addition, if the driver supports scaling, cropping or composing it will test that as well in various combinations. If the driver supports a lot of combinations then this test can take a long time.
The configuration of the driver at the time v4l2-compliance was called will be used for the streaming tests.
-s, --streaming=<count>
Enable the streaming tests. Set <count> to the number of frames to stream (default 60). This requires that before v4l2-compliance is called the device has been configured with a valid input (or output) and frequency (when the device has a tuner). For DMABUF testing --expbuf-device needs to be set as well.
The configuration of the driver at the time v4l2-compliance was called will be used for the streaming tests.
```
以下為執行此命令的結果,旨在測試 video0 所有可使用的格式是否可以被串流:
```shell
$ sudo v4l2-compliance -d /dev/video0 -f
...
Total for vcam device /dev/video0: 47, Succeeded: 47, Failed: 0, Warnings: 0
```
[v4l2-ctl](https://manpages.ubuntu.com/manpages/xenial/man1/v4l2-ctl.1.html) 則是控制 video4linux 裝置的命令,以下為幾個常用參數:
```
-d, --device=<dev>
Use device <dev> as the V4L2 device. If <dev> is a number, then /dev/video<dev> is used.
-w, --wrapper
Use the libv4l2 wrapper library for all V4L2 device accesses. By default v4l2-ctl will directly access the V4L2 device, but with this option all access will go via this wrapper library.
--all Display all information available.
```
執行 `$ sudo v4l2-ctl -d /dev/video0 --all` 命令的結果:
```shell
v4l2-compliance 1.22.1, 64 bits, 64-bit time_t
Compliance test for vcam device /dev/video0:
Driver Info:
Driver name : vcam
Card type : vcam
Bus info : platform: virtual
Driver version : 6.5.13
Capabilities : 0x85200001
Video Capture
Read/Write
Streaming
Extended Pix Format
Device Capabilities
```
在掛載 `vcam.ko` 的前後可以使用 `ls /dev` 命令來觀察掛載前後模組的差異。在我的裝置中,掛載後多了 `video0` 和 `fb1` 等,因此再使用 `$ sudo fbset -fb /dev/fb1 --info` 命令來觀察 framebuffer `fb1` 的資訊。
```shell
mode "640x480"
geometry 640 480 640 480 24
timings 0 0 0 0 0 0 0
rgba 8/0,8/8,8/16,0/0
endmode
Frame buffer device information:
Name : vcamfb
Address : 0xffff9f1346979000
Size : 921600
Type : PACKED PIXELS
Visual : TRUECOLOR
XPanStep : 1
YPanStep : 1
YWrapStep : 1
LineLength : 1920
Accelerator : No
```
### 使用 vcam driver
在完全沒有寫入 vcam 的 framebuffer 時,用 `vlc v4l2:///dev/video0` 顯示的預設畫面如下所示,為一個漸層的畫面:
![image](https://hackmd.io/_uploads/Sk4fd_b4A.png)
如果我們想要對 vcam 所呈現的畫面做更動時,我們要將程式碼寫入其對應的 framebuffer 中。根據 [2020 開發紀錄](https://hackmd.io/@eecheng/B16rQ3GjU#%E6%B7%B1%E5%85%A5%E6%93%8D%E4%BD%9C),我們可以先建立一個檔案 `test.c` ,當中是我們想要對 framebuffer 所呈現畫面進行的操作,對該檔案進行編譯之後,使用 `./test > /proc/vcamfb` 將檔案寫入 vcam 對應的 framebuffer 中。然而會出現關於沒有 `/proc/vcamfb` 的報錯,當我們將 `/proc/` 底下的檔案列出後,發現當中有一個 `fb` 檔案,對其執行 `cat /proc/fb` 的命令來查看內容時發現 `vcamfb` 其實存在:
```shell
$ cat /proc/fb
0 i915drmfb
1 vcamfb
```
這個結果表示系統中有兩個 framebuffer 的裝置,包含 `i915drmfb` 和 `vcamfb`。
更仔細的去探究兩個檔案之間的差異,發現 [/proc](https://man7.org/linux/man-pages/man5/procfs.5.html) 是一個虛擬的檔案系統,其提供一個界面,讓使用者獲得系統相關的訊息,當中大部分的檔案是唯讀的。而其中的 `/proc/fb` 檔提供當 kernel 被編譯時所定義的 framebuffer 的相關資訊。[/dev](https://tldp.org/LDP/Linux-Filesystem-Hierarchy/html/dev.html) 則是各種 device 儲存的位置,我們可以透過以下命令查看 `/dev` 下的 `fb0` 和 `fb1` 對應到 `/proc/fb` 中的哪一個裝置名稱:
```shell
$ cat /sys/class/graphics/fb0/name
i915drmfb
$ cat /sys/class/graphics/fb1/name
vcamfb
```
因此我們可以確認 `fb1` 在 `/proc/fb` 底下對應到的是 `vcamfb` 這個名稱。我們重新改寫命令為 `$ sudo sh -c './test > /dev/fb1'`,將想要對 vcam 畫面做的操作寫入 `fb1` 中,就可以成功的將想進行的操作呈現:
![rgb](https://hackmd.io/_uploads/Byh4YeXNA.gif)
:::danger
注意用語!
* memory 是「(主) 記憶體」,而非「內存」
* implement 是「實作」
* device 是「裝置」,而非「設備」
* interface 是「介面」,而非「接口」
* data 是「資料」,而非「數據」
* create 是「建立」,而非「創建」
* process 是「行程」,而非「進程」
* access 是「存取」,而非「訪問」(visit)
* call 是「呼叫」,而非「調用」
* character device 是「字元裝置」,而非「字符設備」
* command 是「命令」,而非「指令」(instruction)
參見 [詞彙對照表](https://hackmd.io/@l10n-tw/glossaries) 和 [資訊科技詞彙翻譯](https://hackmd.io/@sysprog/it-vocabulary)
> 已更改
:warning: 不!你沒做到 (例如「字符」的誤用),務必詳細閱讀[詞彙對照表](https://hackmd.io/@l10n-tw/glossaries) 和 [資訊科技詞彙翻譯](https://hackmd.io/@sysprog/it-vocabulary),隨後進行用語調整,尊重台灣資訊科技前輩的篳路藍縷。
:::
Reference: https://hackmd.io/@yinghuaxia/linux2024-final
:::danger
將上方超連結指向的內容,展開在本頁面。
:::
> 針對 Linux v6.8 進行調整
## TODO: 修正 vcam 並使其得以運作在 Linux v6.8
> 解釋 Linux 核心原始程式碼的相關異動,探討開發者的考量
```shell
Linux yinghuaxia-Altos-P30-F6 6.8.0-36-generic #35~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Mon Jun 10 10:49:14 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
```
在 make 的時候會遇到以下問題:
```shell
make -C /lib/modules/6.8.0-36-generic/build M=/home/yinghuaxia/Downloads/vcam-master modules
make[1]: Entering directory '/usr/src/linux-headers-6.8.0-36-generic'
warning: the compiler differs from the one used to build the kernel
The kernel was built by: x86_64-linux-gnu-gcc-13 (Ubuntu 13.2.0-23ubuntu4) 13.2.0
You are using: gcc-13 (Ubuntu 13.2.0-23ubuntu4) 13.2.0
CC [M] /home/yinghuaxia/Downloads/vcam-master/module.o
CC [M] /home/yinghuaxia/Downloads/vcam-master/control.o
CC [M] /home/yinghuaxia/Downloads/vcam-master/device.o
CC [M] /home/yinghuaxia/Downloads/vcam-master/videobuf.o
/home/yinghuaxia/Downloads/vcam-master/videobuf.c: In function ‘vcam_out_videobuf2_setup’:
/home/yinghuaxia/Downloads/vcam-master/videobuf.c:138:6: error: ‘struct vb2_queue’ has no member named ‘min_buffers_needed’
138 | q->min_buffers_needed = 2;
| ^~
make[3]: *** [scripts/Makefile.build:243: /home/yinghuaxia/Downloads/vcam-master/videobuf.o] Error 1
make[2]: *** [/usr/src/linux-headers-6.8.0-36-generic/Makefile:1926: /home/yinghuaxia/Downloads/vcam-master] Error 2
make[1]: *** [Makefile:240: __sub-make] Error 2
make[1]: Leaving directory '/usr/src/linux-headers-6.8.0-36-generic'
make: *** [Makefile:14: kmod] Error 2
```
會發生這個錯誤的原因在於在 Linux v6.8 中,`min_buffers_needed` 被重新命名為 `min_queued_buffers`,因此修改 vcam/videobuf.c 中的 `min_buffers_needed` 參數即可解決此問題。
Reference: [[v2,36/36] videobuf2: core: Rename min_buffers_needed field to vb2_queue](https://patches.linaro.org/project/linux-media/patch/20231204133920.23930-1-benjamin.gaignard@collabora.com/)
第二個會遇到的問題是:
```shell
make -C /lib/modules/6.8.0-36-generic/build M=/home/yinghuaxia/Downloads/vcam-master modules
make[1]: Entering directory '/usr/src/linux-headers-6.8.0-36-generic'
warning: the compiler differs from the one used to build the kernel
The kernel was built by: x86_64-linux-gnu-gcc-13 (Ubuntu 13.2.0-23ubuntu4) 13.2.0
You are using: gcc-13 (Ubuntu 13.2.0-23ubuntu4) 13.2.0
CC [M] /home/yinghuaxia/Downloads/vcam-master/videobuf.o
CC [M] /home/yinghuaxia/Downloads/vcam-master/fb.o
/home/yinghuaxia/Downloads/vcam-master/fb.c: In function ‘vcamfb_init’:
/home/yinghuaxia/Downloads/vcam-master/fb.c:405:19: error: ‘FBINFO_FLAG_DEFAULT’ undeclared (first use in this function); did you mean ‘FAULT_FLAG_DEFAULT’?
405 | info->flags = FBINFO_FLAG_DEFAULT;
| ^~~~~~~~~~~~~~~~~~~
| FAULT_FLAG_DEFAULT
/home/yinghuaxia/Downloads/vcam-master/fb.c:405:19: note: each undeclared identifier is reported only once for each function it appears in
make[3]: *** [scripts/Makefile.build:243: /home/yinghuaxia/Downloads/vcam-master/fb.o] Error 1
make[2]: *** [/usr/src/linux-headers-6.8.0-36-generic/Makefile:1926: /home/yinghuaxia/Downloads/vcam-master] Error 2
make[1]: *** [Makefile:240: __sub-make] Error 2
make[1]: Leaving directory '/usr/src/linux-headers-6.8.0-36-generic'
make: *** [Makefile:14: kmod] Error 2
```
原因是因為 `FBINFO_FLAG_DEFAULT` 這個參數已經沒有在 Linux v6.8 中被使用,因此將該參數刪除或改為 0 即可解決此問題:
:::danger
你如何確認?只靠肉眼觀察嗎?是否有更科學的方式檢驗裝置驅動程式運作的機制?
:::
根據 [commit b4f470a](https://github.com/sunflower2333/linux/commit/b4f470aef4492315190bca1dadc693f2a52b0fad) 中所提到的:
```
The flag FBINFO_FLAG_DEFAULT is 0 and has no effect, as struct
fbinfo.flags has been allocated to zero by kzalloc(). So do not
set it.
Flags should signal differences from the default values. After cleaning
up all occurrences of FBINFO_DEFAULT, the token will be removed.
```
我們可以知道這個改變是因為 [kzalloc()](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&cad=rja&uact=8&ved=2ahUKEwirm-mR9_uGAxX2ja8BHY6kDWsQFnoECAwQAQ&url=https%3A%2F%2Flwn.net%2FArticles%2F147014%2F&usg=AOvVaw3in_DcS89suMnyLsUq-sWg&opi=89978449) 在初始化的步驟已經把 `fbinfo.flags` 設為零了,因此原本被拿作為初始化的 `FBINFO_FLAG_DEFAULT = 0` 就可以在移除所有相關的參數後直接被移除。
```diff
- info->flags = FBINFO_FLAG_DEFAULT;
+ info->flags = 0;
```
Reference: [[v4,17/18] fbdev: Remove FBINFO_DEFAULT and FBINFO_FLAG_DEFAULT](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&cad=rja&uact=8&ved=2ahUKEwj90pvNyYiHAxVClK8BHSc7DxMQFnoECBAQAQ&url=https%3A%2F%2Fpatchwork.kernel.org%2Fproject%2Flinux-sh%2Fcover%2F20230715185343.7193-1-tzimmermann%40suse.de%2F&usg=AOvVaw1-xEXzYFENl_ykCg1R6laa&opi=89978449)
:::danger
解讀 Linux 核心相關變革,善用 git log / git blame 追蹤核心開發者的考量因素。
:::
此時編譯可以成功通過,但會有另外一個問題產生:
```shell
Skipping BTF generation for /home/yinghuaxia/Downloads/vcam-master/vcam.ko due to unavailability of vmlinux
```
這個錯誤可能與 Kernel 或編譯器是否支援 [BTF](https://www.kernel.org/doc/html/next/bpf/btf.html) 有關,仍需進行相關測試。
:::warning
待確認
:::
## TODO: 貢獻 vcam
> 從 https://github.com/sysprog21/vcam/issues 挑選 `#35` 並嘗試排除,適度更新你的觀察和推論,若需要程式碼修正,則提交 pull request