驱动函数笔记

本文档详细介绍了Linux驱动程序中中断的注册、处理和资源管理,包括request_irq、disable_irq及其变体的使用。同时,讨论了等待队列的创建、等待条件判断及唤醒进程的相关函数,如wait_event、wait_event_interruptible等。此外,还涵盖了信号量、printk调试、地址映射、设备注册与注销等关键概念和函数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.中断注册函数

int err = 0;

err = request_irq(button_irqs[i].irq, buttons_interrupt, IRQ_TYPE_EDGE_BOTH, button_irqs[i].name, (void *)&button_irqs[i]);

if (err)

{

 disable_irq(button_irqs[i].irq);/* 使中断不起作用*/ 
            free_irq(button_irqs[i].irq, (void *)&button_irqs[i]);      /* 释放中断资源*/
        return -EBUSY
 ; /*中断注册没成功的最终的返回值*/      //返回错误信息:请求的资源不可用

}

return 0; /* 正常返回*/  


 &button_irqs[i] 是该中断享有的资源,,buttons_interrupt为中断处理函数

 return IRQ_RETVAL(IRQ_HANDLED);  中断处理函数  最后的返回函数

中断和等待队列的详细知识看mini2440的button驱动   等待队列机制, 是中断管理中常用到的机制

2.disable_irq关闭中断并等待中断处理完后返回, 而disable_irq_nosync立即返回.

3.open方法会实际的分配和占用资源

4.close/release方法将释放open方法所占用的资源 

5.当进程中有可能发生阻塞的地方,都要有下面的操作 (如读写操作)

   if (!ev_press)

     {
        if (filp->f_flags & O_NONBLOCK)
          return -EAGAIN;
  else
         wait_event_interruptible(button_waitq, ev_press);
   }
都要首先去判断用户的设置,用户设置的是非阻塞还是阻塞。。

 当设置为非阻塞”的时候,遇到等待的条件,直接返回给用户。。

 当用户设置了阻塞”的时候,遇到等待的条件,将进程放在阻塞队列。当等待的条件满足时,进程会继续执行下面的操作。

6.static DECLARE_WAIT_QUEUE_HEAD(button_waitq);    button_waitq  为等待队列的名字

创建一个等待队列头并初始化

下面介绍的是等待事件函数,其就是依据condition条件是否满足来选择是否返回或者阻塞等待。 等待condition变为true,否则一直睡眠

wait_event(wq, condition)  

wait_event_timeout(wq, condition, timeout)   

wait_event_interruptible(wq, condition)   

wait_event_interruptible_timeout(wq, condition, timeout)  

(1)不可中断的等待

(2)超时返回的等待

(3)可中断的等待

(4)可中断并超时返回的等待

唤醒休眠进程的函数:wake_up

void wake_up(wait_queue_head_t  *queue);

void wake_up_interruptible(wait_queue_head  *queue);

 

惯例:用wake_up唤醒wait_event,用wake_up_interruptible唤醒wait_event_interruptible

7.注意驱动程序中结构体的应用, '.' 和'->'的区别,前者是变量,后者是指针

8.当有等待队列时要用到poll函数 ,而poll函数其实就是 poll_wait函数的应用

  unsigned int mask = 0;
   poll_wait(file, &button_waitq, wait);
   if (ev_press)
        mask |= POLLIN | POLLRDNORM;
   return mask;

  poll 函数的写法  poll_wait函数所做的工作是把当前进程添加到wait参数指定的等待列表

9.static struct semaphore lock; 定义一个信号量

void sema_init(struct semaphore *sem,int val); 信号量的初始化

void init_MUTEX(struct semaphore *sem);互斥信号量的初始化 init_MUTEX(&lock);

或者DECLARE_MUTEX(ADC_LOCK);定义并初始化

信号量不是某个资源的信号量,其就是一个标志位。。就是一个整数,当大于0是表明进程可以去操作临近资源。。其他情况下不可以(已经被其他进程占用)。

步骤:申请信号量-----进入临界区操作-----释放信号量-----其他操作

 if (!down_trylock(&lock))    试着获取信号量,如果能够立即获得,它就获得该信号量并返回0,否则返回非0.并且它不会导致休眠

信号量只能在进程的上下文中使用

10.

驱动程序中printk(KERN_ERR "Failed to remap register block\n");  用printk来打印

11.s3c2440有27根地址线,32根数据线。。地址线使用来接外设的,接需要寻址的外设(即有存储器的外设,没有存储器的外设不需要接地址线,给它几个数据就行了)。ARM通过地址线找到外设。。而数据线则是ARM自己用的。。用来传数据。

 有存储器的外设需要将它的IO端口占用的这段IO空间映射到内存的虚拟地址,IO空间要映射后才能使用,以后对虚拟  地址的操作就是对IO空间的操作。ioremap()函数完成这项功能。


