MINI2440

1 EXAMPLE 3

程式檔 led.c 使用驅動程式 leds,操控開發板的 LED。
先嘗試打開 leds0,失敗時再嘗試打開 leds,都失敗的話就離開程式。
裝置開啟成功後,使用 ioctl 操作驅動程式,控制 led。
完成動作後,關閉驅動程式。

驅動程式路徑是 /dev/leds0 或 /dev/leds,因為驅動程式掛載到作業系統後會以檔案的形式存在,其實就是目錄結構中的一個 inode。 所有檔案都會以 inode 資料結構存在於記憶體的目錄結構中,再由 inode 指向驅動程式。

s3c2440

1.1 應用程式

LED 應用程式是由 SHELL 呼叫,以一個行程的形式執行。
1.執行過程中,打開 leds 裝置驅動程式。
2.呼叫 ioctl 操控 leds 裝置,控制 led 燈號。

01 #include <stdio.h>
01 #include <stdlib.h>
01 #include <unistd.h>
01 #include <sys/ioctl.h>
01 
01 int main(int argc, char **argv)
01 {
01     int on;
01     int led_no;
01     int fd;
01     if (argc != 3 || sscanf(argv[1], "%d", &led_no) != 1 || sscanf(argv[2],"%d", &on) != 1 ||
01         on < 0 || on > 1 || led_no < 0 || led_no > 3) {
01         fprintf(stderr, "Usage: leds led_no 0|1\n");
01         exit(1);
01     }
01     fd = open("/dev/leds0", 0);
01     if (fd < 0) {
01         fd = open("/dev/leds", 0);
01     }
01     if (fd < 0) {
01         perror("open device leds");
01         exit(1);
01     }
01     ioctl(fd, on, led_no);
01     close(fd);
01     return 0;
01 }


1.2 驅動程式

LED 裝置驅動程式 mini2440_leds.c 是直接編入核心,不是以外部驅動程式的方式掛載。 編譯核心時,會直接產生 mini2440_leds.o,納入核心驅動程式映像,而不是產生 mini2440_leds.ko,從外面掛載。

引入標頭檔群。
linux/miscdevice.h 是在 include/linux/miscdevice.h,定義一般設備裝置相關的常數與結構。
mach/regs-gpio.h 是在 /arch/arm/mach-s3c2410/include/mach,定義 S3C2410_GPB(x)、S3C2410_GPIO_OUTPUT。

01 #include <linux/miscdevice.h>
01 #include <linux/delay.h>
01 #include <asm/irq.h>
01 #include <mach/regs-gpio.h>
01 #include <mach/hardware.h>
01 #include <linux/kernel.h>
01 #include <linux/module.h>
01 #include <linux/init.h>
01 #include <linux/mm.h>
01 #include <linux/fs.h>
01 #include <linux/types.h>
01 #include <linux/delay.h>
01 #include <linux/moduleparam.h>
01 #include <linux/slab.h>
01 #include <linux/errno.h>
01 #include <linux/ioctl.h>
01 #include <linux/cdev.h>
01 #include <linux/string.h>
01 #include <linux/list.h>
01 #include <linux/pci.h>
01 #include <linux/gpio.h>
01 #include <asm/uaccess.h>
01 #include <asm/atomic.h>
01 #include <asm/unistd.h>

定義裝置名稱字串 leds,這個名稱字串會加入到 misc 結構的 name 變數中,成為驅動程式的名稱。這個名稱在核心驅動程式中必須是唯一。

01 #define DEVICE_NAME "leds"

定義腳位表,陣列中每個成員都描述一根腳位的位置,分別指向 s3c2410 的 GPB 的第 5、6、7、8 腳位。

01 static unsigned long led_table [] = {
01     S3C2410_GPB(5),
01     S3C2410_GPB(6),
01     S3C2410_GPB(7),
01     S3C2410_GPB(8),
01 };

定義腳位組態表,陣列中每個成員都描述一根腳位的組態,均為輸出組態。

