心得

簡單說明Linux驅動程式

簡單說明Linux驅動程式

Char Device

Kernel設備驅動程式是將底層硬體公開給系統其餘部分的機制。

作為嵌入式系統的開發者,您需要了解這些設備驅動程式如何適合整體架構,以及如何從用戶空間程式中訪問它們。

您的系統可能會有一些新奇的硬體組件,您需要找到一種方法來訪問它們。

在許多情況下,您會發現已經為您提供了一些設備驅動程式,您可以在不編寫任何Kernel代碼的情況下實現所需的一切。

例如,您可以使用 sysfs 中的文件來操作 GPIO 引腳和 LED,並且有庫可以訪問串行匯流排,包括 SPI(串行週邊介面)和 I2C(雙線串行匯流排)。

字符設備(Character Device)適用於 非緩衝的輸入/輸出(unbuffered I/O) 操作,提供了多種豐富的功能,並在應用程式代碼和驅動程式之間保持了簡潔的界面。當您需要實現自訂的設備驅動程式時,這通常是首選方案。換句話說,它能夠讓您的應用程式直接和硬體互動,而不需要太多繁雜的中間步驟。(這裡的"中間步驟"是指介於應用程式和驅動程式之間的額外操作、轉換或處理,這可能會增加代碼的複雜性和執行時間。)

例如,您可能會通過 /sys/class/gpio 目錄下的一組文件來訪問 GPIO 驅動程式。

字符設備在用戶空間中通過稱為設備節點的特殊文件來識別。這個文件名通過主要和次要編號來映射到設備驅動程式。主要編號將設備節點映射到特定的驅動程式,次要編號則告訴驅動程式訪問的介面。

例如,在 ARM Versatile PB 上,

第一個串口的設備節點是 /dev/ttyAMA0

它的主要編號是 204,次要編號是 64

第二個串口的設備節點有相同的主要編號,但次要編號是 65。這些編號可以從目錄清單中看到。

# ls -l /dev/ttyAMA*
crw-rw---- 1 root root 204, 64 Jan 1 1970 /dev/ttyAMA0
crw-rw---- 1 root root 204, 65 Jan 1 1970 /dev/ttyAMA1
crw-rw---- 1 root root 204, 66 Jan 1 1970 /dev/ttyAMA2
crw-rw---- 1 root root 204, 67 Jan 1 1970 /dev/ttyAMA3

標準的主要(major number) 和次要(minor number)編號清單可以在Linux Kernel文檔中的 Documentation/devices.txt 找到。該清單不會經常更新,且不包含前面段落中描述的 ttyAMA 設備。然而,如果您查看位於 drivers/tty/serial/amba-pl011.c 的Kernel源代碼,您將看到major number和minor number的define:

#define SERIAL_AMBA_MAJOR 204
#define SERIAL_AMBA_MINOR 64

這些聲明指定了特定設備(例如 ttyAMA)的major number和minor number,以便Kernel和應用程式可以識別和訪問這些設備。雖然該標準清單可能不包含所有設備,但它提供了一個參考,供開發者了解如何設定major number和minor number以進行設備識別。

當存在多個同類型的設備實例時,例如 ttyAMA 驅動程式,形成設備節點名稱的慣例是採用基本名稱 ttyAMA,然後在這個例子中從 0 到 3 添加實例編號。

我的raspberry pi 如下
root@raspberrypi:~# ls -l /dev/ttyAMA*
crw-rw---- 1 root dialout 204, 64 Aug  5 10:38 /dev/ttyAMA0

P.S. 在一些系統中,特別是像 Linux 等操作系統中,串口(UART)設備的命名可能會類似於 ttyS0、ttyS1 等。不同的系統和設備可能會有不同的命名慣例。 在 Raspberry Pi 上,UART 串口的命名通常是 ttyAMA0。但是,如果您使用的是較新的 Raspberry Pi 版本,例如 Raspberry Pi 3 或更高版本,可能會使用 ttyS0 來表示第一個 UART 串口。

linux 裝置管理員 有以下這麼多種:

devtmpfs:當設備驅動程式使用由驅動程式提供的基本名稱(例如 ttyAMA)和實例編號註冊新的設備介面時,設備節點將被創建。

udev 或 mdev(不使用 devtmpfs):與使用 devtmpfs 基本相同,不同之處在於需要使用用戶空間的守護程序從 sysfs 中提取設備名稱並創建節點。稍後我會談到 sysfs。

mknod:如果您正在使用靜態設備節點,則可以使用 mknod 手動創建它們。

major number已經擴展為12位元,有效的編號範圍從1到4,095,而minor number則擴展為20位元,範圍從0到1,048,575。

