前言
?? 現在來聊點原理性的東西——linux設備管理模型
?? 嵌入式驅動學習專欄將詳細記錄博主學習驅動的詳細過程,未來預計四個月將高強度更新本專欄,喜歡的可以關注本博主并訂閱本專欄,一起討論一起學習。現在關注就是老粉啦!
行文目錄
- 前言
- 1. LDM數據結構
- 1.1 總線、設備、驅動關系
- 1.1.1 類
- 1.1.2 設備
- 1.1.3 驅動
- 2. kobject sysfs
- 2.1 kobjects
- 2.2 ktype
- 2.3 kset
- 參考資料
1. LDM數據結構
?? linux
設備模型LDM
的上層依賴于總線、設備驅動程序、設備和類。
1.1 總線、設備、驅動關系
?? 在LINUX驅動的世界里,所有的設備和驅動都是掛在總線上的,也就是總線來管理設備和驅動的,總線知道掛在它上邊的所有驅動和設備的情況,由總線完成驅動和設備的匹配和探測。
?? 總線上掛著驅動和設備,一個驅動可以管理多個設備,一個設備保存一個對應驅動的信息,一般在初始化的時候,總線先初始化,然后設備先注冊,最后驅動去找設備,完成他們之間的銜接。
?? 系統已經給我們準備好了我們所學要的總線。對于我們來說,就是去學好怎么在系統中添加設備以及相關的驅動就行了。
1.1.1 類
?? 相關結構體:struct class
和 struct class_device
?? 類發明來就是來管理設備的,是對設備的高級抽象,本質也是一個結構體,但是按照類的思想來組織成員的。運用class,可以讓用戶空間的程序根據自己要處理的事情來調用設備,而不是根據設備被接入到系統的方式或設備的工作原來調用。
?? 一個struct class
結構體類型變量對應一個類,內核提供了class_create()
函數,可以用它來創建一個類,這個類存放于 sysfs 下面, 一旦創建了類,再調用 device_create() 函數在 /dev 目錄下創建相應的設備節點。
1.1.2 設備
?? 驅動中常寫的struct device
是硬件設備在內核驅動框架中的抽象
?? 使用device_register
函數向內核驅動框架注冊一個設備,也可使用device_create
來創建,device_create
函數是對device_register
的封裝。
?? 通常device
不會單獨使用,而是被包含在一個具體的設備結構體中
?? 使用案例如下所示:
struct gpioled_dev {dev_t devid;struct cdev cdev;struct class *class; // 定義一個classstruct device *device; // 定義一個deviceint major;int minor;struct device_node *nd;int led_gpio;
};static int __init led_init(void)
{......newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME); // 先創建classif (IS_ERR(newchrled.class)) {return PTR_ERR(newchrled.class);}newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME); // 再用class創建deviceif (IS_ERR(newchrled.device)) {return PTR_ERR(newchrled.device);}......
}static void __exit led_exit(void)
{......device_destroy(newchrled.class, newchrled.devid); // 先釋放deviceclass_destroy(newchrled.class); // 再釋放class
}
1.1.3 驅動
?? struct device_driver
是驅動程序在內核驅動框架中的抽象
?? 關鍵元素1:name
,驅動程序的名字,很重要,經常被用來作為驅動和設備的匹配依據
?? 關鍵元素2:probe
,驅動程序的探測函數,用來檢測一個設備是否可以被該驅動所管理
?? 使用方式:
/** @description: 驅動程序的探測函數,檢測設備是否被該驅動所管理,當驅動與設備匹配后此函數會執行* @param-dev : platform設備* @return : 0,成功;其他負值,失敗*/
static int led_probe(struct platform_device *dev)
{int i = 0;int ressize[5];u32 val = 0;struct resource *ledsource[5];printk("led driver and device has matched!\r\n");for (i = 0; i < 5; i++) {ledsource[i] = platform_get_resource(dev, IORESOURCE_MEM, i);if (!ledsource[i]) {dev_err(&dev->dev, "No MEM resource for always on\n");return -ENXIO;}ressize[i] = resource_size(ledsource[i]);}IMX6U_CCM_CCGR1 = ioremap(ledsource[0]->start, ressize[0]);SW_MUX_GPIO1_IO3 = ioremap(ledsource[1]->start, ressize[1]);SW_PAD_GPIO1_IO3 = ioremap(ledsource[2]->start, ressize[2]);GPIO1_DR = ioremap(ledsource[3]->start, ressize[3]);GPIO1_GDIR = ioremap(ledsource[4]->start, ressize[4]);val = readl(IMX6U_CCM_CCGR1);val &= ~(3 << 26);val |= (3 << 26);writel(val, IMX6U_CCM_CCGR1);writel(5, SW_MUX_GPIO1_IO3);writel(0x10b0, SW_PAD_GPIO1_IO3);val = readl(GPIO1_GDIR);val &= ~(1 << 3);val |= (1 << 3);writel(val, GPIO1_GDIR);val = readl(GPIO1_DR);val |= (1 << 3);writel(val, GPIO1_DR);if (leddev.major) {leddev.devid = MKDEV(leddev.major, 0);register_chrdev_region(leddev.devid, LEDDEV_CNT, LEDDEV_NAME);} else {alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME);leddev.major = MAJOR(leddev.devid);leddev.minor = MINOR(leddev.devid);}leddev.cdev.owner = THIS_MODULE;cdev_init(&leddev.cdev, &led_fops);cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);if (IS_ERR(leddev.class)){return PTR_ERR(leddev.class);}leddev.device = device_create(leddev.class, NULL, leddev.devid, NULL, LEDDEV_NAME);if (IS_ERR(leddev.device)) {return PTR_ERR(leddev.device);}return 0;
}/** @description: remove函數,移除platform驅動的時候此函數會執行* @param-dev : platfrom 設備* @return : 0,成功;其他負值,失敗*/
static int led_remove(struct platform_device *dev)
{iounmap(IMX6U_CCM_CCGR1);iounmap(SW_MUX_GPIO1_IO3);iounmap(SW_PAD_GPIO1_IO3);iounmap(GPIO1_DR);iounmap(GPIO1_GDIR);cdev_del(&leddev.cdev);unregister_chrdev_region(leddev.devid, LEDDEV_CNT);device_destroy(leddev.class, leddev.devid);class_destroy(leddev.class);return 0;
}static struct platform_driver led_driver = {.driver = {.name = "imx6ul-led2",},.probe = led_probe,.remove = led_remove
};
2. kobject sysfs
2.1 kobjects
?? 設備模型的核心部分是kobjects
,類似于java中object對象類,提供了諸如計數、名稱、父指針等字段,可以創建對象的層次結構。其定義如下:
struct kobject {char * k_name; // 指向kobject名稱,如果名稱長度小于KOBJ_NAME_LEN,則存入name數組中,如果超過,則動態分配一個緩沖區存放,KOBJ_NAME_LEN是20個字節char name[KOBJ_NAME_LEN];struct kref kref; // 實現kobject的引用計數struct list_head entry;struct kobject * parent; // 父對象,在內核中構造一個對象層次結構,并可以將多個對象間的關系表現出來struct kset * kset;struct kobj_type * ktype;struct dentry * dentry; // 指向dentry結構體,在sysfs中該結構體就表示這個kobject
};
?? kobject通常是嵌入到其他結構體中的,其單獨意義其實并不大。當kobject被嵌入到其他結構體中時,該結構體便擁有了kobject提供的標準功能。
2.2 ktype
?? ktype是為了描述一族kobject所具有的普遍特性。因此,不在需要每個kobject都分別定義自己的特性,而是將這些特性在ktype結構體中一次定義,然后所有“同類”的kobject都能共享一樣的特性。
struct kobj_type {void (*release)(struct kobject *);struct sysfs_ops * sysfs_ops;struct attribute ** default_attrs;
};
?? release指針指向在kobject引用計數減為零時要被調用的析構函數。該函數負責釋放所有的kobject使用的內存和其他相關清理工作。
?? sysfs_ops變量指向sysfs_ops結構體。該結構體表述了sysfs文件讀寫是的特性。
?? default_attrs指向一個attribute結構體數組。這些結構體定義了該kobject相關的默認屬性。屬性描述了給定對象的特征,如果該kobject被導出到sysfs中,那么這些屬性都將相應地作為文件而導出。數組中的最后一項必須為NULL。
2.3 kset
?? kset是kobject對象的集合體。把它看成是一個容器,可將所有相關的kobject對象,比如“全部的塊設備”置于一個位置。kset把kobject集中到一個集合中。
struct kset {struct subsystem * subsys;struct kobj_type * ktype;struct list_head list;struct kobject kobj;struct kset_hotplug_ops * hotplug_ops;
};
?? 其中ktype
指向kset
集合中kobject
對象的類型。
?? list
連接該集合中所有的kobject
對象。
?? kobj
指向的kobject
對象代表了該集合的基類。
?? hotplug_ops
指向一個用于處理集合中kobject
對象的熱插拔操作的結構體。
?? subsys
指針指向該結構體相關的struct subsystem
結構體。
參考資料
[1] linux設備模型
[2] Linux設備模型
[3] Linux設備驅動模型
[4] device_create()、device_register()、deivce_add()區別
[5] Linux內核設計與實現—kobject sysfs