01 static unsigned int led_cfg_table [] = {
01     S3C2410_GPIO_OUTPUT,
01     S3C2410_GPIO_OUTPUT,
01     S3C2410_GPIO_OUTPUT,
01     S3C2410_GPIO_OUTPUT,
01 };

定義裝置 IO 控制函式,此函式會設定到裝置驅動程式的操作函式結構 dev_fops 的 .ioctl 變數,成為驅動程式的 ioctl 操作函式。 參數 cmd 是命令參數,0 表示 LED 亮,1 表示 LED 暗。
參數 arg 是命令參數的引數,0 表示第一個 LED,其餘 1、2、3 表示另外三個 LED。
函式 s3c2410_gpio_setpin 是 arch/arm/plat-s3c24xx/gpio.c,是 GPIO 的設定函式,給定腳位位置和值,設定該腳位為邏輯 0 或 1。

01 static int sbc2440_leds_ioctl(
01     struct inode *inode, 
01     struct file *file, 
01     unsigned int cmd, 
01     unsigned long arg)
01 {
01     switch(cmd) {
01     case 0:
01     case 1:
01         if (arg > 4) {
01             return -EINVAL;
01         }
01         s3c2410_gpio_setpin(led_table[arg], !cmd);
01         return 0;
01     default:
01         return -EINVAL;
01     }
01 }

定義裝置(檔案)操作結構,包含裝置擁有者和裝置控制函式。
其中,裝置控制函式就是當應用程式呼叫 ioctl(device,cmd,arg) 時,重新指向的操作函式。

01 static struct file_operations dev_fops = {
01     .owner    =    THIS_MODULE,
01     .ioctl    =    sbc2440_leds_ioctl,
01 };

定義裝置結構,包含次要編號、裝置名稱、裝置操作結構。
次要編號是裝置所歸屬的裝置佇列號碼,裝置管理器有好幾個裝置佇列。
裝置名稱就是字串 leds,在裝置中必需是唯一,不該有兩個裝置的裝置名稱一樣。
裝置操作結構包含裝置操作函式,是裝置驅動程式運作時的函式集合,主要是 ioctl 成員。

01 static struct miscdevice misc = {
01     .minor = MISC_DYNAMIC_MINOR,
01     .name = DEVICE_NAME,
01     .fops = &dev_fops,
01 };

關於一般裝置結構 miscdevice。
裝置結構 miscdevice 定義在 /include/linux/miscdevice.h,是一個雙向串列結構,用來承裝各式各樣的驅動程式。 當呼叫模組註冊函式 misc_register 時,會將裝置驅動程式的裝置結構安裝到核心裝置結構佇列。 核心用此裝置結構佇列管理所有核心裝置驅動程式。

/include/linux/miscdevice.h
01 struct miscdevice  {
01     int minor;
01     const char *name;
01     const struct file_operations *fops;
01     struct list_head list;
01     struct device *parent;
01     struct device *this_device;
01     const char *nodename;
01     mode_t mode;
01 };

模組初始化函式。
當模組載入核心完成後,第一時間執行的函式。
模組初始化函式會註冊驅動程式,顯示驅動程式安裝完成訊息。

函式的功能是設定 LED 相關的 GPIO 的組態和初始值,即設定 4 個 GPIO 都為輸出,並設定輸出值為 0。 之後,註冊驅動程式,並顯示驅動程式初始化完成訊息。

misc_register 是裝置註冊函式,用來安裝使用結構 miscdevice 的裝置驅動程式。 misc_register 的實作是放在 /drivers/char/misc.c。

01 static int __init dev_init(void)
01 {
01     int ret;
01     int i;
01     
01     for (i = 0; i < 4; i++) {
01         s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]);
01         s3c2410_gpio_setpin(led_table[i], 0);
01     }
01     ret = misc_register(&misc);
01     printk (DEVICE_NAME"\tinitialized\n");
01     return ret;
01 }

模組卸載函式。
當模組從核心卸載之前,必需要執行的函式。
模組卸載函式會註銷驅動程式。

