使用platform虚拟总线驱动按键

 Linux下按键驱动相对来说,编程简单。本例程使用了platform虚拟总线进行编程,主要是为了加深对总线,设备和驱动的了解,程序不是本人所写。只希望通过写下表笔记,梳理下思路。
 
    在设备驱动程序中经常会见到和platform相关的字段,分布在驱动程序的多个角落,这也是2.6内核中比较重要的一种机制,把它的原理弄懂了,对以后分析驱动程序很有帮助,下面简单介绍一下:

在linux2.6设备模型中,关心总线,设备,驱动这三个实体,总线将设备和驱动绑定,在系统每注册一个设备的时候,会寻找与之匹配的驱动。相反,在系统每注册一个驱动的时候,寻找与之匹配的设备,匹配是由总线来完成的。

一个现实的Linux 设备和驱动通常都需要挂接在一种总线上,对于本身依附于PCI、USB、I2C、SPI 等的设备而言,这自然不是问题,但是在嵌入式系统里面,SoC 系统中集成的独立的外设控制器、挂接在SoC 内存空间的外设等确不依附于此类总线。基于这一背景,Linux 发明了一种虚拟的总线,称为platform 总线。

  

        
首先,需要注册设备:
 static int __init platform_init(void)
{
    s3c_buttons = platform_device_alloc("mini2440-buttons",-1);
    platform_device_add_resources(s3c_buttons,&s3c_buttons_resource,7);
    /*平台设备的注册*/
    platform_device_add(s3c_buttons);

}
s3c_buttons = platform_device_alloc(const char name,int id);  
为struct Platform_device设备分配空间.
struct platform_device *platform_device_alloc(const char *name, int id)
{
 struct platform_object *pa;
 pa = kzalloc(sizeof(struct platform_object) + strlen(name), GFP_KERNEL);
 if (pa) {
  strcpy(pa->name, name);
  pa->pdev.name = pa->name;
  pa->pdev.id = id;
  device_initialize(&pa->pdev.dev);
  pa->pdev.dev.release = platform_device_release;
 }
 return pa ? &pa->pdev : NULL;
}
 
关于platform_device
 
struct platform_device {
const char *name;
u32 id;
struct device dev;
u32 num_resources;
struct resource *resource;

};

关于platform_object:

struct platform_object {
 struct platform_device pdev;
 char name[1];
};

 

关于resource:
struct resource{
     resource_size_t start;    //资源的起始物理地址
     resource_size_t end;     //资源的终止物理地址
     const char* name;          //资源名称
     unsigned long flags;       //资源的类型,比如MEM,IO,IRQ
     struct resource *parent,*sibling,*child;  //资源链表指针
}
/*pdev:用于添加资源到由platform_device_alloc分配的platform device
int platform_device_add_resources(struct platform_device *pdev,
 res:需要分配到device的资源集
 num:资源数
*/
struct resource *res,unsigned int num)
{
     struct resource *r;
     r = kmalloc(sizeof(struct resource) * num, GFP_KERNEL);
     if (r) {
      memcpy(r, res, sizeof(struct resource) * num);
      pdev->resource = r;
      pdev->num_resources = num;
 }
 return r ? 0 : -ENOMEM; 
}
 
/*
platform_device_register的第二部分
*/
int platform_device_add(struct platform_device *pdev)
{
 int i, ret = 0;
 if (!pdev)
  return -EINVAL;
 if (!pdev->dev.parent)
  pdev->dev.parent = &platform_bus;
 pdev->dev.bus = &platform_bus_type;
 if (pdev->id != -1)
  dev_set_name(&pdev->dev, "%s.%d", pdev->name,  pdev->id);
 else
  dev_set_name(&pdev->dev, pdev->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;
  }
  if (p && insert_resource(p, r)) {
   printk(KERN_ERR
          "%s: failed to claim resource %d\n",
          dev_name(&pdev->dev), i);
   ret = -EBUSY;
   goto failed;
  }
 }
 pr_debug("Registering platform device '%s'. Parent at %s\n",
   dev_name(&pdev->dev), dev_name(pdev->dev.parent));
 ret = device_add(&pdev->dev);
 if (ret == 0)
  return ret;
 failed:
 while (--i >= 0) {
  struct resource *r = &pdev->resource[i];
  unsigned long type = resource_type(r);
  if (type == IORESOURCE_MEM || type == IORESOURCE_IO)
   release_resource(r);
 }
 return ret;
