文章目錄
- 1 平臺驅動程序
- 2 平臺設備
- 2.1 資源和平臺數據
- 1 設備配置---廢棄的舊方法
- 資源
- 平臺數據
- 聲明平臺設備
- 2 設備配置---推薦的新方法
- 3 設備、驅動程序和總線匹配
- OF風格
- ACPI
- ID表匹配
- 匹配平臺設備的名字和平臺驅動的名字
- 平臺設備和平臺驅動程序如何匹配
- 4 Platfrom架構驅動程序
有些物理總線已為人熟知:USB、I2S、I2C、UART、SPI、PIC、SATA等。這種總線是名為控制器的硬件設備。由于它們是SoC的一部分,因此無法刪除,不可發現,也稱為平臺設備。
從SoC的角度來看,這些設備(總線)內部通過專用總線連接,而且大部分時間是專用的,專門針對制造商。從內核的角度來看,這些是根設備,未連接到任何設備,也就是未連接到任何總線上。這就是偽平臺總線的用途,偽平臺總線也稱為平臺總線,是內核虛擬總線,該總線實際不存在,用于連接不在內核所知物理總線上的設備和驅動程序,也就是根設備和其相關的驅動程序。在下面的討論中,平臺設備是指依靠偽平臺總線的設備。
處理平臺設備實際上需要兩個步驟:
- 注冊管理設備的平臺驅動程序(具有唯一的名稱)。
- 注冊平臺設備(與驅動程序具有相同的名稱)及其資源,以便內核獲取設備位置。
1 平臺驅動程序
在進一步介紹之前,請注意以下警告,并非所有平臺設備都由平臺驅動程序處理。平臺驅動程序專用于不基于傳統總線的設備。I2C設備或SPI設備是平臺設備,但分別依賴I2C或SPI總線,而不是平臺總線。對于平臺驅動程序一切都需手工完成。平臺驅動程序必須實現probe函數,在插入模塊或設備聲明時,內核調用它。在開發平臺驅動程序時,必須填寫主結構struct platform_driver
,由它來代表平臺驅動程序,并用專門函數把驅動程序注冊到平臺總線上。
struct platform_driver
定義在include/linux/platform_device.h中
struct platform_driver {int (*probe)(struct platform_device *); /* 設備和驅動匹配后調用的函數 */int (*remove)(struct platform_device *); /* 驅動程序不再為設備所需而要刪除時調用的函數 */void (*shutdown)(struct platform_device *); /* 設備被關閉時調用的代碼 */int (*suspend)(struct platform_device *, pm_message_t state); /* 設備被掛起執行的函數 */int (*resume)(struct platform_device *); /* 設備從掛起中恢復所執行的函數 */struct device_driver driver; /* 設備驅動 */const struct platform_device_id *id_table; /* 設備的ID表 */bool prevent_deferred_probe;
};/*** struct device_driver - The basic device driver structure* @name: Name of the device driver.* @bus: The bus which the device of this driver belongs to.* @owner: The module owner.* @mod_name: Used for built-in modules.* @suppress_bind_attrs: Disables bind/unbind via sysfs.* @probe_type: Type of the probe (synchronous or asynchronous) to use.* @of_match_table: The open firmware table.* @acpi_match_table: The ACPI match table.* @probe: Called to query the existence of a specific device,* whether this driver can work with it, and bind the driver* to a specific device.* @remove: Called when the device is removed from the system to* unbind a device from this driver.* @shutdown: Called at shut-down time to quiesce the device.* @suspend: Called to put the device to sleep mode. Usually to a* low power state.* @resume: Called to bring a device from sleep mode.* @groups: Default attributes that get created by the driver core* automatically.* @pm: Power management operations of the device which matched* this driver.* @p: Driver core's private data, no one other than the driver* core can touch this.** The device driver-model tracks all of the drivers known to the system.* The main reason for this tracking is to enable the driver core to match* up drivers with new devices. Once drivers are known objects within the* system, however, a number of other things become possible. Device drivers* can export information and configuration variables that are independent* of any specific device.*/
struct device_driver {const char *name;struct bus_type *bus;struct module *owner;const char *mod_name; /* used for built-in modules */bool suppress_bind_attrs; /* disables bind/unbind via sysfs */enum probe_type probe_type;const struct of_device_id *of_match_table;const struct acpi_device_id *acpi_match_table;int (*probe) (struct device *dev);int (*remove) (struct device *dev);void (*shutdown) (struct device *dev);int (*suspend) (struct device *dev, pm_message_t state);int (*resume) (struct device *dev);const struct attribute_group **groups;const struct dev_pm_ops *pm;struct driver_private *p;
};
在內核中注冊平臺驅動程序很簡單,只需在init函數中調用platform_driver_register()或platform_driver_probe()。這兩個函數之間的區別如下:
- platform_driver_register():注冊驅動程序并將其放入由內核維護的驅動程序列表中,內核遍歷注冊的平臺設備表,每當發現新的匹配時就可以按需調用其probe函數。為防止驅動程序在該列表中插入和注冊,請使用下一個函數。
- platform_driver_probe():調用該函數后,內核立即運行匹配循環,檢查是否有平臺設備名稱匹配,如果匹配則調用驅動程序的probe,這意味著設備存在,否則,驅動程序將被忽略。此方法可防止延遲探測,因為它不會在系統上注冊驅動程序。
platform_driver_probe和 platform_driver_register的一點區別就是,如果設備是熱插拔的,那么就使用 platform_driver_register函數,因為platform_driver_register將驅動注冊到內核上,每當發現新的匹配時就可以按需調用其probe函數;如果設備不是熱插拔的,例如dm368芯片中的USB接口,這種情況下usb控制器是在片內集成的,任何情況下都不會出現插拔情況,這種情況下可以使用函數 platform_driver_probe,platform_driver_probe被調用后,就開始匹配,如果沒有匹配上,驅動程序將被忽略。
2 平臺設備
實際上,應該叫偽平臺設備,完成驅動程序后,必須向內核提供需要該驅動程序的設備。平臺設備在內核中表示為struct platform_device
的實例,定義在文件include/linux/platform_device.h中。如下所示:
struct platform_device {const char *name; /* 用于和platform_driver進行匹配的名字 */int id; /* 設備ID */bool id_auto;struct device dev;u32 num_resources; /* resource的個數*/struct resource *resource; /* 資源數組 */const struct platform_device_id *id_entry;char *driver_override; /* Driver name to force a match *//* MFD cell pointer */struct mfd_cell *mfd_cell;/* arch specific additions */struct pdev_archdata archdata;
};
對于平臺驅動程序,在驅動程序和設備匹配之前,struct platform_device
的name字段要和 struct platform_driver.driver.name
相同,這樣才能匹配上。
使用platform_device_register(struct platform_device *pdev)
函數注冊平臺設備
2.1 資源和平臺數據
在可熱插拔的另一端,內核不知道系統上存在那些設備、它們能夠做什么,或者需要什么才能運行。因為沒有自主協商的過程,所以提供給內核的任何信息都會收到歡迎。有兩種方法可以把相關設備所需的資源和數據通知內核。
1 設備配置—廢棄的舊方法
這種方法用于不支持設備樹的內核版本,使用這種方法,驅動程序可以保持其通用性,使設備注冊到與開發板相關的源文件中。
資源
資源代表設備在硬件方面的所有特征元素,以及設備所需的所有元素,以便設置使其正常運行,平臺設備struct platform_device
由struct source *resource
描述其資源。內核中只有6種類型的資源,全部列在include/linux/ioport.h中,并用標志來描述資源類型:
#define IORESOURCE_IO 0x00000100 /* PCI/ISA I/O ports */
#define IORESOURCE_MEM 0x00000200 /*內存區域 */
#define IORESOURCE_REG 0x00000300 /* Register offsets */
#define IORESOURCE_IRQ 0x00000400 /* IRQ線 */
#define IORESOURCE_DMA 0x00000800 /* DMA通道 */
#define IORESOURCE_BUS 0x00001000 /* 總線 */
資源在內核中表示為struct resource
的實例:
struct resource {resource_size_t start;resource_size_t end;const char *name;unsigned long flags;unsigned long desc;struct resource *parent, *sibling, *child;
};
- start/end:這代表資源的開始結束位置。對于I/O或內存區域,它表示開始結束位置。對于中斷線、總線或DMA通道,開始結束必須具有相同的值。
- flags:這是表示資源類型的掩碼,例如IORESOURCE_BUS
- name:標識或描述資源
一旦提供了資源,就需要在驅動中獲取使用它們。probe功能是獲取它們的好地方。嵌入在struct platform_device
中的struct resource
可以通過platform_get_resource函數進行檢索:
struct resource *platform_get_resource(struct platform_device *dev,unsigned int type,unsigned int num);
第一個參數是平臺設備自身的實例。第二個參數說明需要什么樣的資源。對于內存,它應該是IORESOURCE_MEM。num是一個索引,表示需要那個資源類型,0表示第一個,以此類推。
平臺數據
所有類型不屬于上一部分所列舉資源的其他數據都屬于這里(如GPIO)。無論它們是什么類型,struct platform_device
都包含struct device
字段,該字段又包含struct platform_data
字段。通常,應該將數據嵌入結構中,將其傳輸到platform_data字段中。
聲明平臺設備
用設備的資源和數據注冊設備,在這個廢棄的方法中,它們聲明在單獨的模塊中或者arch/<arch>/mach-xxx/yyy.c的開發板init文件中,在函數platform_device_register()
內實現聲明:
static struct platform_device my_device = {//進行初始化
};
platform_device_register(&my_device);
2 設備配置—推薦的新方法
在第一種方法中,任何修改都需要重構整個內核。如果內核必須包含所有應用程序/開發板特殊配置,則其大小將會大大增加。為了簡單起見,從內核源中分離設備聲明,并引入一個新的概念:設備樹(DTS)。設備樹(DTS)的主要目的是從內核中刪除特定且從未測試過的代碼。使用設備樹,平臺數據和資源時同質的。設備樹是硬件描述文件,其格式類似于樹形結構,每個設備用一個結點表示,任何數據、資源或配置數據都表示為結點的屬性。這樣,在做一些修改只需重新編譯設備樹。
3 設備、驅動程序和總線匹配
在匹配發生之前,Linux會調用platform_match(struct device *dev,struct device_driver *drv)
。平臺設備(struct platform_device
的實例)通過字符(name字段)與驅動程序匹配(struct platform_driver.driver.name
)。根據Linux設備模型,總線元素是最重要的部分。每個總線都維護一個注冊的驅動程序和設備列表。總線驅動程序負責設備和驅動程序的匹配。每當連接新設備或者向總線添加新的驅動程序時,總線都會啟動匹配循環。
現在,假設使用I2C核心提供的函數注冊新的I2C設備。內核提供如下方法觸發I2C總線匹配循環:調用由I2C總線驅動程序注冊的I2C核心匹配函數,以檢查是否有已注冊的驅動程序與該設備匹配。如果沒有匹配,則什么都不做;如果發現匹配,則內核通知設備管理器(udev/mdev),由它加載與設備匹配的驅動程序。一旦設備驅動程序加載完成,其probe()函數將立即執行。不僅I2C這樣運行,而且每個總線自己的匹配機制都大致于此相同。總線匹配循環在每個設備或驅動程序注冊時被觸發。
內核負責平臺設備和驅動程序匹配功能的函數在/drivers/base/platform.c中,定義如下:
/*** platform_match - bind platform device to platform driver.* @dev: device.* @drv: driver.** Platform device IDs are assumed to be encoded like this:* "<name><instance>", where <name> is a short description of the type of* device, like "pci" or "floppy", and <instance> is the enumerated* instance of the device, like '0' or '42'. Driver IDs are simply* "<name>". So, extract the <name> from the platform_device structure,* and compare it against the name of the driver. Return whether they match* or not.*/
static int platform_match(struct device *dev, struct device_driver *drv)
{struct platform_device *pdev = to_platform_device(dev);struct platform_driver *pdrv = to_platform_driver(drv);/* When driver_override is set, only bind to the matching driver */if (pdev->driver_override)return !strcmp(pdev->driver_override, drv->name);/* Attempt an OF style match first */if (of_driver_match_device(dev, drv))return 1;/* Then try ACPI style match */if (acpi_driver_match_device(dev, drv))return 1;/* Then try to match against the id table */if (pdrv->id_table)return platform_match_id(pdrv->id_table, pdev) != NULL;/* fall-back to driver name match */return (strcmp(pdev->name, drv->name) == 0);
}
該函數的描述如下:
平臺設備 ID 假定編碼如下:“<name><instance>”,其中\ <name>是設備類型的簡短描述,如“pci”或“floppy”,<instance> 是 設備的枚舉實例,如“0”或“42”。 驅動程序 ID只是“<name>”。 因此,從 platform_device 結構中提取 <name>,并將其與驅動程序的名稱進行比較。返回它們是否匹配。
對于平臺驅動程序,在驅動程序和設備匹配之前,struct platform_device
的name字段要和 struct platform_driver.driver.name
相同,這樣才能匹配上。
匹配順序如下:
- 平臺設備設置 driver_override 時,僅綁定到匹配的驅動程序
- 嘗試OF(Open Firmware,開放固件)風格匹配
- 嘗試ACPI風格匹配
- 嘗試ID表匹配
- 匹配平臺設備的名字和平臺驅動的名字
OF風格
/*** of_driver_match_device - Tell if a driver's of_match_table matches a device.* @drv: the device_driver structure to test* @dev: the device structure to match against*/
static inline int of_driver_match_device(struct device *dev,const struct device_driver *drv)
{return of_match_device(drv->of_match_table, dev) != NULL;
}
of_driver_match_device判斷驅動程序的 of_match_table 是否與設備匹配。
ACPI
ACPI基于ACPI表,不討論
ID表匹配
ID表基于struct device_id
結構。所有設備ID結構都在include/linux/mod_devicetable.h中定義。要找到正確的結構名稱,需要在device_id前加上設備驅動程序所在的總線名稱。例如:對于I2C,結構名稱是struct i2c_device_id
,而平臺設備是struct platform_device_id
,SPI設備是spi_device_id
。
if (pdrv->id_table)return platform_match_id(pdrv->id_table, pdev) != NULL;static const struct platform_device_id *platform_match_id(const struct platform_device_id *id,struct platform_device *pdev)
{while (id->name[0]) {if (strcmp(pdev->name, id->name) == 0) {pdev->id_entry = id;return id;}id++;}return NULL;
}
平臺驅動的id_table是 平臺驅動的ID表,類似是platform_device_id
struct platform_device_id {char name[PLATFORM_NAME_SIZE];kernel_ulong_t driver_data;
};
name字段必須與注冊設備時所指定的設備名稱相同,platform_match_id函數會找到平臺驅動的ID表,然后遍歷ID表中的ID,將每個ID的name字段與平臺設備的name作比較,如果相同,則平臺設備的id_entry 等于ID,然后返回該ID,如果沒有ID的name字段和平臺設備的name相同,返回NULL。
無論如何,如果ID表已經注冊,則每當內核運行匹配函數為止或新的平臺設備查找驅動程序時,都會遍歷ID表。如果匹配成功,則調用已匹配驅動程序的probe函數。
匹配平臺設備的名字和平臺驅動的名字
現在大多數平臺驅動程序根本不提供任何ID表,它們只在程序名稱字段中填寫驅動程序本身的名稱。但是仍然可行,因為platform_match函數,在最后會回到名字匹配,比較驅動程序名稱和設備名稱。
/* fall-back to driver name match */return (strcmp(pdev->name, drv->name) == 0);
平臺設備和平臺驅動程序如何匹配
MODULE_DEVICE_TABLE(type,name)
宏讓驅動程序公開其ID表,該表描述它可以支持那些設備。同時,如果驅動程序可以編譯成模塊,則平臺驅動實例的driver.name字段要與模塊名稱匹配。如果不匹配,模塊則不會自動加載,除非已經使用MODULE_ALIAS宏為模塊添加了另一個名稱。編譯時,從所有驅動程序中提取該消息,以構建設備表。當設備和驅動程序匹配時,內核遍歷設備表。如果找到的條目與添加的設備兼容,并與設備/供應商ID或名稱匹配,則加載提供該匹配的模塊,運行模塊的init函數,調用probe函數。
MODULE_DEVICE_TABLE宏在linux/module.h中定義:
/* Creates an alias so file2alias.c can find device table. */
#define MODULE_DEVICE_TABLE(type, name) \
extern const typeof(name) __mod_##type##__##name##_device_table \__attribute__ ((unused, alias(__stringify(name))))
- type:這可以是i2c、spi、acpi、of、platform、usb、pci。也可以是在include/linux/mod_devicetable.h中找到的其他任何總線
- name:這是XXX_device_id數組上的指針,用于設備匹配。對于I2C設備。結構是i2c_device_id。對于設備樹的Open Firmware(開放固件,OF)匹配機制,必須使用of_device_id。
4 Platfrom架構驅動程序
代碼原文章:https://www.toutiao.com/article/6874918991081505283/?log_from=d62ff69d6ea06_1651283905265
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/cdev.h>
#include <linux/platform_device.h>
#include <linux/fs.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/slab.h>#define BUFFER_MAX (10)
#define OK (0)
#define ERROR (-1)struct cdev *gDev;
struct file_operations *gFile;
dev_t devNum;
unsigned int subDevNum = 1;
int reg_major = 232;
int reg_minor = 0;
char *buffer;
#define LEDBASE 0x56000010
#define LEDLEN 0x0cint hello_open(struct inode *p, struct file *f)
{printk(KERN_INFO "hello_open\r\n");return 0;
}ssize_t hello_write(struct file *f, const char __user *u, size_t s, loff_t *l)
{printk(KERN_INFO "hello_write\r\n");return 0;
}
ssize_t hello_read(struct file *f, char __user *u, size_t s, loff_t *l)
{printk(KERN_INFO "hello_read\r\n"); return 0;
}static int hellodev_probe(struct platform_device *pdev)
{printk(KERN_INFO "hellodev_probe\n");devNum = MKDEV(reg_major, reg_minor);if(OK == register_chrdev_region(devNum, subDevNum, "helloworld")){printk(KERN_INFO "register_chrdev_region ok \n"); }else {printk(KERN_INFO "register_chrdev_region error n");return ERROR;}printk(KERN_INFO " hello driver init \n");gDev = kzalloc(sizeof(struct cdev), GFP_KERNEL);gFile = kzalloc(sizeof(struct file_operations), GFP_KERNEL);gFile->open = hello_open;gFile->read = hello_read;gFile->write = hello_write;gFile->owner = THIS_MODULE;cdev_init(gDev, gFile);cdev_add(gDev, devNum, 1);return 0;
}
static int hellodev_remove(struct platform_device *pdev)
{printk(KERN_INFO "hellodev_remove \n");cdev_del(gDev);kfree(gFile);kfree(gDev);unregister_chrdev_region(devNum, subDevNum);return;
}static void hello_plat_release(struct device *dev)
{return;
}
static struct resource hello_dev_resource[] = {[0] = {.start = LEDBASE,.end = LEDBASE + LEDLEN - 1,.flags = IORESOURCE_MEM,}
};struct platform_device hello_device = {.name = "hello-device",.id = -1,.num_resources = ARRAY_SIZE(hello_dev_resource),.resource = hello_dev_resource,.dev = {.release = hello_plat_release,}
};
static struct platform_driver hellodev_driver = {.probe = hellodev_probe,.remove = hellodev_remove,.driver = {.owner = THIS_MODULE,.name = "hello-device",},
};int charDrvInit(void)
{platform_device_register(&hello_device);return platform_driver_register(&hellodev_driver);
}void __exit charDrvExit(void)
{platform_device_unregister(&hello_device);platform_driver_unregister(&hellodev_driver);return;
}
module_init(charDrvInit);
module_exit(charDrvExit);
MODULE_LICENSE("GPL");
Makefile:
ifneq ($(KERNELRELEASE),)
obj-m := helloDev.o
else
PWD := $(shell pwd)
#KDIR:=/home/jinxin/linux-4.9.229
#KDIR:= /lib/modules/4.4.0-31-generic/build
KDIR := /lib/modules/`uname -r`/build
all:make -C $(KDIR) M=$(PWD)
clean: rm -rf *.o *.ko *.mod.c *.symvers *.c~ *~
endif