misc_deregister 是裝置註銷函式,用來註銷使用結構 miscdevice 的裝置驅動程式,是 misc_register 的反向函式。 misc_deregister 的實作是放在 /drivers/char/misc.c。

01 static void __exit dev_exit(void)
01 {
01     misc_deregister(&misc);
01 }

設定模組初始化函式。
設定模組卸載函式。
設定模組授權等級。
設定模組著作者名稱。
這些資訊會紀錄在模組檔中,以區段與符號的方式紀錄。

01 module_init(dev_init);
01 module_exit(dev_exit);
01 MODULE_LICENSE("GPL");
01 MODULE_AUTHOR("FriendlyARM Inc.");


1.3 相關的 LINUX 函式庫


1.3.1 misc_register

一般裝置註冊函式 misc_register。
步驟是
1.初始化裝置結構的佇列指標。
2.取得裝置管理器的互斥鎖,因為接下來的動作不能被其他行程打岔。
3.根據裝置的次要號碼 minor,取得所屬的裝置佇列。
4.如果次要號碼為 MISC_DYNAMIC_MINOR,計算次要號碼的位置值,在 misc_minors 陣列中尋找空的位置。
5.將對應於 misc_minors 的位置的數值設定為 1,表示有裝置註冊其中。
6.計算設備號碼,由主要設備號碼和次要號碼組成。
7.建立設備,成功時,將設備的佇列結構註冊到設備管理器的佇列。有錯誤時,結束並回返。
函士回返前,必須先解除設備互斥鎖。

01 int misc_register(struct miscdevice * misc)
01 {
01     struct miscdevice *c;
01     dev_t dev;
01     int err = 0;
01 
01     INIT_LIST_HEAD(&misc->list);
01     mutex_lock(&misc_mtx);
01     list_for_each_entry(c, &misc_list, list) {
01         if (c->minor == misc->minor) {
01             mutex_unlock(&misc_mtx);
01             return -EBUSY;
01         }
01     }
01     if (misc->minor == MISC_DYNAMIC_MINOR) {
01         int i = DYNAMIC_MINORS;
01         while (--i >= 0)
01             if ( (misc_minors[i>>3] & (1 << (i&7))) == 0) break;
01         if (i<0) {
01             mutex_unlock(&misc_mtx);
01             return -EBUSY;
01         }
01         misc->minor = i;
01     }
01     if (misc->minor < DYNAMIC_MINORS) misc_minors[misc->minor >> 3] |= 1 << (misc->minor & 7);
01     dev = MKDEV(MISC_MAJOR, misc->minor);
01     misc->this_device = device_create(misc_class, misc->parent, dev,misc, "%s", misc->name);
01     if (IS_ERR(misc->this_device)) {
01         err = PTR_ERR(misc->this_device);
01         goto out;
01     }
01     list_add(&misc->list, &misc_list);
01  out:
01     mutex_unlock(&misc_mtx);
01     return err;
01 }


1.3.2 misc_deregister

一般裝置註銷函式 misc_deregister。
步驟是
1.檢查設備的佇列結構是否為空佇列。空佇列沒有設備可以移除。
2.取得裝置管理器的互斥鎖,因為接下來的動作不能被其他行程打岔。
3.將設備結構的佇列結構從佇列中移除。
4.根據裝置類別,將裝置號碼摧毀。
5.將對應的 misc_minors 陣列的位元位置清除為 0,註銷設備的次要號碼。
6.解除設備管理器的互斥鎖。

01 int misc_deregister(struct miscdevice *misc)
01 {
01     int i = misc->minor;
01 
01     if (list_empty(&misc->list)) return -EINVAL;
01     mutex_lock(&misc_mtx);
01     list_del(&misc->list);
01     device_destroy(misc_class, MKDEV(MISC_MAJOR, misc->minor));
01     if (i < DYNAMIC_MINORS && i>0) {
01         misc_minors[i>>3] &= ~(1 << (misc->minor & 7));
01     }
01     mutex_unlock(&misc_mtx);
01     return 0;
01 }


1.3.3 module_init