當你打開一個字符設備節點時,內核會檢查主要和次要編號是否落在由字符設備驅動程式註冊的範圍內。如果是,它會將呼叫傳遞給驅動程式,否則打開呼叫將失敗。設備驅動程式可以提取次要編號,以了解要使用哪個硬體介面。

所以 major number + minor number = 32個位元

假設你有個應用程式(Application)如下,一個簡單的例子是虛擬隨機數發生器 urandom,每次讀取它時會返回隨機數據的位元組:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(void)
{
    int f;
    unsigned int rnd;
    int n;
    f = open("/dev/urandom", O_RDONLY);
    if (f < 0) {
        perror("Failed to open urandom");
        return 1;
    }
    n = read(f, &rnd, sizeof(rnd));
    if (n != sizeof(rnd)) {
        perror("Problem reading urandom");
        return 1;
    }
        printf("Random number = 0x%x\n", rnd);
    close(f);
    return 0;
}

我的RPI如下:

/dev/urandom 不是一個真的硬體,但軟體RD 喜歡這樣,把東西都給虛擬化、抽象化。

blog圖片的連結

Finding out about drivers at runtime

一旦您運行了 Linux 系統,了解已加載的設備驅動程式以及它們的狀態將非常有用。您可以通過讀取 /proc 和 /sys 目錄中的文件來獲得許多信息。

# cat /proc/devices
Character devices:
  1 mem
  4 /dev/vc/0
  4 tty
  4 ttyS
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  5 ttyprintk
  7 vcs
 10 misc
 13 input
 14 sound
 29 fb
 81 video4linux
116 alsa
128 ptm
136 pts
180 usb
189 usb_device
204 ttyAMA
226 drm
240 cec
241 media
242 uio
243 hidraw
244 rpmb
245 bcm2835-gpiomem
246 vc-mem
247 bsg
248 watchdog
249 ptp
250 pps
251 lirc
252 rtc
253 dma_heap
254 gpiochip

Block devices:
  1 ramdisk
  7 loop
  8 sd
 65 sd
 66 sd
 67 sd
 68 sd
 69 sd
 70 sd
 71 sd
128 sd
129 sd
130 sd
131 sd
132 sd
133 sd
134 sd
135 sd
179 mmc
259 blkext

/proc 是告訴我,目前那些東西已經啟動

root@raspberrypi:~# cd /proc/
root@raspberrypi:/proc# ls
1    208  281  405  522  684        cgroups      key-users      swaps
10   209  285  41   53   688        cmdline      kmsg           sys
100  210  289  42   553  690        consoles     kpagecgroup    sysrq-trigger
11   211  29   420  554  741        cpu          kpagecount     sysvipc
112  212  3    421  567  743        cpuinfo      kpageflags     thread-self
114  213  32   43   58   744        crypto       latency_stats  timer_list
12   214  33   44   59   746        devices      loadavg        tty
13   22   35   451  591  749        device-tree  locks          uptime
138  23   36   452  6    88         diskstats    meminfo        version
14   24   360  456  60   89         driver       misc           vmallocinfo
146  253  362  464  61   90         execdomains  modules        vmstat
15   256  363  465  613  91         fb           mounts         zoneinfo
16   257  37   482  62   92         filesystems  net
165  27   372  485  63   93         fs           pagetypeinfo
17   272  378  5    633  95         interrupts   partitions
178  273  38   50   64   96         iomem        schedstat
179  277  385  502  65   97         ioports      self
18   279  39   51   656  asound     irq          slabinfo
19   28   4    516  66   buddyinfo  kallsyms     softirqs
2    280  40   52   683  bus        keys         stat

Getting information from sysfs

新的driver 都希望在/sys 產生節點

您可以將 sysfs 在嚴格的定義中看作是內核對象、屬性和關係的表示。內核對象是一個目錄,屬性是一個文件,而關係則是從一個對象到另一個對象的符號連結。從一個更實際的角度來看,由於 Linux 設備驅動程式模型將所有設備和驅動程式都表示為內核對象,您可以透過查看 /sys 來看到內核對系統的視圖,如下所示:

root@raspberrypi:/proc# ls /sys/class
backlight        extcon            iscsi_iface      pps          spi_master
bcm2835-gpiomem  gpio              iscsi_session    ptp          spi_slave
bdi              graphics          iscsi_transport  pwm          thermal
block            hidraw            leds             rc           tty
bluetooth        hwmon             lirc             regulator    udc
bsg              i2c-adapter       mdio_bus         rfkill       uio
devcoredump      ieee80211         mem              rtc          vc
devlink          input             misc             scsi_device  vc-mem
dma              iscsi_connection  mmc_host         scsi_disk    video4linux
dma_heap         iscsi_endpoint    net              scsi_host    vtconsole
drm              iscsi_host        power_supply     sound        watchdog