static void __iomem *base_addr;

base_addr=ioremap(S3C2410_PA_ADC,0x20);// 进行物理地址到虚拟地址的映射  S3C2410_PA_ADC要映射的起始I/O地址, 0x20要映射空间的大小

 if (base_addr == NULL)

 {
     printk(KERN_ERR "Failed to remap register block\n");
     return -ENOMEM;
 } 判断一下 虚拟首地址是不是有效的

 #define ADCCON      (*(volatile unsigned long *)(base_addr + S3C2410_ADCCON))  设置寄存器的虚拟地址

 iounmap(base_addr);//取消ioremap()所做的地址映射

11.
  if (adc_clock)

    { 
      clk_disable(adc_clock);
      clk_put(adc_clock);
      adc_clock = NULL;
   }

12.read()函数中要判断用户传递过来的count和驱动要传递给用户的数据的位数的大小。。选择小的进行传递。

13.len = sprintf(str, "%d\n", value);  将value转化成str(char)型,len为字符串的长度   即将整形的数据转换成字符型的数据输出。。

14.bank 向数据线上写数据时 会有写信号的跟随,而向地址线发送地址信号则没有那么多要求,直接放上去即可

往数据线上是写,往地址线上是放

15.ARM IO引脚的操作        arch/arm/mach-s3c2410/include/mach/regs-gpio.h中定义好了。

static unsigned long led_table [] =   //定义IO引脚  第一步

{
S3C2410_GPB(5),
S3C2410_GPB(6),
S3C2410_GPB(7),
S3C2410_GPB(8),
};

static unsigned int led_cfg_table [] =   //定义输出,输入模式  第二步

 {
S3C2410_GPIO_OUTPUT,
S3C2410_GPIO_OUTPUT,
S3C2410_GPIO_OUTPUT,
S3C2410_GPIO_OUTPUT,
};

IO 口属性设置

S3C2410_GPIO_OUTPUT  输出

S3C2410_GPIO_INPUT  输入

S3C2410_GPG0_EINT8  IO 口对应的中断

S3C2410_GPB0_TOUT0         设置GPB0为tout0,pwm输出   如 s3c2410_gpio_cfgpin(S3C2410_GPB0, S3C2410_GPB0_TOUT0);


对引脚的设置见http://blog.youkuaiyun.com/olinxi1234567/article/details/25053759

16.在设备驱动中通常先定义一个结构体来描述硬件的性质  如buttons 驱动中

struct button_irq_desc          表述了按键的形态

{
    int irq;
    int pin;
    int pin_setting;
    int number;
    char *name;
};

static struct button_irq_desc button_irqs [] =    //结构体的实现     数组的个数通常用 sizeof(button_irqs)/sizeof(button_irqs[0]) 表示,其他可以类似表示

{
    {IRQ_EINT8 , S3C2410_GPG(0) ,  S3C2410_GPG0_EINT8  , 0, "KEY0"},
    {IRQ_EINT11, S3C2410_GPG(3) ,  S3C2410_GPG3_EINT11 , 1, "KEY1"},
    {IRQ_EINT13, S3C2410_GPG(5) ,  S3C2410_GPG5_EINT13 , 2, "KEY2"},
    {IRQ_EINT14, S3C2410_GPG(6) ,  S3C2410_GPG6_EINT14 , 3, "KEY3"},
    {IRQ_EINT15, S3C2410_GPG(7) ,  S3C2410_GPG7_EINT15 , 4, "KEY4"},
    {IRQ_EINT19, S3C2410_GPG(11),  S3C2410_GPG11_EINT19, 5, "KEY5"},
};


17.字符设备的注册与注销

字符设备的注册我们会在static int __init函数中完成,注销在static void __exit函数

static int __init注册

下面是例子  //杂项设备的注册

int ret;----ret = misc_register(&misc);---return ret; 其中misc为static struct miscdevice misc中函数名

static void __exit注销

misc_deregister(&misc);

static struct file_operations dev_fops = {
    .owner   =   THIS_MODULE,
    .open    =   s3c24xx_buttons_open,
};


static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR, 次设备号  
.name = DEVICE_NAME,
.fops = &dev_fops,
};

static int __init dev_init(void)
{
int ret;
ret = misc_register(&misc);  
printk (DEVICE_NAME"\tinitialized\n");
return ret;
}
static void __exit dev_exit(void)
{
misc_deregister(&misc);
}

字符设备的注册

