〖想觀看更多課程筆記,至[課程筆記]課程筆記系列總覽 可以看到目前已發布的所有文章!〗
Course 5 - 新字元設備驅動實驗 新字元設備驅動原理 在之前的課程中了解到了怎麼註冊一個chrdev,但之前使用的api是舊版的存在著一些問題,例如register_chrdev()
這個function
會佔用主設備號下的所有次設備號 ,浪費了很多次設備號
必須手動指定主設備號,也就是說你得先手動查詢哪個主設備號還沒被佔用,然後再去指定
所以這節課程會使用新的api來撰寫字元驅動,新版的api有兩種方式:
alloc_chrdev_region()
: 向linux kernel申請設備號
給定次設備號和名字,告訴linux要申請幾個設備號,讓linux直接幫你尋找主設備號
register_chrdev_region()
: 給定主設備號去向linux申請
給定次設備號為0的devid,讓linux自己去找該主設備下可使用的次設備號
0 1 2 3 int alloc_chrdev_region (dev_t *dev, unsigned baseminor, unsigned count, const char *name) ;int register_chrdev_region (dev_t from, unsigned count, const char *name) ;
不論是哪種申請方式,釋放時都是call unregister_chrdev_region()
0 void unregister_chrdev_region (dev_t from, unsigned count) ;
一般來說在設計時兩種情況都會考慮到,一個範例code如下:
0 1 2 3 4 5 6 7 8 9 10 int major, minor;dev_t devid;if (major) { devid = MKDEV(major, 0 ); register_chrdev_region(devid, 1 , "test" ); } else { alloc_chrdev_region(&devid, 0 , 1 , "test" ); major = MAJOR(devid); minor = MINOR(devid); }
新字元設備註冊方式 介紹一個新的char structure: cdev
0 1 2 3 4 5 6 7 8 struct cdev { struct kobject kobj ; struct module *owner ; const struct file_operations *ops ; struct list_head list ; dev_t dev; unsigned int count; } __randomize_layout;
使用cdev表示char device,然後使用cdev_init()
初始化結構體,然後透過cdev_add()
進行添加到linux kernel中。最後,註銷時使用cdev_del()
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 void cdev_init (struct cdev *cdev, const struct file_operations *fops) { memset (cdev, 0 , sizeof *cdev); INIT_LIST_HEAD(&cdev->list ); kobject_init(&cdev->kobj, &ktype_cdev_default); cdev->ops = fops; } int cdev_add (struct cdev *p, dev_t dev, unsigned count) { int error; p->dev = dev; p->count = count; error = kobj_map(cdev_map, dev, count, NULL , exact_match, exact_lock, p); if (error) return error; kobject_get(p->kobj.parent); return 0 ; } void cdev_del (struct cdev *p) { cdev_unmap(p->dev, p->count); kobject_put(&p->kobj); }
如果回頭去trace register_chrdev()
的code,其實他背後的本質就是上述這些flow,既然這樣為何不用register_chrdev()
就好?
還記得一開始提到register_chrdev()
的缺點嗎?他會佔用主設備號下的所有次設備號
自動創建設備節點 到這裡還有另一個需要探討的議題:
driver儘管寫完了,但目前為止每次都仍要透過mknod
來創建設備節點,例如0 mknod /dev/newchrled c 249 0
接下來將介紹如何自動創建設備節點
udev udev是Linux kernel 2.6系列引入的設備管理器,主要功能是管理/dev目錄下的設備節點,在熱插拔機制下,添加/刪除硬體時需要負責處理/dev目錄以及所有user space的行為。
傳統的建立設備節點需要手動添加,透過使用mknod
方法,但這樣並不易管理,比方說可能有設備根本沒掛載,但他仍然再/dev下存在著設備節點。
採用udev的方法的話,只有備kernel檢測到的設備才會去添加設備節點。因為這些設備節點在每次系統啟動時被創建,他們會備存在ramfs(一個內存中的文件系統,不占用任何空間),設備節點不會占用大量的磁碟空間,因此使用的memory可以忽略。
mdev 在使用busybox建構rootfs的時候,busybox會創建一個udev的簡化版本-mdev的簡化版本 - mdev,在嵌入式系統一般都是用mdev來管理設備節點的自動插入和刪除。
在/etc/init.d/rcS中可以看到如下的設置
echo /sbin/mdev > /proc/sys/kernel/hotplug
上述命令設置熱插拔事件由mdev來管理,所以接下來來看一下如何用mdev來實現設備節點的新增和刪除。
自動創建設備節點是在驅動的entry function完成的,一般會在cdev_add
後面加入相關代碼。
首先需要創建一個class ,class被定義在include/linux/device.h內
創建/註銷class struct class { const char *name; struct module *owner ; const struct attribute_group **class_groups ; const struct attribute_group **dev_groups ; struct kobject *dev_kobj ; int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env); char *(*devnode)(struct device *dev, umode_t *mode); void (*class_release)(struct class *class); void (*dev_release)(struct device *dev); int (*shutdown_pre)(struct device *dev); const struct kobj_ns_type_operations *ns_type ; const void *(*namespace )(struct device *dev); void (*get_ownership)(struct device *dev, kuid_t *uid, kgid_t *gid); const struct dev_pm_ops *pm ; struct subsys_private *p ; };
使用時需要#include <linux/device.h>
透過class_create
來自動創建class
0 1 2 3 4 5 6 7 8 #define class_create(owner, name) \ ({ \ static struct lock_class_key __key; \ __class_create(owner, name, &__key); \ })
第一個參數是owner, 通常都帶THIS_MODULE
第二個參數帶入driver name
而註銷class的時候則是透過class_destroy
創建/註銷設備 有了創建/註銷class後,需要在該class下創建一個設備,使用device_create
來創立
0 1 2 3 struct device *device_create (struct class *cls, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...) ;
device_create
是一個不定參數的function
class代表設備要建立在哪個class下面
parent代表父設備, 一般為NULL
devt是設備號
drvdata是設備可能會使用到的一些數據, 一般為NULL
fmt是設備名稱, 如果fmt=xxx的話就會生成/dev/xxx文件
同樣的,卸載驅動的時候也要註銷設備,透過device_destroy
來註銷
文件私有數據和goto對錯誤的處理 driver常常會用到一些私有的數據,好的設計方式是透過structure把他們都包起來
在struct file
中其實有個private_data的變數可以用來將需要的data struct存起來,如此就不用在code中另外宣告需要的變數
通常會在open()
內設置filp->private_data,然後在其他function來存取私有數據
release()
的時候會做對應的釋放
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 struct file { union { struct llist_node fu_llist ; struct rcu_head fu_rcuhead ; } f_u; struct path f_path ; struct inode *f_inode ; const struct file_operations *f_op ; spinlock_t f_lock; enum rw_hint f_write_hint; atomic_long_t f_count; unsigned int f_flags; fmode_t f_mode; struct mutex f_pos_lock ; loff_t f_pos; struct fown_struct f_owner ; const struct cred *f_cred ; struct file_ra_state f_ra ; u64 f_version; #ifdef CONFIG_SECURITY void *f_security; #endif void *private_data; #ifdef CONFIG_EPOLL struct list_head f_ep_links ; struct list_head f_tfile_llink ; #endif struct address_space *f_mapping ; errseq_t f_wb_err; } __randomize_layout __attribute__((aligned(4 )));
最後來講講linux driver在init過程中如果發生錯誤時,我們需要將我們init的東西都釋放掉,這時我們常常會透過goto
來做流程的控制,透過好的goto流程設計可以讓我們的driver code有更好的可讀性以及程式架構。
了解了上述的所有內容後,最後來重寫之前寫過的led driver code
下方的code還不包含init錯誤時goto的處理
driver 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/slab.h> #include <linux/uaccess.h> #include <linux/io.h> #include <linux/cdev.h> #include <linux/device.h> #define LED_MAJOR 200 #define LED_NAME "led" #define CCM_CCGR1_BASE (0x020C406C) #define SW_MUX_GPIO1_IO03_BASE (0x020E0068) #define SW_PAD_GPIO1_IO03_BASE (0x020E02F4) #define GPIO1_DR_BASE (0x0209C000) #define GPIO1_GDIR_BASE (0x0209C004) static void __iomem *IMX6U_CCM_CCGR1;static void __iomem *SW_MUX_GPIO1_IO03;static void __iomem *SW_PAD_GPIO1_IO03;static void __iomem *GPIO1_DR;static void __iomem *GPIO1_GDIR;#define LEDOFF 0 #define LEDON 1 #define NEWCHRLED_NAME "newchrled" #define NEWCHRLED_CNT 1 struct newchrled_dev { struct cdev cdev ; dev_t devid; struct class *class ; struct device *device ; int major; int minor; }; void led_switch (u8 sta) { u32 val = 0 ; if (databuf[0 ] == LEDOFF) { val = readl(GPIO1_DR); val |= ~(1 << 3 ); writel(val, GPIO1_DR); } else { val = readl(GPIO1_DR); val &= ~(1 << 3 ); writel(val, GPIO1_DR); } } static int newchrled_open (struct inode *inode, struct file *filp) { filp->private_data = &newchrled; return 0 ; } static int newchrled_release (struct inode *inode, struct file *filp) { struct newchrled_dev *dev = (struct newchrled_dev )filp ->private_data ; return 0 ; } static ssize_t newchrled_write (struct file *filp, const char __user *buf, size_t count, loff_t *ppos) { int ret; int val = 0 ; unsigned char databuf[1 ]; ret = copy_from_user(databuf, buf, count); if (ret < 0 ){ printk("kernel write fail\n" ); return -EFAULT; } led_switch(databuf[0 ]); return 0 ; } struct newchrled_dev newchrled ;static const struct file_operations newchrled_fops = { .owner = THIS_MODULE, .write = newchrled_write, .open = newchrled_open, .release = newchrled_release, }; static void __init newchrled_init (void ) { int ret = 0 ; int val = 0 ; IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4 ); SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4 ); SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4 ); GPIO1_DR = ioremap(GPIO1_DR_BASE, 4 ); GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4 ); val = readl(IMX6U_CCM_CCGR1); val &= ~(3 << 26 ); val |= 3 << 26 ; writel(val, IMX6U_CCM_CCGR1); writel(0x5 , SW_MUX_GPIO1_IO03); writel(0x10B0 , SW_PAD_GPIO1_IO03); val = readl(GPIO1_GDIR); val |= 1 << 3 ; writel(val, GPIO1_GDIR); val = readl(GPIO1_DR); val &= ~(1 << 3 ); writel(val, GPIO1_DR); if (newchrled.major) { newchrled.devid = MKDEV(newchrled.major, 0 ); register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME); } else { ret = alloc_chrdev_region(&newchrled.devid, 0 , NEWCHRLED_CNT, NEWCHRLED_NAME); newchrled.major = MAJOR(newchrled.devid); newchrled.minor = MINOR(newchrled.devid); } if (ret < 0 ) { printk("newchrled chr_dev region err\n" ); return -1 ; } printk("major:%d, minor:%d\n" , newchrled.majorm newchrled.minor); newchrled.cdev.owner = THIS_MODULE; cdev_init(&newchrled.cdev, &newchrled_fops); ret = cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT); newchrled.class = class_create (THIS_MODULE , NEWCHRLED_NAME ); if (IS_ERR(newchrled.class)) reutrn PTR_ERR (newchrled.class) ; newchrled.device = device_create(newchrled.class, NULL , newchrled.devid, NULL , NEWCHRLED_NAME); if (IS_ERR(newchrled.device)) return PTR_ERR(newchrled.device); return 0 ; } static void __exit newchrled_exit (void ) { int val = 0 ; val = readl(GPIO1_DR); val |= ~(1 << 3 ); writel(val, GPIO1_DR); iounmap(IMX6U_CCM_CCGR1); iounmap(SW_MUX_GPIO1_IO03); iounmap(SW_PAD_GPIO1_IO03); iounmap(GPIO1_DR); iounmap(GPIO1_GDIR); cdev_del(&newchrled.cdev); printk("chrdev unregister\n" ); unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT); device_destroy(newchrled.class, newchrled.devid); class_destroy(newchrled.class); reutrn 0 ; } module_init(newchrled_init); module_exit(newchrled_exit); MODULE_LICENSE("GPL" ); MODULE_AUTHOR("john" );
app app的部分沒有什麼變動,所以就不重寫了,要看的可以看前幾次的課程筆記