要編譯要先安裝 build-essential 與 libelf-dev 套件, build-essential 包含了編譯 C / C++ 所需的套件,lebelf-dev 則包含編譯 driver (kernel module) 所需的程式。
$ sudo apt-get update $ sudo apt-get install build-essential $ sudo apt-get install libelf-dev
// debug_card.h #ifndef _DEBUG_CARD_H_ #define MSG(format, arg...) printk(KERN_INFO "DEBUG CARD: " format "\n", ## arg) #include <linux/ioctl.h> #define DEV_MAJOR 121 #define DEV_NAME "debug" #define DEV_IOCTLID 0xD0 #define IOCTL_WRITE _IOW(DEV_IOCTLID, 10, int) #define IOCTL_RESET _IOW(DEV_IOCTLID, 0, int) #endif
//debug_card.c #include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/slab.h> //#include <asm/uaccess.h> #include <linux/uaccess.h> #include "debug_card.h" unsigned long IOPort = 0x80; #define DRIVER_NAME "debug_card" static unsigned int chrdev_sys_major = 0; static unsigned int num_of_dev = 1; static struct cdev debug_card_cdev; static struct class *chrdev_sys_class = NULL; void write_debug_card(unsigned int num) { MSG("write 0x%02X (%d) to debug card", (unsigned char)num, num); outb((unsigned char)num, IOPort); } static long debug_card_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { switch (cmd) { case IOCTL_RESET: write_debug_card(0x00); break; default: return -1; } return 0; } ssize_t debug_card_write(struct file *filp, const char *buff, size_t count, loff_t *offp) { char *str; unsigned int num; int i; if (count == 0) return 0; filp->private_data = (char *)kmalloc(64, GFP_KERNEL); str = filp->private_data; if (copy_from_user(str, buff, count)) return -EFAULT; /* atoi() */ num = str[0] - '0'; for (i = 1; i < count; i++) { num = num * 10 + (str[i] - '0'); } write_debug_card(num); return 1; }; /**************************************************/ int debug_card_release(struct inode *inode, struct file *filp) { module_put(THIS_MODULE); kfree(filp->private_data); return 0; }; int debug_card_open(struct inode *inode, struct file *filp) { try_module_get(THIS_MODULE); return 0; }; struct file_operations card_fops = { .owner = THIS_MODULE, .open = debug_card_open, .write = debug_card_write, .release = debug_card_release, .unlocked_ioctl = debug_card_ioctl, }; static char *devnode(struct device *dev, umode_t *mode) { if (!mode) return NULL; if (dev->devt == MKDEV(chrdev_sys_major, 0) || dev->devt == MKDEV(chrdev_sys_major, 2)) *mode = 0666; return NULL; } static int card_init_module(void) { dev_t dev = MKDEV(chrdev_sys_major, 0); int alloc_ret = 0; int cdev_ret = 0; alloc_ret = alloc_chrdev_region(&dev, 0, num_of_dev, DRIVER_NAME); if (alloc_ret) goto error; chrdev_sys_major = MAJOR(dev); cdev_init(&debug_card_cdev, &card_fops); cdev_ret = cdev_add(&debug_card_cdev, dev, num_of_dev); if (cdev_ret) goto error; chrdev_sys_class = class_create(THIS_MODULE ,"debug_card_80port"); //Name to be displayed inside /sys/class/ when driver is loaded. if (IS_ERR(chrdev_sys_class)) goto error; chrdev_sys_class->devnode = devnode; //Set device file (/dev/debug_card_80port) permission to 0666 on creation device_create(chrdev_sys_class, NULL, dev, NULL, "debug_card_80port"); //device file to be created under /dev. printk(KERN_ALERT "%s driver(major: %d) installed.\n", DRIVER_NAME, chrdev_sys_major); return 0; error: if (cdev_ret == 0) cdev_del(&debug_card_cdev); if (alloc_ret == 0) unregister_chrdev_region(dev, num_of_dev); return -1; } static void card_exit_module(void) { dev_t dev = MKDEV(chrdev_sys_major, 0); device_destroy(chrdev_sys_class, dev); class_destroy(chrdev_sys_class); cdev_del(&debug_card_cdev); unregister_chrdev_region(dev, num_of_dev); printk(KERN_ALERT "%s driver removed.\n", DRIVER_NAME); } module_init(card_init_module); module_exit(card_exit_module); MODULE_LICENSE("GPL"); MODULE_AUTHOR("RayKuo's blog"); MODULE_INFO(intree, "Y");//fix err: "giodrv: loading out-of-tree module taints kernel."
# Makefile KVERSION := $(shell uname -r) obj-m := debug_card.o all: $(MAKE) -C /lib/modules/$(KVERSION)/build M=${PWD} modules clean: $(MAKE) -C /lib/modules/$(KVERSION)/build M=${PWD} clean
測試程式 sample code:
// test.c #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #include <sys/ioctl.h> #include <sys/types.h> #include <sys/stat.h> #include "../drv/debug_card.h" int main(int argc, char *argv[]) { int devfd; unsigned int num = 0; if (argc == 1) argv[1] = "0"; devfd = open("/dev/debug_card_80port", O_RDWR); if (devfd == -1) { printf("Can't open /dev/debug_card_80port\n"); return -1; } printf("Resetting debug card...\n"); ioctl(devfd, IOCTL_RESET, NULL); printf("Done. Wait 1 second...\n"); sleep(1); printf("Writing %s...\n", argv[1]); write(devfd, argv[1], strlen(argv[1])); printf("Done.\n"); close(devfd); return 0; }
# Makefile all: gcc test.c -o test clean: rm test
編譯與測試:
切到 debug_card driver 目錄,使用 make 指令 compiler:
cd debug_card/drv make
沒有錯誤的話可以得到 debug_card.ko (driver)。
使用 insmod 安裝 driver :
$ sudo insmod debug_card.ko
看到 kernel 分配給 driver 的 major number。
# 查看 kernel 分配給 drive 的 major number $ cat /proc/devices|grep debug_card 243 debug_card # list 已載入的 driver $ lsmod|grep debug_card debug_card 16384 0 # driver 載入後建立的 class $ ls -dl /sys/class/debug* drwxr-xr-x 2 root root 0 10月 1 16:09 /sys/class/debug_card_80port # driver 載入後建立的 device node (不需使用 mknod 來手動建立),用來與 user mode 程式溝通 $ ls -d /dev/debug* crw-rw-rw- 1 root root 243, 0 10月 1 16:09 /dev/debug_card_80port
到此為止,driver 與 device 已完成建置,test 測試程式將通過讀寫 device /dev/debug_card_80port (檔案)與 driver 溝通。
再來切到 test 測試程式目錄,使用 make 來 compiler 測試程式。
cd ../test make
執行 test 即可看到 debug card (80 port) 的變化。
# 往 80 port 丟 12h ./test 12
移除已安裝的 driver:
cd ../drv sudo rmmod debug_card.ko
Download source code : debug_card_src