天天看點

動手做一個linux字元驅動

最近學習了一本書《linux 裝置驅動開發詳解》——宋寶華老師寫的,不過買的竟然是第二版的,裡面是關于處理器s3c6410的,本來看這書的第一版是講s3c2410,後來發現其實内容差不多了。

    學習了一下裡面第2篇,裝置驅動的核心理論,涉及很多,基礎的知識需要反複的推敲才能明白。

    主要内容:

1、子產品加載函數

static int __init combine_init(void)
{
//注冊裝置号和裝置名
    int result;
    dev_t dev_num= MKDEV(combine_major,0);  
    //dev_t是cdev結構體的成員,定義了裝置号,MAJOR(dev_t),MINOR(dev_t)可以分别獲得主裝置号和次裝置号
    char dev_name[] = "combine";
    if(combine_major)
    {
        result = register_chrdev_region(dev_num, 1, dev_name);  
        //int register_chrdev_region(dev_t from,unsigned count,const char *name);   
        //第一個參數:裝置号;第二個參數:連續配置設定的裝置号;第三個參數:裝置名}   
    else
    {
        result = alloc_chrdev_region(&dev_num,0,1,dev_name);        
        //int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char *name);   
        combine_major = MAJOR(dev_num); 
    }   
    if(result < 0)   
    {   
        printk("combine:unable to get major %d\n",combine_major);return result; 
    }   
    
//關聯裝置結構體cdev和檔案操作結構體fops       
    cdev_init(&combine_cdev, &combine_fops);    
    //static struct cdev combine_cdev;  
    //void cdev_init(struct cdev *,struct file_operation *);    
    result = cdev_add(&combine_cdev, dev_num, 1);   
    //int cdev_add(struct cdev *,dev_t,unsigned);   
    if(result <0 )//加上函數的出錯處理    
    {   
        printk("combine:unable to add cdev\n"); 
    }   
    printk("combine device installed \nwith major %d,%s\n",combine_major,dev_name); 
    return 0;   
}      

2、子產品解除安裝函數

static void __exit combine_exit(void)
{
  cdev_del(&combine_cdev);  
  //void cdev_del(struct cdev *);
  unregister_chrdev_region(MKDEV(combine_major,0), 1);
  //void unregister_chrdev_region(dev_t from,unsigned count);
  printk("combine device uninstalled\n");}      

3、很重要的一個結構體:file_operations檔案操作結構體

static struct file_operations combine_fops = 
{
  .owner = THIS_MODULE,
  .open = combine_open,
  .release = combine_release,
  .read = combine_read,
  .write = combine_write,
  .ioctl = combine_ioctl,
};      

然後就是在驅動函數中利用

中斷屏蔽local_irq_disable(),local_irq_enable(),

自旋鎖spinlock_t,

信号量struct semaphore sem,

完成量struct completion,

互斥體struct mutex來解決并發與競态問題。

4、阻塞操作

當應用程式進行read(),write()等系統調用時,若裝置的資源不能擷取,而使用者又希望以阻塞的方式通路裝置,驅動程式應在裝置驅動的xxx_read(),xxx_write()等操作中将程序阻塞直到資源可以擷取,此後應用程式的read(),write()等調用才傳回。

用于裝置阻塞操作可以使用等待隊列wait_queue_head_t my_queue;

将事件加入等待隊列wait_event_interrupt(my_queue,condition);

将事件從等待隊列中喚醒wake_up_interrupt(&my_queue);

或者利用unsigned int poll(struct file *filp,struct poll_table *wait);查詢是否可對裝置進行無阻塞通路

struct unsigned int xxx_poll(struct file *filp,poll_table *wait)
{
  unsigned int mask=0;
  struct xxx_dev *dev = filp->private_data;//擷取裝置結構體指針
  poll_wait(filp,my_queue,wait);
  //這個函數加了之後再read中就不用再wait_event_interrupt,
  //因為在這裡已經實作了阻塞,一旦資源可用就可以在read中實作無阻塞通路 
  //像read中wait_event_interrupt一樣被wake_up_interrupt喚醒

  if(...)//可讀 
            mask |= POLLIN | POLLRDNORM;//标示資料可獲得 
        if(...)//可寫 
            mask |= POLLOUT | POLLWRNORM;//标示資料可寫入  
        return mask;<pre name="code" class="cpp">}      

5、linux中斷程式設計

<pre name="code" class="html">struct key_irq_desc 
{
  unsigned int irq;
  int pin;
  int pin_setting;
  int number;
  char *name;
};
static struct key_irq_desc key_irqs[] = 
{
  {IRQ_EINT8, S3C2410_GPG(0), S3C2410_GPG0_EINT8,    0, "key1"},
  {IRQ_EINT11,  S3C2410_GPG(3), S3C2410_GPG3_EINT11,   1, "key2"},
  {IRQ_EINT13,  S3C2410_GPG(5), S3C2410_GPG5_EINT13,   2, "key3"},
  {IRQ_EINT14,  S3C2410_GPG(6), S3C2410_GPG6_EINT14,   3, "key4"},
  {IRQ_EINT15,  S3C2410_GPG(7), S3C2410_GPG7_EINT15,   4, "key5"},
  {IRQ_EINT19,  S3C2410_GPG(11),S3C2410_GPG11_EINT19,  5, "key6"},
};
static volatile int key_values[] = {0,0,0,0,0,0};
static DECLARE_WAIT_QUEUE_HEAD(key_waitq);
static volatile int ev_press = 0;
static void my_tasklet_func(unsigned long data)
{
  printk("key do tasklet\n");
}
static DECLARE_TASKLET(my_tasklet,my_tasklet_func,0);

static irqreturn_t key_interrupt(int irq,void *dev_id)//中斷頂半部函數
{
  struct key_irq_desc *key_irqs = (struct key_irq_desc *)dev_id;
  int up = s3c2410_gpio_getpin(key_irqs->pin);

  printk("<1>up=%d\n",up);
  if(up)//up 按下去是0,沒按是1
    key_values[key_irqs->number] = (key_irqs->number + 1) + 0x80;
  else
    key_values[key_irqs->number] += 1;

  ev_press = 1;
  wake_up_interruptible(&key_waitq);
  
  tasklet_schedule(&my_tasklet);//在頂半部函數中排程執行底半部函數

  return IRQ_RETVAL(IRQ_HANDLED);
}

static int combine_open(struct inode *inode,struct file *file)
{
  int i,err;
  
  for(i = 0;i < sizeof(key_irqs) / sizeof(key_irqs[0]);i++)
  {
    s3c2410_gpio_cfgpin(key_irqs[i].pin,key_irqs[i].pin_setting);
    err = request_irq(key_irqs[i].irq, key_interrupt, 0, key_irqs[i].name, (void *)&key_irqs[i]); 
    //int request_irq(unsigned int irq,irq_handler_t handler,unsigned long irqflags,const char *devname,void *dev_id);    //handler是向系統登記的中斷處理頂半部函數,dev_id是傳給他的參數     
    set_irq_type(key_irqs[i].irq,IRQ_TYPE_EDGE_BOTH);
    if(err) break;
  }
  if(err)
  {
    i--;
    for(;i >= 0;i--)
    {
      disable_irq(key_irqs[i].irq);
      free_irq(key_irqs[i].irq, (void *)&key_irqs[i]);
      //void free_irq(unsigned int irq,void *dev_id); 
    }return -EBUSY;}return 0;}      

6、下面是完整的按鍵與蜂鳴器結合的字元驅動函數,資源上傳于:​​javascript:void(0)​​

<pre name="code" class="cpp">/*********************************************************************************************
* File:   conbine.c
* Author:   luckywang
* Desc: 組合各種驅動,驅動6個按鍵,和beep驅動,但是現在沒有将兩者結合起來,隻是各自獨立驅動  
* History:  2013.11.09
*********************************************************************************************/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/serio.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/miscdevice.h>
#include <linux/gpio.h>
#include <asm/io.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <asm/uaccess.h>
#include <mach/regs-clock.h>
#include <plat/regs-timer.h>
#include <mach/regs-gpio.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <mach/hardware.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/wait.h>

static int combine_major = 0;
static struct cdev combine_cdev; 

#define BEEP_MAGIC 'k'
#define BEEP_START_CMD _IO(BEEP_MAGIC,1)
#define BEEP_STOP_CMD _IO(BEEP_MAGIC,2)

struct key_irq_desc 
{
  unsigned int irq;
  int pin;
  int pin_setting;
  int number;
  char *name;
};

static struct key_irq_desc key_irqs[] = 
{
  {IRQ_EINT8, S3C2410_GPG(0), S3C2410_GPG0_EINT8,     0,  "key1"},
  {IRQ_EINT11,  S3C2410_GPG(3), S3C2410_GPG3_EINT11,   1, "key2"},
  {IRQ_EINT13,  S3C2410_GPG(5), S3C2410_GPG5_EINT13,   2, "key3"},
  {IRQ_EINT14,  S3C2410_GPG(6), S3C2410_GPG6_EINT14,   3, "key4"},
  {IRQ_EINT15,  S3C2410_GPG(7), S3C2410_GPG7_EINT15,   4, "key5"},
  {IRQ_EINT19,  S3C2410_GPG(11),S3C2410_GPG11_EINT19,5, "key6"},
};

static volatile int key_values[] = {0,0,0,0,0,0};
static DECLARE_WAIT_QUEUE_HEAD(key_waitq);
static volatile int ev_press = 0;

static void my_tasklet_func(unsigned long data)
{
  printk("key do tasklet\n");
}
static DECLARE_TASKLET(my_tasklet,my_tasklet_func,0);

static irqreturn_t key_interrupt(int irq,void *dev_id)
{
  struct key_irq_desc *key_irqs = (struct key_irq_desc *)dev_id;
  int up = s3c2410_gpio_getpin(key_irqs->pin);

  printk("<1>up=%d\n",up);
  if(up)//up 按下去是0,沒按是1
    key_values[key_irqs->number] = (key_irqs->number + 1) + 0x80;
  else
    key_values[key_irqs->number] += 1;

  ev_press = 1;
  wake_up_interruptible(&key_waitq);
  
  tasklet_schedule(&my_tasklet);

  return IRQ_RETVAL(IRQ_HANDLED);
}

static int combine_open(struct inode *inode,struct file *file)
{
  int i,err;
  
  for(i = 0;i < sizeof(key_irqs) / sizeof(key_irqs[0]);i++)
  {
    s3c2410_gpio_cfgpin(key_irqs[i].pin,key_irqs[i].pin_setting);
    err = request_irq(key_irqs[i].irq, key_interrupt, 0, key_irqs[i].name, (void *)&key_irqs[i]);   
    set_irq_type(key_irqs[i].irq,IRQ_TYPE_EDGE_BOTH);
    if(err) break;
  }
  if(err)
  {
    i--;
    for(;i >= 0;i--)
    {
      disable_irq(key_irqs[i].irq);
      free_irq(key_irqs[i].irq, (void *)&key_irqs[i]);
    }
    return -EBUSY;
  }
  return 0;
}

static int combine_release(struct inode *inode,struct file *file)
{
  int i;
  for(i = 0;i < sizeof(key_irqs) / sizeof(key_irqs[i]);i++)
  {
    disable_irq(key_irqs[i].irq);
    free_irq(key_irqs[i].irq,(void *)&key_irqs[i]);
  }
  return 0;
}

static int combine_read(struct file *filp,char __user *buff,size_t count,loff_t *offp)
{
  unsigned long err;
  if(!ev_press)
  {
    if(filp->f_flags & O_NONBLOCK)
      return -EAGAIN;
    else
      wait_event_interruptible(key_waitq, ev_press);
  }

  ev_press = 0;
  err = copy_to_user(buff,(const void *)key_values,min(sizeof(key_values),count));
  memset((void *)key_values,0,sizeof(key_values));
  
  return err ? -EFAULT : min(sizeof(key_values),count);
}

static int combine_write(struct file *file,const char __user *buff,size_t count,loff_t *offp)
{
  
  return 0;
}

void beep_start(void)
{
  s3c2410_gpio_pullup(S3C2410_GPB(0),1);
  s3c2410_gpio_cfgpin(S3C2410_GPB(0), S3C2410_GPIO_OUTPUT);
  s3c2410_gpio_setpin(S3C2410_GPB(0),1);
}

void beep_stop(void)
{
  s3c2410_gpio_cfgpin(S3C2410_GPB(0), S3C2410_GPIO_OUTPUT);
  s3c2410_gpio_setpin(S3C2410_GPB(0),0);

}

static int combine_ioctl(struct inode *inode,struct file *file,unsigned int cmd,unsigned long arg)
{
  switch(cmd)
  {
    case BEEP_START_CMD:
      beep_start();
      break;
    case BEEP_STOP_CMD:
      beep_stop();
      break;
    default:      
      break;
        
  }
  
  return 0;
}

static struct file_operations combine_fops = 
{
  .owner = THIS_MODULE,
  .open = combine_open,
  .release = combine_release,
  .read = combine_read,
  .write = combine_write,
  .ioctl = combine_ioctl,
};
  
static int __init combine_init(void)
{
//注冊裝置号和裝置名
  int result;
  dev_t dev_num= MKDEV(combine_major,0);
  char dev_name[] = "combine";
  if(combine_major)
  {
    result = register_chrdev_region(dev_num, 1, dev_name);
  }
  else
  {
    result = alloc_chrdev_region(&dev_num,0,1,dev_name);
    combine_major = MAJOR(dev_num);
  }
  if(result < 0)
  {
    printk("combine:unable to get major %d\n",combine_major);
    return result;
  }

//關聯裝置結構體cdev和檔案操作結構體fops
  cdev_init(&combine_cdev, &combine_fops);
  result = cdev_add(&combine_cdev, dev_num, 1);
  if(result <0 )//加上函數的出錯處理
  {
    printk("combine:unable to add cdev\n");
  }
  
  printk("combine device installed \nwith major %d,%s\n",combine_major,dev_name);
  
  return 0;
}

static void __exit combine_exit(void)
{
  //printk("combine driver exit\n");
  cdev_del(&combine_cdev);
  unregister_chrdev_region(MKDEV(combine_major,0), 1);

  printk("combine device uninstalled\n");
}

module_init(combine_init);
module_exit(combine_exit);

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("luckywang");
MODULE_DESCRIPTION("s3c2440 combine driver");
MODULE_VERSION("V0.1");      
<p></p><p><span style="font-size:14px;"></span></p><pre name="code" class="cpp">/*********************************************************************************************
* File: combine_test.c
* Author: luckkywang
* Desc:   combine test code
* History:2013.11.10
*********************************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <linux/ioctl.h>
#include<errno.h>

#define BEEP_MAGIC 'k'
#define BEEP_START_CMD _IO (BEEP_MAGIC, 1)
#define BEEP_STOP_CMD _IO (BEEP_MAGIC, 2)

int main()
{
  int i = 0;
  int key_values[] = {0,0,0,0,0,0};
  int dev_fd;
  int ret;
  dev_fd = open("/dev/combine",O_RDWR | O_NONBLOCK);
  if ( dev_fd == -1 ) {
    printf("Cann't open file /dev/beep\n");
    exit(1);
  }
  while(1)
  {
    ret = read(dev_fd,key_values,sizeof(key_values));
    if(ret != sizeof(key_values))
    {
      if(errno != EAGAIN)
        perror("read key\n");
      continue;
    }
    else
    {
      for(i = 0;i < 6;i++)
      {
        printf("K%d %s,key_value=0x%02x\n",i+1,(key_values[i] & 0x80) ? "released" : key_values[i] ? "pressed down":"",key_values[i]);
      }
      key_values[i] = 0;
    }
  } 

  
  /*printf("Start beep\n");
  ioctl (dev_fd, BEEP_START_CMD,0);
  getchar();
  ioctl (dev_fd, BEEP_STOP_CMD,0);
  printf("Stop beep and Close device\n");*/
  close(dev_fd);
  return 0;
}      

繼續閱讀