调用此函数后,将在platform总线的devices下出现“mini2440-buttons”设备文件
 
关于资源:
/*平台资源的定义*/
static struct resource s3c_buttons_resource[] = {
 [0]={
  .start = S3C24XX_PA_GPIO,
  .end   = S3C24XX_PA_GPIO + S3C24XX_SZ_GPIO - 1,
  .flags = IORESOURCE_MEM,
 },
 
 [1]={
  .start = IRQ_EINT8,
  .end   = IRQ_EINT8,
  .flags = IORESOURCE_IRQ,
 },
 [2]={
  .start = IRQ_EINT11,
  .end   = IRQ_EINT11,
  .flags = IORESOURCE_IRQ,
 },
 [3]={
  .start = IRQ_EINT13,
  .end   = IRQ_EINT13,
  .flags = IORESOURCE_IRQ,
 },
 [4]={
  .start = IRQ_EINT14,
  .end   = IRQ_EINT14,
  .flags = IORESOURCE_IRQ,
 },
 [5]={
  .start = IRQ_EINT15,
  .end   = IRQ_EINT15,
  .flags = IORESOURCE_IRQ,
 },
 [6]={
  .start = IRQ_EINT19,
  .end   = IRQ_EINT19,
  .flags = IORESOURCE_IRQ,
 }
};
static struct platform_device *s3c_buttons;
 
 
 
介绍完设备,现在介绍驱动程序:
platform_driver_register(&mini2440buttons_driver)
 
int platform_driver_register(struct platform_driver *drv)
{
 drv->driver.bus = &platform_bus_type;  //将platform驱动注册到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;
 if (drv->suspend)
  drv->driver.suspend = platform_drv_suspend;
 if (drv->resume)
  drv->driver.resume = platform_drv_resume;
 return driver_register(&drv->driver);   注册驱动
}
 
关于driver_register:
int driver_register(struct device_driver *drv)
{
 int ret;
 struct device_driver *other;
 if ((drv->bus->probe && drv->probe) ||
     (drv->bus->remove && drv->remove) ||
     (drv->bus->shutdown && drv->shutdown))
  printk(KERN_WARNING "Driver '%s' needs updating - please use "
   "bus_type methods\n", drv->name);
 other = driver_find(drv->name, drv->bus);
 if (other) {
  put_driver(other);
  printk(KERN_ERR "Error: Driver '%s' is already registered, "
   "aborting...\n", drv->name);
  return -EEXIST;
 }
 ret = bus_add_driver(drv);
 if (ret)
  return ret;
 ret = driver_add_groups(drv, drv->groups);
 if (ret)
  bus_remove_driver(drv);
 return ret;
}
 
回头看mini2440buttons_driver:
 
static struct platform_driver mini2440buttons_driver = {
 .probe  = mini2440_buttons_probe,
 .remove  = mini2440_buttons_remove,
 .driver  = {
  .owner = THIS_MODULE,
  .name = "mini2440-buttons",  //与设备名相同
 },
};
 
当匹配了driver和device后,platform_driver通过probe检测platform总线上的设备:
static int mini2440_buttons_probe(struct platform_device *pdev)
{
 struct resource *res;
 struct device *dev;
 int ret;
 int size;
 int i;
 
 printk("probe:%s\n", __func__);
 dev = &pdev->dev;
 buttons_dev = &pdev->dev;
 
 /*平台资源获取*/
 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 if (res == NULL) {
  dev_err(dev, "no memory resource specified\n");
  return -ENOENT;
 }
 
 size = (res->end - res->start) + 1;
 buttons_mem = request_mem_region(res->start, size, pdev->name);
 if (buttons_mem == NULL) {
  dev_err(dev, "failed to get memory region\n");
  ret = -ENOENT;
  goto err_req;
 }
 
 buttons_base = ioremap(res->start, size);
 if (buttons_base == NULL) {
  dev_err(dev, "failed to ioremap() region\n");
  ret = -EINVAL;
  goto err_req;
 }
 printk(KERN_DEBUG"probe: mapped buttons_base=%p\n", buttons_base);
 /*get irq number*/
  for(i=0; i<6; i++){
  buttons_irq = platform_get_resource(pdev,IORESOURCE_IRQ,i);
  if(buttons_irq == NULL){
     dev_err(dev,"no irq resource specified\n");
    ret = -ENOENT;
    goto err_map;
 }
 button_irqs[i] = buttons_irq->start;
 //printk("button_irqs[%d]=%d\n",i,button_irqs[i]);  
 }
 ret = misc_register(&mini2440_miscdev);
  
 return 0;
 err_map:
 iounmap(buttons_base);
 err_req:
 release_resource(buttons_mem);
 kfree(buttons_mem);
 return ret;
}
 