關於 module_init。
module_init(dev_init) 最後會演變成區段 rel.initcall6.init 的符號 __initcall_dev_init6 的位址值。
可以在下圖中看到目的檔 mini2440_leds.o 的字串表,其中有區段名稱字串 rel.initcall6.init 和 符號名稱字串 __initcall_dev_init6。
核心模組管理器會解析模組檔的區段與符號表,取得並執行模組初始化函式 dev_init。

mini2440_leds.o PART I

s3c2440

mini2440_leds.o PART II

s3c2440

module_init 定義在 /include/linux/init.h。
module_init 的定義是 __initcall。

01 #define module_init(x) __initcall(x);

__initcall 的定義是 device_initcall。

01 #define __initcall(fn) device_initcall(fn)

device_initcall 的定義是 __define_initcall。

01 #define device_initcall(fn) __define_initcall("6",fn,6)

__define_initcall 的定義。

01 #define __define_initcall(level,fn,id) \
01         static initcall_t __initcall_##fn##id __used \
01         __attribute__((__section__(".initcall" level ".init"))) = fn

module_init(x) 的展開如下。

01 static initcall_t __initcall_##fn##id __used  __attribute__((__section__(".initcall" level ".init"))) = fn

module_init(dev_init) 的展開如下。而 dev_init 應該是符號 __initcall_dev_init6 的位址值。

01 static initcall_t  __initcall_dev_init_6 __used __attribute__((__section__(".initcall6.init"))) = dev_init;


1.3.4 module_exit

關於 module_exit。
module_init(dev_init) 最後會演變成符號 rel.exitcall.exit 的符號 __exit_call_dev_exit 的位址值。
可以在下圖中看到目的檔 mini2440_leds.o 的字串表,其中有區段名稱字串 rel.exitcall.exit 和符號名稱字串 __exit_call_dev_exit。
核心模組管理器會解析模組檔的區段與符號表,取得並執行模組卸載函式 dev_exit。

module_exit 定義在 /include/linux/init.h。
module_exit 的定義是 __exitcall。

01 #define module_exit(x)    __exitcall(x);

__exitcall 的定義。

01 #define __exitcall(fn) static exitcall_t __exitcall_##fn __exit_call = fn

其中,__exit_call 的定義,指定區段名稱是 .exitcall.exit。

01 #define __exit_call    __used __section(.exitcall.exit)

最後,module_exit(dev_exit) 的展開。
其中,dev_exit 編譯後的符號名稱是 __exit_call_dev_exit。

01 static exitcall_t __exitcall_dev_exit __used __section(.exitcall.exit) = dev_exit

mini2440_leds.o PART III

s3c2440

mini2440_leds.o PART IV

s3c2440

1.4 相關資料結構


1.4.1 file_operations

檔案操作函式結構是定義在 /include/linux/fs.h,定義檔案操作所需要的函式。 有的函式是用於操作檔案,有的函式是用於操作設備驅動程式。 設備驅動程式可以視需要,選擇性的實作操作函式。

01 struct file_operations {
01 	struct module *owner;
01 	loff_t (*llseek) (struct file *, loff_t, int);
01 	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
01 	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
01 	ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
01 	ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
01 	int (*readdir) (struct file *, void *, filldir_t);
01 	unsigned int (*poll) (struct file *, struct poll_table_struct *);
01 	int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
01 	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
01 	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
01 	int (*mmap) (struct file *, struct vm_area_struct *);
01 	int (*open) (struct inode *, struct file *);
01 	int (*flush) (struct file *, fl_owner_t id);
01 	int (*release) (struct inode *, struct file *);
01 	int (*fsync) (struct file *, struct dentry *, int datasync);
01 	int (*aio_fsync) (struct kiocb *, int datasync);
01 	int (*fasync) (int, struct file *, int);
01 	int (*lock) (struct file *, int, struct file_lock *);
01 	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
01 	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
01 	int (*check_flags)(int);
01 	int (*flock) (struct file *, int, struct file_lock *);
01 	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
01 	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
01 	int (*setlease)(struct file *, long, struct file_lock **);
01 };