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; }