1.设备驱动模型
1.1由来
- 在之前的字符设备驱动编程模型里面主要有以下几步
1.首先要实现入口函数
xxx_init()
和卸载函数xxx_exit()
2.申请设备号
register_chrdev
3.创建设备节点,如
class_create
,device_create
4.硬件部分初始化,如io资源映射
ioremap
,中断注册等5.构建
file_operation
结构6.实现
xxx_open
,xxx_read
,xxx_write
等函数
- 这样写看似没毛病,但是可移植性较差.如果在写完一个驱动的情况下又要写另外一个驱动,明显按照这种写法又得推倒重来
- 举个例子,假如要写手机摄像头驱动,使用上述方法可写一套前摄驱动和一套后摄驱动,但实际上我们发现除了步骤4不一样,其他的步骤前后摄都差不多,因此上述方法在代码复用性上有待提升,换种说法,就是为了省事,少加班
- 因此隆重推出linux设备驱动模型
1.2概念
- 该模型将驱动划分为3个部分:Device(设备对象),Driver(设备驱动对象),Bus(总线对象)
- 其中可以把Device想象为车,Driver想象为老司机,每辆车的具体参数不一样,如功耗,百公里加速,油耗等等.但是对于老司机而言其驾驶方式都是一样的.其驾驶步骤都差不多
- 在驱动里面,Device也就是针对不同硬件,其对于的操作地址或者中断不一样,但是对于Driver部分而言都是差不多的,都可以使用
ioremap
函数映射地址,之后通过readl
或者writel
函数操作地址 - 因此在写驱动代码的时候就需要将Device与Driver相分类,前者的核心是存异,后者的核心是求同.因此存异的代价是数量繁多,针对不同设备就需要不同的代码,但是其代码量是比较少的.而求同的代价是代码量多,但是可做到一劳永逸
- 既然在写代码的时候将Device与Driver相分离了,但是到了最后还是会将二者组合起来.怎么组合呢?故引入Bus(总线对象)来将Device与Driver进行组合
- 我们将设备信息用Device对象描述好(封装为一个节点),再将设备操作方法用Driver对象描述好(封装为一个节点),之后将二者交给Bus总线(设备注册),Bus总线则根据名称将Device与Driver相匹配,进而完成老司机开车的壮举,在内核中通过链表管理
- 如果Device与Driver匹配成功,Bus将调用proke方法
1.3结合实际分析
- 先看一张图
在
/sys
下面有总线文件夹,设备文件夹和类文件夹.总线文件夹保存总线信息,如i2c总线,usb总线;
设备文件夹下面保存设备信息,如usb设备
在类文件夹下保存事件信息
在某一总线下面又包含设备和驱动,驱动即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
1.4构建自己的总线
- 系统默认创建的总线在
/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
#include
#include
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]#
1.5device与driver注册
1.5.1概述
- 上述过程实现了总线的创建,但是其内部device与driver为空,需要我们自己去创建和注册
- 由于在内核内部会自动帮我们完成device与driver的匹配,所以二者的注册顺序不论先后
1.5.2device注册与卸载
- 内核里面有一个device设备对象,用于描述设备信息,包括地址,中断号,自定义数据等
- device对象属性如下
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; //自定义数据,可指向任何数据类型的数据
};
- device注册和注销的方法
int device_register(struct device * dev); //小于0代表设备注册失败
int device_unregister(struct device * dev);
- device驱动代码
#include
#include
#include
/*导入外部结构体变量,使得编译通过*/
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
#include
#include
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]#
1.5.3driver注册与卸载
- 同device一样,driver也有一个设备驱动对象,他主要用于描述设备驱动的方法(代码逻辑,即如何操作地址中断等)
- driver设备驱动对象属性如下
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;
};
- driver注册和注销的方法
int driver_register(struct device_driver * drv); //小于0代表驱动注册失败
void driver_unregister(struct device_driver * drv);
- driver驱动代码如下
#include
#include
#include
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");
- 同时要修改Makefile,让其能够同时编译
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
- 执行结果,可见在安装完dev与drv驱动后在
/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]#
1.5.4手动匹配
- 在1.5.3中说到,device与driver匹配之后,会调用driver中的
probe
方法,但是在上述代码中driver的名字为mydrv(.name = "mydrv",
),device的名字为mydev(.init_name = "mydev",
).可见二者名字不同,那么自然无法匹配 - 如何完成匹配,并且调用
probe
方法呢?此时需要我们自己在bus中实现匹配的逻辑过程,并且将device与driver中的名字改为一样,即总线作用之一就是匹配device和driver - 我们可以通过实现bus对象中的match方法完成匹配
//注意:如果匹配成功,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
- kobject对象
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;
};
1.5.5完整总线框架
- bus总线
#include
#include
#include
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");
- device设备
#include
#include
#include
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");
- driver驱动
#include
#include
#include
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");
- 结果如下,可以看到,当注册完device与driver后会调用bus中的match方法匹配,匹配成功后,系统会自动调用driver对象中的probe方法
[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]#
1.6driver驱动device
1.6.1概述
- 当driver与device匹配成功后,会调用driver中的probe方法,其中probe方法如下,其参数为一个device对象.换句话而言,我们在创建总线后会注册driver和device,当二者匹配成功后系统会将device作为参数告诉driver,以便让driver进行操作.再进一步而言,我们在device里面写好设备信息然后传给driver去使用
int (*probe) (struct device *dev);
1.6.2自定义device设备属性
- 在1.5.2里面说到,在device对象中有一个属性为自定义数据类型,如下,因为该参数为一个void类型指针,故可以接收任何数据类型,包括结构体(对象)
void *platform_data; //自定义数据,可指向任何数据类型的数据
- 为了方便device创建自定义对象,driver接收自定义对象,在此我创建一个头文件
dev_info.h
来声明一个类,用于保存device设备属性
#ifndef __DEV_INFO_H__
#define __DEV_INFO_H__
/*自定义一个数据类,描述设备特性*/
struct mydev_desc {
char *name;
int irqno;
unsigned long addr;
};
#endif
1.6.3device设备添加自定义属性
- 我们可创建自定义对象,在对象中描述设备属性,之后将对象作为platform_data参数传进device结构体中,如下
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,
};
1.6.4driver接收设备属性
- 首先创建一个mydev_desc类型的对象,用于接收probe方法中device参数中的设备属性,如下
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;
}
1.6.5完整代码
- 描述设备属性的头文件
#ifndef __DEV_INFO_H__
#define __DEV_INFO_H__
/*自定义一个数据类,描述设备特性*/
struct mydev_desc {
char *name;
int irqno;
unsigned long addr;
};
#endif
- 总线代码
#include
#include
#include
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");
- device设备部分代码
#include
#include
#include
#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");
- driver驱动部分代码
#include
#include
#include
#include
#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");
- 最终实现效果,当匹配成功后,driver驱动成功拿到了device设备信息
[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]#
2.平台总线模型
2.1由来
- 平台总线模型用于平台升级
- 假设某个硬件平台升级了,例如三星,由2440升级到4412,很明显后者处理能力要比前者强.但是后者的诞生并不是完全颠覆前者.而是继承了前者的一些特性,例如串口,IIC,GPIO等外设的控制方式等.在控制层面二者基本相同.但是后者由于内存容量的提升,那么其寻址范围要比前者更大,即寄存器地址不一样
- 如果不用平台总线的话,对于soc升级的时候,对于相似的设备驱动,需要编写很多重复代码.因此引入平台总线模型,将设备与驱动分离,故在升级的时候只需要更改设备信息即可
- 最终实现一个驱动驱动多个平台的设备
2.2概念
- 平台总线(platform bus)三元素:bus(总线),driver(驱动),device(设备)
2.2.1bus
- 在平台总线里面,bus不需要自己创建,在开机的时候由系统创建.其会在
/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,
};
- bus匹配规则
1.优先匹配driver中的id_table,在其中包含了支持不同的平台的名字
2.直接匹配driver中的名字和device中的名字,与1.5类似
2.2.2device
- 平台设备对象描述如下,直接继承了device类,但是又增加了一些私有属性,总体偏向于记录设备属性
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;
};
2.2.3driver
- 平台驱动对象描述如下,继承了driver父类并增加了私有方法,总体偏向于实现某种方法.
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;
};
2.3平台总线匹配原理
- 我们可以研究以下平台设备与平台驱动之间是如何匹配的
- 由2.2部分可看出,platform_device与platform_driver类均继承了自己的父类,分别为device与driver.并且实际注册到平台总线中的是其父类(device与driver)
- 那么在注册的时候是注册的父类,但是在比较的时候却是子类,此处就需要通过父类来推测子类
- 在linux内核中可通过
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);
}
2.4编写platform_device
- 实操:此处使用平台总线完成点灯操作
- 我们需要注册一个platform_device对象并定义好相关的资源:地址,中断...
- 注册API为
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);
- 在platform_device对象中我们需要填写硬件资源信息,其保存在resource结构体内,注意:一般某个设备可能包含多个资源,因此可以通过使用结构体数组来描述多个资源信息
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;
};
- 知道大概原理后我们查看开发板原理图,其中有2个LED灯可控,如图.其中我们可以发现其使用了2个引脚控制,一个为
KP_COL0
,一个为VDD50_EN
,此可我们需要跳转到核心板原理图去寻找两个引脚
- LED2与LED3分别对应芯片的
GPL2_0
和GPK1_1
引脚,之后再去查找芯片数据手册
- 首先查找GPL2组寄存器,发现其对应配置寄存器
GPL2CON
描述如下,可以看出其基地址为0x1100 0000(#define GPL_BASE 0x11000000
),偏移地址为0x0100,所以描述资源的起始地址可设置为0x1100 0100(#define GPL2_CON GPL_BASE + 0x0100
),而资源的终止地址则为起始地址加上GPL2系列地址的总长度(24Byte)
- GPL2系列寄存器如下,可以看到在GPL2系列中一共有6个寄存器,其中每个寄存器大小为4字节,因此GPL2系列大小为24字节(
#define GPL2_SIZE 24
),因此结束地址为GPL2_CON + GPL2_SIZE - 1
.特别注意:最后要减去1,假设GPL2CON编号为1,那么GPL2PUDPDN编号为6,即1 + 6 - 1 = 6,所以要减去1.
- 对于LED3的GPK1系列就不必多说了,最终填写的资源描述如下
#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,
},
};
- 最后在platform_device结构体中指定资源地址和大小即可
struct platform_device led_pdev = {
.name = "exynos4412_led", //用作匹配
.id = -1, //一般取-1,之后会详解
.num_resources = ARRAY_SIZE(led_res), //资源个数,此处为2
.resource = led_res, //资源数组
};
- 完整platform_device代码
#include
#include
#include
#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
2.5编写platform_driver
- 我们需要注册一个platform_driver对象并实现相关操作代码
/*注册平台驱动*/
int platform_driver_register(struct platform_driver *drv);
/*注销平台驱动*/
void platform_driver_unregister(struct platform_driver *drv);
- 如果该驱动与设备匹配成功则会调用驱动中的probe方法,因此我们会在probe方法里面对硬件进行操作,例如注册设备号,注册file_operation为用户提供设备标识,同时提供文件操作的接口,如read,write...
- 填写platform_driver对象中的方法,此处需要注意id_table的填写,如果platform_driver中有id_table,则优先匹配platform_driver中的设备,否则匹配drvice中的名字(samsung led_drv),详见2.3
/*平台列表,表示本驱动可以支持的平台*/
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,
};
- platform_driver完整代码
#include
#include
#include
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");
- 执行结果,可以看到platform_device与platform_driver匹配成功,进而执行了led_pdrv_probe方法
[root@iTOP-4412]# insmod plat_led_pdrv.ko
[ 2071.656696] ------led_pdrv_probe------
2.6实现probe方法
- 上述代码成功注册设备并与驱动绑定成功,那么接下来就需要在probe中实现具体的操作逻辑,其操作步骤与之前编写字符设备驱动步骤类似:动态创建主设备号,创建设备节点,硬件初始化(将物理地址映射为虚拟地址),之后再获取platform_device资源即可
- 获取platform_device资源可通过
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);
- 最终我们实现的probe方法如下
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;
}
- 之后当我们调用open函数的时候就将GPL2_0配置为输出模式,参考数据手册,我们需要将GPL2CON(32bit)寄存器的低4位(bit0~3)设置为0x1即可,设置代码如下,即先将低4位置0,再置1即可
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;
}
- 之后在根据应用层传入的参数判断是否开关灯,此时可以看GPL2DAT寄存器,其只有8位,对于8个GPIO,对于位写0则输出0,写1则输出1.此处为GPL2_0,对应第0位,配置代码如下,需要注意的是GPL2DAT寄存器相较于基地址(GPL2CON)寄存器地址偏移了4字节,所以需要手动偏移4字节(
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;
}
2.7完整全部代码
- platform_device代码
#include
#include
#include
#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");
- platform_driver代码
#include
#include
#include
#include
#include
#include
#include
#include
#include
/*设计全局设备对象,方便后续调用*/
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
#include
#include
#include
#include
#include
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;
}
- 现象,在安装对应模块之后,发现灯会持续亮灭