1.首先要實現入口函數
xxx_init()
和卸載函數xxx_exit()
node2.申請設備號
register_chrdev
linux3.建立設備節點,如
class_create
,device_create
shell4.硬件部分初始化,如io資源映射
ioremap
,中斷註冊等express5.構建
file_operation
結構編程6.實現
xxx_open
,xxx_read
,xxx_write
等函數ubuntu
ioremap
函數映射地址,以後經過readl
或者writel
函數操做地址在
/sys
下面有總線文件夾,設備文件夾和類文件夾.api總線文件夾保存總線信息,如i2c總線,usb總線;數組
設備文件夾下面保存設備信息,如usb設備bash
在類文件夾下保存事件信息app
在某一總線下面又包含設備和驅動,驅動即driver,設備即device,其中的設備經過軟連接的方式指向
/sys/devices
中的設備
/sys
目錄查看其中的文件,能夠看到其中有bus總線目錄,devices設備目錄(記錄設備信息)topeet@ubuntu:~$ cd /sys topeet@ubuntu:/sys$ ls block bus class dev devices firmware fs hypervisor kernel module power topeet@ubuntu:/sys/devices$ cd /sys/devices/ topeet@ubuntu:/sys/devices$ ls breakpoint cpu LNXSYSTM:00 pci0000:00 platform pnp0 rapidio software system tracepoint virtual topeet@ubuntu:/sys/devices$
/sys/bus
目錄,可看到其中有不少總線,以usb
總線爲例子,在其內部就有devices
設備文件和drivers
驅動文件topeet@ubuntu:/sys/bus$ ls ac97 cpu hid mdio_bus node platform scsi spi xen acpi event_source i2c memory pci pnp sdio usb xen-backend clocksource gameport machinecheck mmc pci_express rapidio serio virtio topeet@ubuntu:/sys/bus$ cd usb/ topeet@ubuntu:/sys/bus/usb$ ls devices drivers drivers_autoprobe drivers_probe uevent
/dev
目錄下生成相應節點,可是卻不能查看其詳細信息,如輸入設備事件,咱們直接使用ls /dev/input/event*
指令查看輸入設備事件節點,可是卻看不到其對於驅動信息,其具體信息保存在/sys/class/input
下面# 查看輸入設備節點 topeet@ubuntu:~$ ls /dev/input/event* /dev/input/event0 /dev/input/event1 /dev/input/event2 /dev/input/event3 /dev/input/event4 # 查看輸入設備節點信息 topeet@ubuntu:~$ cd /sys/class/input/ topeet@ubuntu:/sys/class/input$ ls event0 event2 event4 input1 input3 js0 mouse0 mouse2 event1 event3 input0 input2 input4 mice mouse1 # 查看event0節點設備號 topeet@ubuntu:/sys/class/input$ cd event0 topeet@ubuntu:/sys/class/input/event0$ ls dev device power subsystem uevent topeet@ubuntu:/sys/class/input/event0$ cat uevent MAJOR=13 MINOR=64 DEVNAME=input/event0 # 查看event0節點設備號 topeet@ubuntu:/sys/class/input/event0$ ls -l /dev/input/event0 crw-r----- 1 root root 13, 64 Nov 12 02:43 /dev/input/event0 # 查看event0對於驅動名稱 topeet@ubuntu:/sys/class/input/event0$ cd device topeet@ubuntu:/sys/class/input/event0/device$ ls capabilities device event0 id modalias name phys power properties subsystem uevent uniq topeet@ubuntu:/sys/class/input/event0/device$ cat name Power Button topeet@ubuntu:/sys/class/input/event0/device$ cat uevent PRODUCT=19/0/1/0 NAME="Power Button" PHYS="LNXPWRBN/button/input0" PROP=0 EV=3 KEY=10000000000000 0 MODALIAS=input:b0019v0000p0001e0000-e0,1,k74,ramlsfw
/sys/bus/
文件夾下/sys/bus/
下面建立咱們本身的總線mybus
,以後在mybus
內部實現driver驅動和device設備軟連接,其指向/sys/devices/
目錄下的mydevice
設備,如圖struct bus_type
,用於描述一個總線,管理device和driver,完成兩者匹配struct bus_type { const char *name; const char *dev_name; struct device *dev_root; const struct attribute_group **bus_groups; const struct attribute_group **dev_groups; const struct attribute_group **drv_groups; int (*match)(struct device *dev, struct device_driver *drv); int (*uevent)(struct device *dev, struct kobj_uevent_env *env); int (*probe)(struct device *dev); int (*remove)(struct device *dev); void (*shutdown)(struct device *dev); int (*online)(struct device *dev); int (*offline)(struct device *dev); int (*suspend)(struct device *dev, pm_message_t state); int (*resume)(struct device *dev); int (*num_vf)(struct device *dev); const struct dev_pm_ops *pm; const struct iommu_ops *iommu_ops; struct subsys_private *p; struct lock_class_key lock_key; };
struct bus_type { const char *name; int (*match)(struct device *dev, struct device_driver *drv); };
int bus_register(struct bus_type * bus); //非0表明失敗 void bus_unregister(struct bus_type * bus);
#include <linux/init.h> #include <linux/module.h> #include <linux/device.h> struct bus_type mybus = { .name = "mybus", }; static int __init mybus_init(void) { int ret; ret = bus_register(&mybus); //註冊總線 if(ret != 0) { printk("bus_register error\n"); return ret; } return 0; } static void __exit mybus_exit(void) { bus_unregister(&mybus); //卸載總線 } module_init(mybus_init); module_exit(mybus_exit); MODULE_LICENSE("GPL");
/sys/bus
目錄下可看到本身的總線mybus成功被建立,進入其目錄下咱們會發現系統已經自動建立了系列文件,可是drivers和devices文件夾爲空,這部分須要咱們本身去實現[root@iTOP-4412]# insmod my_bus.ko [root@iTOP-4412]# cd /sys/bus/ [root@iTOP-4412]# ls amba cpu iio mybus sdio workqueue clockevents gpio mdio_bus nvmem serio clocksource hid mipi-dsi platform spi container i2c mmc scsi usb [root@iTOP-4412]# cd mybus/ [root@iTOP-4412]# ls devices drivers_autoprobe uevent drivers drivers_probe [root@iTOP-4412]# cd devices/ [root@iTOP-4412]# cd .. [root@iTOP-4412]# cd drivers/ [root@iTOP-4412]#
struct device { struct device *parent; struct device_private *p; struct kobject kobj; //全部對象的父類 const char *init_name; /* initial name of the device */ const struct device_type *type; struct mutex mutex; /* mutex to synchronize calls to * its driver. */ struct bus_type *bus; /* type of bus device is on */ struct device_driver *driver; /* which driver has allocated this device */ void *platform_data; /* Platform specific data, device core doesn't touch it */ void *driver_data; /* Driver data, set and get with dev_set/get_drvdata */ struct dev_links_info links; struct dev_pm_info power; struct dev_pm_domain *pm_domain; #ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN struct irq_domain *msi_domain; #endif #ifdef CONFIG_PINCTRL struct dev_pin_info *pins; #endif #ifdef CONFIG_GENERIC_MSI_IRQ struct list_head msi_list; #endif #ifdef CONFIG_NUMA int numa_node; /* NUMA node this device is close to */ #endif const struct dma_map_ops *dma_ops; u64 *dma_mask; /* dma mask (if dma'able device) */ u64 coherent_dma_mask;/* Like dma_mask, but for alloc_coherent mappings as not all hardware supports 64 bit addresses for consistent allocations such descriptors. */ unsigned long dma_pfn_offset; struct device_dma_parameters *dma_parms; struct list_head dma_pools; /* dma pools (if dma'ble) */ struct dma_coherent_mem *dma_mem; /* internal for coherent mem override */ #ifdef CONFIG_DMA_CMA struct cma *cma_area; /* contiguous memory area for dma allocations */ #endif /* arch specific additions */ struct dev_archdata archdata; struct device_node *of_node; /* associated device tree node */ struct fwnode_handle *fwnode; /* firmware device node */ dev_t devt; /* dev_t, creates the sysfs "dev" */ u32 id; /* device instance */ spinlock_t devres_lock; struct list_head devres_head; struct klist_node knode_class; struct class *class; const struct attribute_group **groups; /* optional groups */ void (*release)(struct device *dev); /*使用platform_device_register函數時候須要實現*/ struct iommu_group *iommu_group; struct iommu_fwspec *iommu_fwspec; bool offline_disabled:1; bool offline:1; bool of_node_reused:1; };
struct device { struct kobject kobj; //全部對象的父類,相似於繼承父類 const char *init_name; //在總線中/sys/bus/devices中會建立一個該名字命名的文件,用於匹配 struct bus_type *bus; //指向該device對象依附的總線對象 struct device_driver *driver; //描述該device被哪一個driver驅動 void *platform_data; //自定義數據,可指向任何數據類型的數據 };
int device_register(struct device * dev); //小於0表明設備註冊失敗 int device_unregister(struct device * dev);
#include <linux/init.h> #include <linux/module.h> #include <linux/device.h> /*導入外部結構體變量,使得編譯經過*/ extern struct bus_type mybus; struct device mydev = { .init_name = "mydev", .bus = &mybus, }; static int __init mydev_init(void) { int dr_ret; /*註冊device到總線中去*/ dr_ret = device_register(&mydev); if(dr_ret < 0) { printk("device_register error\n"); return dr_ret; } return 0; } static void __exit mydev_exit(void) { device_unregister(&mydev); } module_init(mydev_init); module_exit(mydev_exit); MODULE_LICENSE("GPL");
#include <linux/init.h> #include <linux/module.h> #include <linux/device.h> struct bus_type mybus = { .name = "mybus", }; /*導出結構體,方便內核之間交互*/ EXPORT_SYMBOL(mybus); static int __init mybus_init(void) { int ret; ret = bus_register(&mybus); //註冊總線 if(ret != 0) { printk("bus_register error\n"); return ret; } return 0; } static void __exit mybus_exit(void) { bus_unregister(&mybus); //卸載總線 } module_init(mybus_init); module_exit(mybus_exit); MODULE_LICENSE("GPL");
my_bus.ko
驅動後在/sys/bus/
下出現本身的總線.安裝my_dev.ko
後在/sys/bus/mybus/devices/
出現本身的設備[root@iTOP-4412]# ls key_drv.ko my_bus.ko my_dev.ko [root@iTOP-4412]# ls /sys/bus/ amba cpu iio nvmem serio clockevents gpio mdio_bus platform spi clocksource hid mipi-dsi scsi usb container i2c mmc sdio workqueue [root@iTOP-4412]# insmod my_bus.ko [root@iTOP-4412]# ls /sys/bus/ amba cpu iio mybus sdio workqueue clockevents gpio mdio_bus nvmem serio clocksource hid mipi-dsi platform spi container i2c mmc scsi usb [root@iTOP-4412]# ls /sys/bus/mybus/devices/ [root@iTOP-4412]# insmod my_dev.ko [root@iTOP-4412]# ls /sys/bus/mybus/devices/ mydev [root@iTOP-4412]#
struct device_driver { const char *name; //在總線中/sys/bus/drivers中會建立一個該名字命名的文件,用於匹配 struct bus_type *bus; //指向該driver對象依附的總線對象 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); //device與driver匹配以後,driver要乾的事情 int (*remove) (struct device *dev); //device與driver從總線移除後,driver要乾的事情 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; };
int driver_register(struct device_driver * drv); //小於0表明驅動註冊失敗 void driver_unregister(struct device_driver * drv);
#include <linux/init.h> #include <linux/module.h> #include <linux/device.h> int mydrv_probe (struct device *dev) { return 0; } int mydrv_remove (struct device *dev) { return 0; } /*導入外部結構體變量,使得編譯經過*/ extern struct bus_type mybus; struct device_driver mydrv = { .name = "mydrv", .bus = &mybus, .probe = mydrv_probe, .remove = mydrv_remove, }; static int __init mydrv_init(void) { int dr_ret; /*註冊driver到總線中去*/ dr_ret = driver_register(&mydrv); if(dr_ret < 0) { printk("driver_register error\n"); return dr_ret; } return 0; } static void __exit mydrv_exit(void) { driver_unregister(&mydrv); } module_init(mydrv_init); module_exit(mydrv_exit); MODULE_LICENSE("GPL");
my_bus.c
,my_dev.c
,my_drv.c
ROOTFS_DIR = /mnt/hgfs/share_drv/driver_ko # 應用程序代碼名稱 #APP_NAME = key_test # 驅動代碼名稱 MODULE_NAME = my_bus MODULE_NAME1 = my_dev MODULE_NAME2 = my_drv CROSS_COMPILE = /usr/local/arm/gcc-4.6.2-glibc-2.13-linaro-multilib-2011.12/fsl-linaro-toolchain/bin/arm-linux- CC = $(CROSS_COMPILE)gcc ifeq ($(KERNELRELEASE), ) KERNEL_DIR = /home/topeet/kernel/itop4412_kernel_4_14_2_bsp/linux-4.14.2_iTop-4412_scp CUR_DIR = $(shell pwd) all: make -C $(KERNEL_DIR) M=$(CUR_DIR) modules # $(CC) $(APP_NAME).c -o $(APP_NAME) clean: make -C $(KERNEL_DIR) M=$(CUR_DIR) clean rm $(APP_NAME) install: cp -raf *.ko $(APP_NAME) $(ROOTFS_DIR) else obj-m += $(MODULE_NAME).o obj-m += $(MODULE_NAME1).o obj-m += $(MODULE_NAME2).o endif
/sys/bus/mybus/devices/
和/sys/bus/mybus/drivers
目錄下有對應驅動文件出現[root@iTOP-4412]# ls key_drv.ko my_bus.ko my_dev.ko my_drv.ko [root@iTOP-4412]# ls /sys/bus/ amba cpu iio nvmem serio clockevents gpio mdio_bus platform spi clocksource hid mipi-dsi scsi usb container i2c mmc sdio workqueue [root@iTOP-4412]# insmod my_bus.ko [ 525.038385] my_bus: loading out-of-tree module taints kernel. [root@iTOP-4412]# ls /sys/bus/ amba cpu iio mybus sdio workqueue clockevents gpio mdio_bus nvmem serio clocksource hid mipi-dsi platform spi container i2c mmc scsi usb [root@iTOP-4412]# ls /sys/bus/mybus/devices/ [root@iTOP-4412]# insmod my_dev.ko [root@iTOP-4412]# ls /sys/bus/mybus/devices/ mydev [root@iTOP-4412]# ls /sys/bus/mybus/drivers [root@iTOP-4412]# insmod my_drv.ko [root@iTOP-4412]# ls /sys/bus/mybus/drivers mydrv [root@iTOP-4412]#
probe
方法,可是在上述代碼中driver的名字爲mydrv(.name = "mydrv",
),device的名字爲mydev(.init_name = "mydev",
).可見兩者名字不一樣,那麼天然沒法匹配probe
方法呢?此時須要咱們本身在bus中實現匹配的邏輯過程,而且將device與driver中的名字改成同樣,即總線做用之一就是匹配device和driver//注意:若是匹配成功,match方法必定要返回1;匹配失敗會返回0 int (*match)(struct device *dev, struct device_driver *drv); /*demo*/ int mybus_match(struct device *dev, struct device_driver *drv) { /*注意參數2爲kobj對象中的name,而不是dev->init_name*/ /*匹配成功返回0,取反後爲true*/ if(!strncmp(drv->name, dev->kobj.name, strlen(drv->name))) { printk("match ok\n"); return 1; } else { printk("match failed\n"); return 0; } }
特別注意:在執行strncmp函數的時候,參數1爲device_driver對象中的name屬性,參數2不能是device對象中的init_name屬性,由於在內核中會將device對象中的init_name值賦給kobject對象中的name,而後將本身置爲NULL,若是使用dev->init_name會出現段錯誤,其中kobject對象以下,其中init_name的值就給了kobject中的name
struct kobject { const char *name; struct list_head entry; struct kobject *parent; struct kset *kset; struct kobj_type *ktype; struct kernfs_node *sd; /* sysfs directory entry */ struct kref kref; #ifdef CONFIG_DEBUG_KOBJECT_RELEASE struct delayed_work release; #endif unsigned int state_initialized:1; unsigned int state_in_sysfs:1; unsigned int state_add_uevent_sent:1; unsigned int state_remove_uevent_sent:1; unsigned int uevent_suppress:1; };
#include <linux/init.h> #include <linux/module.h> #include <linux/device.h> int mybus_match(struct device *dev, struct device_driver *drv) { if(!strncmp(drv->name, dev->kobj.name, strlen(drv->name))) { printk("match ok\n"); return 1; } else { printk("match failed\n"); return 0; } } struct bus_type mybus = { .name = "mybus", .match = mybus_match, }; /*導出結構體,方便內核之間交互*/ EXPORT_SYMBOL(mybus); static int __init mybus_init(void) { int ret; ret = bus_register(&mybus); //註冊總線 if(ret != 0) { printk("bus_register error\n"); return ret; } return 0; } static void __exit mybus_exit(void) { bus_unregister(&mybus); //卸載總線 } module_init(mybus_init); module_exit(mybus_exit); MODULE_LICENSE("GPL");
#include <linux/init.h> #include <linux/module.h> #include <linux/device.h> void mydev_release (struct device *dev) { printk("------%s------\n", __FUNCTION__); } /*導入外部結構體變量,使得編譯經過*/ extern struct bus_type mybus; struct device mydev = { .init_name = "myderv", .bus = &mybus, .release = mydev_release, }; static int __init mydev_init(void) { int dr_ret; /*註冊device到總線中去*/ dr_ret = device_register(&mydev); if(dr_ret < 0) { printk("device_register error\n"); return dr_ret; } return 0; } static void __exit mydev_exit(void) { device_unregister(&mydev); } module_init(mydev_init); module_exit(mydev_exit); MODULE_LICENSE("GPL");
#include <linux/init.h> #include <linux/module.h> #include <linux/device.h> int mydrv_probe (struct device *dev) { printk("------%s------\n", __FUNCTION__); return 0; } int mydrv_remove (struct device *dev) { printk("------%s------\n", __FUNCTION__); return 0; } /*導入外部結構體變量,使得編譯經過*/ extern struct bus_type mybus; struct device_driver mydrv = { .name = "myderv", .bus = &mybus, .probe = mydrv_probe, .remove = mydrv_remove, }; static int __init mydrv_init(void) { int dr_ret; /*註冊driver到總線中去*/ dr_ret = driver_register(&mydrv); if(dr_ret < 0) { printk("driver_register error\n"); return dr_ret; } return 0; } static void __exit mydrv_exit(void) { driver_unregister(&mydrv); } module_init(mydrv_init); module_exit(mydrv_exit); MODULE_LICENSE("GPL");
[root@iTOP-4412]# insmod my_bus.ko [ 29.385382] my_bus: loading out-of-tree module taints kernel. [root@iTOP-4412]# insmod my_dev.ko [root@iTOP-4412]# insmod my_drv.ko [ 45.094375] match ok [ 45.095171] ------mydrv_probe------ [root@iTOP-4412]#
int (*probe) (struct device *dev);
void *platform_data; //自定義數據,可指向任何數據類型的數據
dev_info.h
來聲明一個類,用於保存device設備屬性#ifndef __DEV_INFO_H__ #define __DEV_INFO_H__ /*自定義一個數據類,描述設備特性*/ struct mydev_desc { char *name; int irqno; unsigned long addr; }; #endif
struct mydev_desc mydev_info = { .name = "test_dev", .irqno = 999, .addr = 0x30006000, }; struct device mydev = { .init_name = "myderv", .bus = &mybus, .release = mydev_release, .platform_data = &mydev_info, };
struct mydev_desc *pdesc; int mydrv_probe (struct device *dev) { unsigned long *paddr; printk("------%s------\n", __FUNCTION__); /*注意強制類型轉換*/ pdesc = (struct mydev_desc *)dev->platform_data; printk("name = %s\n", pdesc->name); printk("irqno = %d\n", pdesc->irqno); paddr = ioremap(pdesc->addr, 8); return 0; }
#ifndef __DEV_INFO_H__ #define __DEV_INFO_H__ /*自定義一個數據類,描述設備特性*/ struct mydev_desc { char *name; int irqno; unsigned long addr; }; #endif
#include <linux/init.h> #include <linux/module.h> #include <linux/device.h> int mybus_match(struct device *dev, struct device_driver *drv) { if(!strncmp(drv->name, dev->kobj.name, strlen(drv->name))) { printk("match ok\n"); return 1; } else { printk("match failed\n"); return 0; } } struct bus_type mybus = { .name = "mybus", .match = mybus_match, }; /*導出結構體,方便內核之間交互*/ EXPORT_SYMBOL(mybus); static int __init mybus_init(void) { int ret; ret = bus_register(&mybus); //註冊總線 if(ret != 0) { printk("bus_register error\n"); return ret; } return 0; } static void __exit mybus_exit(void) { bus_unregister(&mybus); //卸載總線 } module_init(mybus_init); module_exit(mybus_exit); MODULE_LICENSE("GPL");
#include <linux/init.h> #include <linux/module.h> #include <linux/device.h> #include "dev_info.h" struct mydev_desc mydev_info = { .name = "test_dev", .irqno = 999, .addr = 0x30006000, }; void mydev_release (struct device *dev) { printk("------%s------\n", __FUNCTION__); } /*導入外部結構體變量,使得編譯經過*/ /*此處填寫設備專有的信息*/ extern struct bus_type mybus; struct device mydev = { .init_name = "myderv", .bus = &mybus, .release = mydev_release, .platform_data = &mydev_info, }; static int __init mydev_init(void) { int dr_ret; /*註冊device到總線中去*/ dr_ret = device_register(&mydev); if(dr_ret < 0) { printk("device_register error\n"); return dr_ret; } return 0; } static void __exit mydev_exit(void) { device_unregister(&mydev); } module_init(mydev_init); module_exit(mydev_exit); MODULE_LICENSE("GPL");
#include <linux/init.h> #include <linux/module.h> #include <linux/device.h> #include <linux/io.h> #include "dev_info.h" struct mydev_desc *pdesc; /*設備與驅動匹配成功就提取設備信息*/ int mydrv_probe (struct device *dev) { unsigned long *paddr; printk("------%s------\n", __FUNCTION__); /*此處注意強轉*/ pdesc = (struct mydev_desc *)dev->platform_data; printk("name = %s\n", pdesc->name); printk("irqno = %d\n", pdesc->irqno); paddr = ioremap(pdesc->addr, 8); return 0; } int mydrv_remove (struct device *dev) { printk("------%s------\n", __FUNCTION__); return 0; } /*導入外部結構體變量,使得編譯經過*/ extern struct bus_type mybus; struct device_driver mydrv = { .name = "myderv", .bus = &mybus, .probe = mydrv_probe, .remove = mydrv_remove, }; static int __init mydrv_init(void) { int dr_ret; /*註冊driver到總線中去*/ dr_ret = driver_register(&mydrv); if(dr_ret < 0) { printk("driver_register error\n"); return dr_ret; } return 0; } static void __exit mydrv_exit(void) { driver_unregister(&mydrv); } module_init(mydrv_init); module_exit(mydrv_exit); MODULE_LICENSE("GPL");
[root@iTOP-4412]# insmod my_bus.ko [ 207.194598] my_bus: loading out-of-tree module taints kernel. [root@iTOP-4412]# insmod my_dev.ko [root@iTOP-4412]# insmod my_drv.ko [ 220.166648] match ok [ 220.167439] ------mydrv_probe------ [ 220.176020] name = test_dev [ 220.177356] irqno = 999 [root@iTOP-4412]#
/sys/bus/
下面生成一個platform總線topeet@ubuntu:~$ cd /sys/bus/ topeet@ubuntu:/sys/bus$ ls ac97 clocksource event_source hid machinecheck memory node pci_express pnp scsi serio usb xen acpi cpu gameport i2c mdio_bus mmc pci platform rapidio sdio spi virtio xen-backend # 系統生成的platform總線 topeet@ubuntu:/sys/bus$ cd platform/ topeet@ubuntu:/sys/bus/platform$ ls devices drivers drivers_autoprobe drivers_probe uevent topeet@ubuntu:/sys/bus/platform$
struct bus_type platform_bus_type = { .name = "platform", .dev_groups = platform_dev_groups, .match = platform_match, .uevent = platform_uevent, .pm = &platform_dev_pm_ops, };
1.優先匹配driver中的id_table,在其中包含了支持不一樣的平臺的名字
2.直接匹配driver中的名字和device中的名字,與1.5相似
struct platform_device { const char *name; //與driver作匹配使用 int id; //一半直接爲-1 bool id_auto; struct device dev; //繼承了1.5中的device父類 u32 num_resources; //資源的個數 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_driver { int (*probe)(struct platform_device *); //platform_device與platform_driver匹配以後,platform_driver要乾的事情 int (*remove)(struct platform_device *); //platform_device與platform_driver從總線移除後,platform_driver要乾的事情 void (*shutdown)(struct platform_device *); int (*suspend)(struct platform_device *, pm_message_t state); int (*resume)(struct platform_device *); struct device_driver driver; //繼承driver父類 const struct platform_device_id *id_table; //記錄該platform_driver所支持的平臺 bool prevent_deferred_probe; };
container_of
宏來獲取父類指針,即若是一個結構體內部(子類)包含一個結構體(父類),咱們就能夠根據被包含的結構體地址去獲取包含的結構體的地址,即由父類地址去獲取子類地址#define to_platform_device(x) container_of((x), struct platform_device, dev) 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; } 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; /*若是platform_driver中有id_table,則優先匹配pdrv中的設備,不然匹配drvice中的名字*/ /* 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); }
platform_device_register
或者platform_device_add
/*註冊設備,須要實現platform_device->dev->release方法*/ int platform_device_register(struct platform_device *pdev); /*註冊設備*/ int platform_device_add(struct platform_device * pdev); /*卸載設備*/ void platform_device_unregister(struct platform_device *pdev);
struct resource { resource_size_t start; //起始地址 resource_size_t end; //結束地址 const char *name; //自定義名字 unsigned long flags; //標誌,表示當前資源是描述內存(IORESOURCE_MEM)仍是中斷(IORESOURCE_IRQ) struct resource *parent, *sibling, *child; };
KP_COL0
,一個爲VDD50_EN
,此可咱們須要跳轉到核心板原理圖去尋找兩個引腳GPL2_0
和GPK1_1
引腳,以後再去查找芯片數據手冊GPL2CON
描述以下,能夠看出其基地址爲0x1100 0000(#define GPL_BASE 0x11000000
),偏移地址爲0x0100,因此描述資源的起始地址可設置爲0x1100 0100(#define GPL2_CON GPL_BASE + 0x0100
),而資源的終止地址則爲起始地址加上GPL2系列地址的總長度(24Byte)#define GPL2_SIZE 24
),所以結束地址爲GPL2_CON + GPL2_SIZE - 1
.特別注意:最後要減去1,假設GPL2CON編號爲1,那麼GPL2PUDPDN編號爲6,即1 + 6 - 1 = 6,因此要減去1.#define GPL_BASE 0x11000000 #define GPL2_CON GPL_BASE + 0x0100 #define GPL2_SIZE 24 #define GPK_BASE 0x11000000 #define GPK1_CON GPK_BASE + 0x0060 #define GPK1_SIZE 24 /*定義爲一個數組,由於一個設備中可能有多個資源*/ struct resource led_res[] = { [0] = { .start = GPL2_CON, .end = GPL2_CON + GPL2_SIZE - 1, //注意減去1個偏移量 .flags = IORESOURCE_MEM, }, [1] = { .start = GPK1_CON, .end = GPK1_CON + GPK1_SIZE - 1, .flags = IORESOURCE_MEM, }, };
#define IRQ_EINT(x) (((x) >= 4) ? (IRQ_EINT4 + (x) - 4) : (IRQ_EINT0 + (x))) struct resource led_res[] = { /*中斷資源,此處描述4號中斷*/ [0] = { .start = IRQ_EINT(4), .end = IRQ_EINT(4), .flags = IORESOURCE_IRQ, }, };
struct platform_device led_pdev = { .name = "exynos4412_led", //用做匹配 .id = -1, //通常取-1,以後會詳解 .num_resources = ARRAY_SIZE(led_res), //資源個數,此處爲2 .resource = led_res, //資源數組 };
#include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> #define GPL_BASE 0x11000000 #define GPL2_CON GPL_BASE + 0x0100 #define GPL2_SIZE 24 #define GPK_BASE 0x11000000 #define GPK1_CON GPK_BASE + 0x0060 #define GPK1_SIZE 24 /*定義爲一個數組,由於一個設備中可能有多個資源*/ struct resource led_res[] = { [0] = { .start = GPL2_CON, .end = GPL2_CON + GPL2_SIZE - 1, //注意減去1個偏移量 .flags = IORESOURCE_MEM, }, [1] = { .start = GPK1_CON, .end = GPK1_CON + GPK1_SIZE - 1, .flags = IORESOURCE_MEM, }, }; /*定義該方法,防止在卸載dev模塊時候報警告*/ static void platform_device_test_release(struct device *dev) { } struct platform_device led_pdev = { .name = "exynos4412_led", //用做匹配,系統會在/sys/bus/platform/devices/下建立該名稱文件夾 .id = -1, //通常取-1,以後會詳解 .num_resources = ARRAY_SIZE(led_res), //資源個數,此處爲2 .resource = led_res, //資源數組 .dev = { .release = platform_device_test_release, }, }; static int __init plat_led_pdev_init(void) { /*註冊platform_device*/ return platform_device_register(&led_pdev); //return platform_device_add(&led_pdev); } static void __exit plat_led_pdev_exit(void) { platform_device_unregister(&led_pdev); } module_init(plat_led_pdev_init); module_exit(plat_led_pdev_exit); MODULE_LICENSE("GPL");
# 沒有安裝模塊的時候沒有exynos4412_led設備 [root@iTOP-4412]# ls /sys/bus/platform/devices/ ... [root@iTOP-4412]# insmod plat_led_pdev.ko # 安裝模塊後出現exynos4412_led設備 [root@iTOP-4412]# ls /sys/bus/platform/devices/ 11800000.fimc exynos4412_led 11840000.jpeg-codec gpio-keys
/*註冊平臺驅動*/ int platform_driver_register(struct platform_driver *drv); /*註銷平臺驅動*/ void platform_driver_unregister(struct platform_driver *drv);
/*平臺列表,表示本驅動能夠支持的平臺*/ struct platform_device_id led_id_table[] = { {"exynos4412_led", 0x1111}, {"s5pv210_led", 0x2222}, {"s3c2410_led", 0x3333}, }; struct platform_driver led_pdrv = { .probe = led_pdrv_probe, .remove = led_pdrv_remove, .driver = { .name = "samsung led_drv", //若是沒有定義id_table的話能夠用於匹配device }, .id_table = led_id_table, };
#include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> int led_pdrv_probe(struct platform_device *pdev) { printk("------%s------\n", __FUNCTION__); return 0; } int led_pdrv_remove(struct platform_device *pdev) { printk("------%s------\n", __FUNCTION__); return 0; } /*平臺列表,表示本驅動能夠支持的平臺*/ struct platform_device_id led_id_table[] = { {"exynos4412_led", 0x1111}, {"s5pv210_led", 0x2222}, {"s3c2410_led", 0x3333}, }; struct platform_driver led_pdrv = { .probe = led_pdrv_probe, .remove = led_pdrv_remove, .driver = { .name = "samsung led_drv", //若是沒有定義id_table的話能夠用於匹配device }, .id_table = led_id_table, }; static int __init plat_led_pdrv_init(void) { /*註冊一個平臺驅動*/ platform_driver_register(&led_pdrv); return 0; } static void __exit plat_led_pdrv_exit(void) { platform_driver_unregister(&led_pdrv); } module_init(plat_led_pdrv_init); module_exit(plat_led_pdrv_exit); MODULE_LICENSE("GPL");
[root@iTOP-4412]# insmod plat_led_pdrv.ko [ 2071.656696] ------led_pdrv_probe------
platform_get_resource
函數便可,此處須要注意獲取方式,獲取方式參考如下描述/* 功能:獲取設備中的資源 參數: 參數1:從哪一個設備獲取資源 參數2:獲取的資源類型(內存資源或者中斷資源) 參數3:獲取同種類型的資源的第幾個(注意:同種類型資源) */ struct resource *platform_get_resource(struct platform_device * dev, unsigned int type, unsigned int num);
struct resource led_res[] = { [0] = { .start = GPL2_CON, .end = GPL2_CON + GPL2_SIZE - 1, //注意減去1個偏移量 .flags = IORESOURCE_MEM, }, [1] = { .start = GPK1_CON, .end = GPK1_CON + GPK1_SIZE - 1, .flags = IORESOURCE_MEM, }, [2] = { .start = IRQ_EINT(4), .end = IRQ_EINT(4), .flags = IORESOURCE_IRQ, }, }; /*獲取內存資源的第一個,即GPL2_CON*/ platform_get_resource(pdev, IORESOURCE_MEM, 0); /*獲取內存資源的第二個,即GPK1_CON*/ platform_get_resource(pdev, IORESOURCE_MEM, 1); /*獲取中斷資源的第一個,即IRQ_EINT(4)*/ platform_get_resource(pdev, IORESOURCE_IRQ, 0); platform_get_irq(pdev, 0);
int led_pdrv_probe(struct platform_device *pdev) { printk("------%s------\n", __FUNCTION__); samsung_led = (struct led_dev *)kmalloc(sizeof(struct led_dev), GFP_KERNEL); if(samsung_led == NULL) { printk("kmalloc error\n"); return -ENOMEM; } /*動態註冊設備號*/ samsung_led->dev_major = register_chrdev(0, "led_drv", &led_fops); /*建立設備節點*/ samsung_led->cls = class_create(THIS_MODULE, "led_new_cls"); samsung_led->dev = device_create(samsung_led->cls, NULL, MKDEV(samsung_led->dev_major, 0), NULL, "led0"); /*獲取設備中寄存器資源*/ samsung_led->res = platform_get_resource(pdev, IORESOURCE_MEM, 1); /*內存映射,硬件初始化,下述2種方法相似*/ //ioremap(samsung_led->res->start, (samsung_led->res->end) - (samsung_led->res->start) + 1); samsung_led->reg_base = ioremap(samsung_led->res->start, resource_size(samsung_led->res)); return 0; }
int led_pdrv_open (struct inode *inode, struct file *filp) { printk("------%s------\n", __FUNCTION__); /*配置GPL2_0爲輸出*/ writel((readl(samsung_led->reg_base) & (~((0xf)<<0))) | (0x1<<0), samsung_led->reg_base); return 0; }
samsung_led->reg_base + 4
)ssize_t led_pdrv_write (struct file *filp, const char __user *buf, size_t count, loff_t *fops) { int val, ret; ret = copy_from_user(&val, buf, count); if(ret > 0) { printk("copy_from_user error\n"); return -EFAULT; } if(val) { printk("on\n"); /*開燈1*/ writel(readl(samsung_led->reg_base + 4) | (0x1<<0), samsung_led->reg_base + 4); /*開燈2*/ //writel(readl(samsung_led->reg_base + 4) | (0x1<<1), samsung_led->reg_base + 4); } else { printk("off\n"); /*關燈1*/ writel(readl(samsung_led->reg_base + 4) & ~(0x1<<0), samsung_led->reg_base + 4); /*關燈2*/ //writel(readl(samsung_led->reg_base + 4) & ~(0x1<<1), samsung_led->reg_base + 4); } return 0; }
#include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> #define GPL_BASE 0x11000000 #define GPL2_CON (GPL_BASE + 0x0100) #define GPL2_SIZE 24 #define GPK_BASE 0x11000000 #define GPK1_CON (GPK_BASE + 0x0060) #define GPK1_SIZE 24 /*定義爲一個數組,由於一個設備中可能有多個資源*/ struct resource led_res[] = { [0] = { .start = GPL2_CON, .end = GPL2_CON + GPL2_SIZE - 1, //注意減去1個偏移量 .flags = IORESOURCE_MEM, }, [1] = { .start = GPK1_CON, .end = GPK1_CON + GPK1_SIZE - 1, .flags = IORESOURCE_MEM, }, }; static void platform_device_test_release(struct device *dev) { } struct platform_device led_pdev = { .name = "exynos4412_led", //用做匹配,系統會在/sys/bus/platform/devices/下建立該名稱文件夾 .id = -1, //通常取-1,以後會詳解 .num_resources = ARRAY_SIZE(led_res), //資源個數,此處爲2 .resource = led_res, //資源數組 .dev = { .release = platform_device_test_release, }, }; static int __init plat_led_pdev_init(void) { /*註冊platform_device*/ return platform_device_register(&led_pdev); //return platform_device_add(&led_pdev); } static void __exit plat_led_pdev_exit(void) { platform_device_unregister(&led_pdev); } module_init(plat_led_pdev_init); module_exit(plat_led_pdev_exit); MODULE_LICENSE("GPL");
#include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/uaccess.h> #include <asm/io.h> #include <linux/slab.h> #include <linux/fs.h> #include <linux/device.h> #include <linux/ioport.h> /*設計全局設備對象,方便後續調用*/ struct led_dev{ int dev_major; //主設備號 struct class *cls; struct device *dev; struct resource *res; //獲取到的內存資源 void *reg_base; //保存轉換後的虛擬地址 }; struct led_dev *samsung_led; ssize_t led_pdrv_write (struct file *filp, const char __user *buf, size_t count, loff_t *fops) { int val, ret; ret = copy_from_user(&val, buf, count); if(ret > 0) { printk("copy_from_user error\n"); return -EFAULT; } if(val) { printk("on\n"); /*開燈1*/ writel(readl(samsung_led->reg_base + 4) | (0x1<<0), samsung_led->reg_base + 4); /*開燈2*/ //writel(readl(samsung_led->reg_base + 4) | (0x1<<1), samsung_led->reg_base + 4); } else { printk("off\n"); /*關燈1*/ writel(readl(samsung_led->reg_base + 4) & ~(0x1<<0), samsung_led->reg_base + 4); /*關燈2*/ //writel(readl(samsung_led->reg_base + 4) & ~(0x1<<1), samsung_led->reg_base + 4); } return 0; } int led_pdrv_open (struct inode *inode, struct file *filp) { printk("------%s------\n", __FUNCTION__); /*配置寄存器爲輸出*/ writel((readl(samsung_led->reg_base) & (~((0xf)<<0))) | (0x1<<0), samsung_led->reg_base); return 0; } int led_pdrv_close (struct inode *inode, struct file *filp) { printk("------%s------\n", __FUNCTION__); return 0; } struct file_operations led_fops = { .open = led_pdrv_open, .release = led_pdrv_close, .write = led_pdrv_write, }; int led_pdrv_probe(struct platform_device *pdev) { printk("------%s------\n", __FUNCTION__); samsung_led = (struct led_dev *)kmalloc(sizeof(struct led_dev), GFP_KERNEL); if(samsung_led == NULL) { printk("kmalloc error\n"); return -ENOMEM; } /*動態註冊設備號*/ samsung_led->dev_major = register_chrdev(0, "led_drv", &led_fops); /*建立設備節點*/ samsung_led->cls = class_create(THIS_MODULE, "led_new_cls"); samsung_led->dev = device_create(samsung_led->cls, NULL, MKDEV(samsung_led->dev_major, 0), NULL, "led0"); /*獲取設備中寄存器資源*/ samsung_led->res = platform_get_resource(pdev, IORESOURCE_MEM, 1); /*內存映射,硬件初始化*/ //ioremap(samsung_led->res->start, (samsung_led->res->end) - (samsung_led->res->start) + 1); samsung_led->reg_base = ioremap(samsung_led->res->start, resource_size(samsung_led->res)); return 0; } int led_pdrv_remove(struct platform_device *pdev) { printk("------%s------\n", __FUNCTION__); iounmap(samsung_led->reg_base); device_destroy(samsung_led->cls, MKDEV(samsung_led->dev_major, 0)); class_destroy(samsung_led->cls); unregister_chrdev(samsung_led->dev_major, "led_drv"); kfree(samsung_led); return 0; } /*平臺列表,表示本驅動能夠支持的平臺*/ struct platform_device_id led_id_table[] = { {"exynos4412_led", 0x1111}, {"s5pv210_led", 0x2222}, {"s3c2410_led", 0x3333}, }; struct platform_driver led_pdrv = { .probe = led_pdrv_probe, .remove = led_pdrv_remove, .driver = { .name = "samsung led_drv", //若是沒有定義id_table的話能夠用於匹配device }, .id_table = led_id_table, }; static int __init plat_led_pdrv_init(void) { /*註冊一個平臺驅動*/ platform_driver_register(&led_pdrv); return 0; } static void __exit plat_led_pdrv_exit(void) { platform_driver_unregister(&led_pdrv); } module_init(plat_led_pdrv_init); module_exit(plat_led_pdrv_exit); MODULE_LICENSE("GPL");
#include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <sys/stat.h> #include <sys/types.h> #include <fcntl.h> int main(int argc, char *argv[]) { int fd; int led_state = 0; fd = open("/dev/led0", O_RDWR); if(fd < 0) { printf("open error\n"); return -1; } while(1) { led_state = 1; write(fd, &led_state, sizeof(led_state)); sleep(1); led_state = 0; write(fd, &led_state, sizeof(led_state)); sleep(1); } return 0; }