root@raspberrypi:/proc# ls /dev
autofs         loop4         ram2     tty17  tty41  tty9       vcsu1
block          loop5         ram3     tty18  tty42  ttyAMA0    vcsu2
btrfs-control  loop6         ram4     tty19  tty43  ttyprintk  vcsu3
bus            loop7         ram5     tty2   tty44  ttyS0      vcsu4
cachefiles     loop-control  ram6     tty20  tty45  uhid       vcsu5
cec0           mapper        ram7     tty21  tty46  uinput     vcsu6
char           media0        ram8     tty22  tty47  urandom    vhci
console        media1        ram9     tty23  tty48  v4l        video10
cuse           media2        random   tty24  tty49  vchiq      video11
disk           mem           rfkill   tty25  tty5   vcio       video12
dma_heap       mmcblk0       serial0  tty26  tty50  vc-mem     video13
dri            mmcblk0p1     serial1  tty27  tty51  vcs        video14
fd             mmcblk0p2     shm      tty28  tty52  vcs1       video15
full           mqueue        snd      tty29  tty53  vcs2       video16
fuse           net           stderr   tty3   tty54  vcs3       video18
gpiochip0      null          stdin    tty30  tty55  vcs4       video20
gpiochip1      ppp           stdout   tty31  tty56  vcs5       video21
gpiomem        ptmx          tty      tty32  tty57  vcs6       video22
hwrng          pts           tty0     tty33  tty58  vcsa       video23
initctl        ram0          tty1     tty34  tty59  vcsa1      video31
input          ram1          tty10    tty35  tty6   vcsa2      watchdog
kmsg           ram10         tty11    tty36  tty60  vcsa3      watchdog0
log            ram11         tty12    tty37  tty61  vcsa4      zero
loop0          ram12         tty13    tty38  tty62  vcsa5
loop1          ram13         tty14    tty39  tty63  vcsa6
loop2          ram14         tty15    tty4   tty7   vcsm-cma
loop3          ram15         tty16    tty40  tty8   vcsu

root@raspberrypi:/proc# cd ~
root@raspberrypi:~# ls /sys/class/tty/ttyAMA0
close_delay     dev     iomem_base       line       type
closing_wait    device  iomem_reg_shift  port       uartclk
console         flags   io_type          power      uevent
custom_divisor  hci0    irq              subsystem  xmit_fifo_size
root@raspberrypi:~# cat /sys/class/tty/ttyAMA0/dev
204:64

都希望在class 下產生,SW RD 想要抽象化,希望使用者,可以直接透過class 存取到device就好,不用直接去改到driver code。

在 make menuconfig 放入自己寫的Driver

注意環境變數

這邊以raspberry pi3 為例

$ sudo apt install bc bison crossbuild-essential-armhf flex git libc6-dev libncurses5-dev libssl-dev

$ export ARCH=arm

$ export KERNEL=kernel7

$ export CROSS_COMPILE=arm-linux-gnueabihf-

$ git clone --depth=1 https://github.com/raspberrypi/linux

$ cd linux

$ make bcm2709_defconfig

blog圖片的連結

  1. 在linux/drivers/char 下放入driver source code ,假設是hello 如下圖所示:

blog圖片的連結

//main.c
#include <linux/module.h>
#include <linux/init.h>
MODULE_LICENSE("Dual BSD/GPL");
extern void sub(void);
static int hello_init(void)
{
	printk(KERN_ALERT "driver loaded\n");

	sub();
		
	return 0;
}
static void hello_exit(void)
{
	printk(KERN_ALERT "driver unloaded\n");
}
module_init(hello_init);
module_exit(hello_exit);
//sub.c
#include <linux/module.h>
#include <linux/init.h>

void sub(void)
{
	printk("%s: sub() called\n", __func__);
}

Makefile :

obj-$(CONFIG_HELLO)         += hello.o

hello-objs			:= main.o sub.o
  1. 在 hello 上層,也就是char 這層更改 Kconfig 和 Makefile

blog圖片的連結

blog圖片的連結

  1. Make menuconfig 可以 M module選取,然後make

blog圖片的連結

blog圖片的連結

獨立編譯.ko的方式,以 MA35D1 為例

# export PATH=$PATH:/home/user/buildroot/MA35D1_Buildroot/output/host/bin

PWD := $(shell pwd)

KDIR = /home/user/buildroot/MA35D1_Buildroot/output/build/linux-custom


obj-m += mymodule.o

mymodule-objs := main.o sub.o

ARCH ?= arm64

CROSS_COMPILE ?= aarch64-linux-

all drivers:
	make -C $(KDIR) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) modules
clean:
	make -C $(KDIR) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) clean

一個ioctl的範例

下載看…..

blog圖片的連結

blog圖片的連結

blog圖片的連結

comments powered by Disqus