关于函数platform_get_resource(struct platform_device *dev,unsigned int type,
unsigned int num)
dev:资源所属的设备
type:获取的资源类型 IORESOURCE_IRQ.....
num:获取资源数目
 
关于request_mem_region()
request_mem_region() -- 将起始地址为[start, start+n-1]的资源插入根资源iomem_resource中。参数start是I/O内存资源的起始物理地址(是CPU的RAM物理地址空间中的物理地址),参数n指定I/O内存资源的大小。

#define request_mem_region(start, n, name) \
        __request_region(&iomem_resource, (start), (n), (name))

注: 调用request_mem_region()不是必须的,但是建议使用。该函数的任务是检查申请的资源是否可用,如果可用则申请成功,并标志为已经使用,其他驱动想再申请该资源时就会失败。
关于ioremap:
驱动程序并不能直接通过物理地址访问I/O内存资源,而必须将它们映射到核心虚地址空间内(通过页表),然后才能根据映射所得到的核心虚地址范围,通过访内指令访问这些I/O内存资源。Linux在io.h头文件中声明了函数ioremap(),用来将I/O内存资源的物理地址映射到核心虚地址空间(3GB-4GB)中,原型如下:
void * ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags);
 
关于platform_get_resource(struct pdev *dev,unsigned int type,unsigned int num)
dev:资源所属的设备
type:获取的资源类型
num:获取的资源数
struct resource *platform_get_resource(struct platform_device *dev,
           unsigned int type, unsigned int num)
{
 int i;
 for (i = 0; i < dev->num_resources; i++) {
  struct resource *r = &dev->resource[i];
  if (type == resource_type(r) && num-- == 0)
   return r;
 }
 return NULL;
}
 
由函数可以看出,函数返回了第num个资源的首地址,但是感觉这个函数做了很多无用功。
 
关于misc_register()
 

注册杂项字符设备,该类设备使用同一个主设备号10
杂项字符设备使用的数据结构
struct miscdevice  {
    int minor;
    const char *name;
    struct file_operations *fops;
    struct list_head list;
    struct device *dev;
    struct class_device *class;
    char devfs_name[64];
};

杂项设备也是在嵌入式系统中用得比较多的一种设备驱动。在 Linux 内核的include\linux\miscdevice.h文件,要把自己定义的misc device从设备定义在这里。其实是因为这些字符设备不符合预先确定的字符设备范畴,所有这些设备采用主设备号10 ,一起归于misc device,其实misc_register就是用主设备号10调用register_chrdev()的。也就是说,misc设备其实也就是特殊的字符设备。

misc_device是特殊的字符设备。注册驱动程序时采用misc_register函数注册,此函数中会自动创建设备节点,即设备文件。无需mknod指令创建设备文件。因为misc_register()会调用class_device_create()或者device_create()。

再看mini2440_miscdev

static struct miscdevice mini2440_miscdev = {
 
    .minor = MISC_DYNAMIC_MINOR,
    .name ="buttons",       //创建设备文件buttons
    .fops = &mini2440buttons_fops,
};

static struct file_operations mini2440buttons_fops = {
    .owner   =   THIS_MODULE,
    .open    =   s3c24xx_buttons_open,
 .release =   s3c24xx_buttons_close,
    .read    =   s3c24xx_buttons_read,
 .poll    =   s3c24xx_buttons_poll,
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值