#define BUTTON_MAJOR 221 /* 主设备号

static int __init mini2440_buttons_init(void) 

        int ret; /* 设备注册的返回值*/ 
/* 注册设备驱动程序*/         /* 设备号, 设备名, 和驱动函数*/ 
ret = register_chrdev(BUTTON_MAJOR,DEVICE_NAME,&mini2440_buttons_fops
/* 对注册失败的处理*/  
if(ret < 0) 

printk(DEVICE_NAME " can't  register major number\n"); 
return ret; 

static void __exit mini2440_buttons_exit(void) 

/* 注消设备驱动*/ 
unregister_chrdev(BUTTON_MAJOR,DEVICE_NAME); 

http://blog.youkuaiyun.com/olinxi1234567/article/details/41630331

18.寄存器读写函数:   __raw_writel()   __raw_writeb()   __raw_readl()   __raw_readb()  32位

 __raw_readl和__raw_writel

Linux对I/O的操作都定义在asm/io.h中,相应的在arm平台下,就在asm-arm/io.h中。

#define __raw_readl(a)   (__chk_io_ptr(a), *(volatile unsigned int __force   *)(a))
#define __raw_writel(v,a) (__chk_io_ptr(a), *(volatile unsigned int __force   *)(a) = (v))

在include\linux\compiler.h中:

#ifdef __CHECKER__
……
extern void __chk_io_ptr(void __iomem *);
#else
……
# define __chk_io_ptr(x) (void)0
……
#endif

__raw_readl(a)展开是:((void)0, *(volatile unsigned int _force *)(a))。在定义了__CHECKER__的时候先调用__chk_io_ptr检查该地址,否则__chk_io_ptr什么也不做,* (volatile unsigned int _force *)(a)就是返回地址为a处的值。(void)xx的做法有时候是有用的,例如编译器打开了检查未使用的参数的时候需要将没有用到的参数这么弄一下才能 编译通过。

CPU对I/O的物理地址的编程方式有两种:一种是I/O映射,一种是内存映射。 __raw_readl和__raw_writel等是原始的操作I/O的方法,由此派生出来的操作方法有:inb、outb、 _memcpy_fromio、readb、writeb、ioread8、iowrite8等。

tcon = __raw_readl(S3C2410_TCON);  ////直接放入寄存器就可。函数会去实现地址的映射。

__raw_writel(tcfg1, S3C2410_TCFG1);  向寄存器写数   

一般都是先去读取寄存器的值。而后在去设置。。最后写

19.得到系统时钟

struct clk *clk_p;
unsigned long pclk;

clk_p = clk_get(NULL, "pclk");                                    //得到pclk   即是系统时钟
pclk = clk_get_rate(clk_p); 


20.Linux2.6设备驱动常用的接口函数

21. Linux 内核访问外设IO资源的方式


1.驱动程序是依赖于内核的,在内核中运行,其编译也是离不开内核的Makefile的,需要建立内核的源码树

2.在内核文档目录下的文件 Documentation/Changes 列出了编译需要的工具版本。

3.驱动程序的头文件

#include <linux/module.h> 
#include <linux/init.h>  这2个几乎都包含了

#include <linux/kernel.h>  printf

#include <linux/errno.h> 在 Linux 内核里, 错误码是负数。 错误码的头文件

4.模块清理函数必须撤销任何由初始化函数进行的注册

5.驱动程序中的 printk 调试技术   http://blog.youkuaiyun.com/olinxi1234567/article/details/41278167

   cat  /proc/kmsg 会给出驱动程序中所有的printk的输出,只是驱动程序

可以直接用命令:dmesg来查看输出信息和cat  /proc/kmsg一样的。。详见http://blog.youkuaiyun.com/olinxi1234567/article/details/41280571


6.kmalloc()函数来开辟一段连续的内存

   Void *kmalloc(size_t size, int flags);         点击打开链接

   在用kmalloc申请函数后,要对其清零用 memset() 函数对申请的内存进行清零。

   memset()函数原型是extern void *memset(void *buffer, int c, int count)        

  buffer:为指针或是数组,c:是赋给buffer的值,count:是buffer的长度.      点击打开链接

  用完之后一定要记得用 kfree()函数来释放自己申请的内存

2.


3.模块参数:和导出模块及符号的相互引用

 我们可以利用module_param(参数名、参数类型、参数读写属性) 为模块定义一个参数,例如: 
1. static char *string_test = “this is a test”;   
2.  static num_test = 1000;     
3.  module_param (num_test, int ,S_IRUGO);   
4.  module_param (steing_test,charp,S_ITUGO);

详见凌阳 linux驱动基础开发

4.


      


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值