1.驱动模型
platform驱动模型由bus、device、driver组成,bus已经是由内核做好的,我们使用这个模型主要是需要将device和driver添加进去,并让其能够正确匹配进行工作,使用驱动模型是为了让程序运行得更加稳定,将驱动程序分开编写,也便于维护和修改。
流程主要是将不同的设备的driver和device分别添加进链表,再用设备名去匹配,如果有相同的设备名就连接起来,然后调用相关操作,完成驱动工作。
/*driver注册过程*/
int platform_driver_register(struct platform_driver *drv)
{
/*设置driver参数*/
drv->driver.bus = &platform_bus_type;
if (drv->probe)
drv->driver.probe = platform_drv_probe;
if (drv->remove)
drv->driver.remove = platform_drv_remove;
if (drv->shutdown)
drv->driver.shutdown = platform_drv_shutdown;
return driver_register(&drv->driver); //将设置后的结构体进行注册
}
int driver_register(struct device_driver *drv)
{
.......
other = driver_find(drv->name, drv->bus); //用设备名判断是否有重复的设备,有就不注册
if (other) {
printk(KERN_ERR "Error: Driver '%s' is already registered, "
"aborting...\n", drv->name);
return -EBUSY;
}
ret = bus_add_driver(drv); //将驱动加入BUS,实质是将驱动加入链表
ret = driver_add_groups(drv, drv->groups); //创建sysfs接口,使得上层可以与硬件进行交互
.......
}
int bus_add_driver(struct device_driver *drv)
{
......
/*重要操作主要是这部分,在这里会将驱动添加进入链表
* 并且还会创建sysfs接口,使得上层可以与硬件进行交互
*/
klist_init(&priv->klist_devices, NULL, NULL);
priv->driver = drv;
drv->p = priv;
priv->kobj.kset = bus->p->drivers_kset;
error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
"%s", drv->name);
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
module_add_driver(drv->owner, drv);
error = driver_create_file(drv, &driver_attr_uevent);
error = driver_add_attrs(bus, drv);
kobject_uevent(&priv->kobj, KOBJ_ADD);
return 0;
}
/*device注册过程*/
int platform_device_register(struct platform_device *pdev)
{
device_initialize(&pdev->dev);
arch_setup_pdev_archdata(pdev);
return platform_device_add(pdev);
}
int platform_device_add(struct platform_device *pdev)
{
.......
/*设置设备名,用于匹配*/
if (pdev->id != -1)
dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id);
else
dev_set_name(&pdev->dev, "%s", pdev->name);
/*将设置的资源、name等内容进行设置*/
for (i = 0; i < pdev->num_resources; i++) {
struct resource *p, *r = &pdev->resource[i];
if (r->name == NULL)
r->name = dev_name(&pdev->dev);
p = r->parent;
if (!p) {
if (resource_type(r) == IORESOURCE_MEM)
p = &iomem_resource;
else if (resource_type(r) == IORESOURCE_IO)
p = &ioport_resource;
}
/*关键步骤就是add,本质是将设置好的结构体加入到链表中*/
ret = device_add(&pdev->dev);
if (ret == 0)
return ret;
return ret;
}
在将driver和device加入链表后,程序运行时会通过设备来进行匹配
在platform_match函数实现中最后都会调用strcmp来对设备名进行配对
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);
if (of_driver_match_device(dev, drv))
return 1;
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
return (strcmp(pdev->name, drv->name) == 0);
}
2.实际操作
匹配完成后就可以使程序正常运行,至于其他的操作和不用platform驱动模型的编写方式类似,也是通过file_operations结构体进行设备注册,然后操作硬件完成相关功能,以下为用platform驱动模型编写的LED驱动
----------------device部分------------------
#include <linux/module.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/miscdevice.h>
#include <linux/irq.h>
#include <linux/io.h>
#include <mach/devices.h>
#include <linux/wait.h>
#include <linux/gpio.h>
#include <linux/input.h>
#define LED_DEV_NAME "led_dev"
#define LED_PHY_START 0xC001E000
#define LED_PHY_END LED_PHY_START + 0x1000 - 1
#define LED_PHY_NUM 13
void led_release(struct device *led_dev)
{
printk(KERN_WARNING "led_dev_release\n");
}
/*创建资源结构体,将硬件相关的资源添加,如IO、中断资源*/
static struct resource led_resource[] = {
[0] = {
.start = LED_PHY_START,
.end = LED_PHY_END,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = LED_PHY_NUM,
.end = LED_PHY_NUM,
.flags = IORESOURCE_IRQ,
},
};
static struct platform_device led_dev = {
.name = LED_DEV_NAME,
.num_resources = ARRAY_SIZE(led_resource),
.resource = led_resource,
.dev = {
.release = led_release,
},
};
static int __init led_dev_init(void)
{
return platform_device_register(&led_dev);
}
static void __exit led_dev_exit(void)
{
platform_device_unregister(&led_dev);
}
module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("6818 led dev");
----------------driver部分------------------
#include <linux/module.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/miscdevice.h>
#include <linux/irq.h>
#include <linux/io.h>
#include <linux/device.h>
#include <linux/wait.h>
#include <linux/gpio.h>
#include <linux/input.h>
#include <linux/cdev.h>
#include <mach/devices.h>
#define LED_DEV_NAME "led_dev"
unsigned long int led_major = 0;
unsigned long int led_minor = 0;
static dev_t led_dev_num = 0;
static struct cdev led_dev_cdev;
static struct class *led_dev_class;
static struct device *led_dev_device;
static struct resource *led_dev_mem_res;
static struct resource *led_dev_irq_res;
static struct resource *led_dev_request_res;
static void __iomem *io_map;
static void led_set(void __iomem *led_reg, int led_num)
{
int temp;
printk(KERN_WARNING "led_set\n");
switch(led_num){
case 7:
temp = readl(led_reg+0x20);
temp &= ~(0x3 << 26);
writel(temp, led_reg+0x20);
temp = readl(led_reg+0x04);
temp |= (0x1 << 13);
writel(temp, led_reg+0x04);
temp = readl(led_reg+0x00);
temp &= ~(0x1 << 13);
writel(temp, led_reg+0x00);
break;
case 8:
break;
case 9:
break;
case 10:
break;
default:
break;
}
}
static int led_open(struct inode *inode, struct file *filp)
{
printk(KERN_WARNING "led_open\n");
led_set(io_map, 7);
return 0;
}
static ssize_t led_read(struct file *filp, char __user *usr, size_t size, loff_t *offset)
{
return 0;
}
static ssize_t led_write(struct file *filp, const char __user *usr, size_t size, loff_t *offset)
{
return 0;
}
static int led_release(struct inode *inode, struct file *filp)
{
int temp;
printk(KERN_WARNING "led_release\n");
temp = readl(io_map+0x00);
temp |= (0x1 << 13);
writel(temp, io_map+0x00);
return 0;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
static int led_probe(struct platform_device *led_dev)
{
int ret = 0;
printk(KERN_WARNING "led_probe\n");
led_dev_num = MKDEV(led_major, led_minor);
if(led_major)
ret = register_chrdev_region(led_dev_num, 1, LED_DEV_NAME);
else
ret = alloc_chrdev_region(&led_dev_num, 0, 1, LED_DEV_NAME);
cdev_init(&led_dev_cdev, &fops);
ret = cdev_add(&led_dev_cdev, led_dev_num, 1);
led_dev_class = class_create(THIS_MODULE, LED_DEV_NAME);
led_dev_device = device_create(led_dev_class, NULL, led_dev_num, NULL, LED_DEV_NAME);
/*通过device的资源结构体获取硬件配置,此为获取IO资源*/
led_dev_mem_res = platform_get_resource(led_dev, IORESOURCE_MEM, 0);
/*通过device的资源结构体获取硬件配置,此为获取中断资源*/
led_dev_irq_res = platform_get_resource(led_dev, IORESOURCE_IRQ, 0);
/*将获得的资源申请出来以便使用*/
led_dev_request_res = request_mem_region(led_dev_mem_res->start, led_dev_mem_res->end - led_dev_mem_res->start + 1, "led7");
/*将申请到的资源进行映射,因为不能直接操作硬件地址,需要映射为虚拟地址后再操作*/
io_map = ioremap(led_dev_mem_res->start, led_dev_mem_res->end - led_dev_mem_res->start + 1);
return 0;
}
int led_remove(struct platform_device *led_dev)
{
printk(KERN_WARNING "led_remove\n");
release_mem_region(led_dev_mem_res->start, led_dev_mem_res->end - led_dev_mem_res->start + 1);
device_destroy(led_dev_class, led_dev_num);
class_destroy(led_dev_class);
cdev_del(&led_dev_cdev);
unregister_chrdev_region(led_dev_num, 1);
return 0;
}
static struct platform_driver led_drv = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = LED_DEV_NAME,
},
};
static int __init led_drv_init(void)
{
return platform_driver_register(&led_drv);
}
static void __exit led_drv_exit(void)
{
platform_driver_unregister(&led_drv);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("6818 led drv");
使用驱动模型编写的代码比较稳定,在硬件有变动时,可以只考虑修改device部分的代码,而driver部分也只需要修改某些和硬件相关的部分,基本框架是不用修改的,这样会减少代码的复杂度,也便于修改且能保持足够的稳定