driver的总结

来源:互联网 发布:星球大战 全介绍 知乎 编辑:程序博客网 时间:2024/06/03 08:39

static int count = 4;
module_param(count,  int, 0);  //声明一个变量是模块的参数,安装ko的时候可以引入该参数
MODULE_PARM_DESC(count, "count --> the count of printk "); //参数的描述。 #modinfo  *.ko


EXPORT_SYMBOL();//在一个模块中声明了函数,该函数如果想要被另外一个module调用,需要将函数输出到"内核符号表"中
1、module


//#modinfo  *.ko 可以查看module的信息
MODULE_AUTHOR("linjung@GEC");
MODULE_DESCRIPTION("the first chrdev of drivers");
MODULE_LICENSE("GPL");
MODULE_VERSION("V1.0");




module_init(chrdev_init); //驱动的入口 #insmod *.ko
module_exit(chrdev_exit); //驱动的出口 #rmmod *.ko


//出口与入口的初始化
static void __exit chrdev_exit(void)
static int __init chrdev_init(void)


2、字符设备的描述 --- cdev结构体
cdev描述了一个字符设备,每创建一个字符设备,就需要创建一个cdev。每个字符设备都有自己的cdev。


//cdev结构体原型
struct cdev {
struct kobject kobj;//给linux内核管理设备的,生成/sys目录下的设备驱动详细信息
struct module *owner;//dev属于哪个module,一般为THIS_MODULE
const struct file_operations *ops;
struct list_head list;//内核链表;内核将所用的cdev放到一个链表中,方便内核管理cdev。
dev_t dev;//设备号,每一个字符设备都有自己的设备号,该设备号在内核中是唯一.
unsigned int count;//在该设备下有多少个次设备。
};


//文件描述集
const struct file_operations beep_fops = {
.owner    = THIS_MODULE,
.open     = beep_open,
.write    = beep_write,
.release  = beep_close,
.ioctl    =
.unlocked_ioctl =
};


3、设备号
dev_t dev;
typedef u_long dev_t;
typedef unsigned long u_long;


1)由主设备号和次设备号,得到设备号
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
#define MINORBITS 20
2)由设备号得到主次设备号
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))


//例: dev_no = MKDEV(Test_Major, Test_Minor);


3)设备号的注册与申请
静态:int register_chrdev_region(dev_t from, unsigned count, const char *name)
参数说明:
    dev_t from --->设备号的值,第一个设备号
    unsigned count ---->次设备的数量
    const char *name ---->设备的名称,可以查看:#cat /proc/devices
返回值:
    成功:0
    失败:负数的错误码。

动态:int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
参数说明:
    dev_t *dev --->申请到的设备号
    unsigned baseminor  ----->申请的第一个次设备号
    unsigned count  ----> 申请次设备的数量
    const char *name  ---->设备的名称,可以查看:#cat /proc/devices
返回值:
    成功:0
    失败:负数的错误码。
//例:ret = register_chrdev_region(dev_no, 1, "chrdev_test");
      ret = alloc_chrdev_region(&dev_no, Test_Minor, 1, "chrdev_test");
 
注册设备号:void unregister_chrdev_region(dev_t from, unsigned count)
参数说明:
    dev_t from --->设备号的值,第一个设备号
    unsigned count ---->次设备的数量
//例:unregister_chrdev_region(dev_no, 1);


4、cdev的使用
一:新方法
1)初始化: void cdev_init(struct cdev *cdev, const struct file_operations *fops)
2)注册到linux内核:int cdev_add(struct cdev *p, dev_t dev, unsigned count)
3)注销: void cdev_del(struct cdev *p)


//例:  struct cdev chrdev;
//定义文件操作集
cdev_init(&chrdev, &beep_fops);
chrdev.owner = THIS_MODULE;
ret = cdev_add(&chrdev, dev_no, 1);
cdev_del(&chrdev);


二:旧方法 
//例://定义文件操作集
ret = register_chrdev(Test_Major, "chrdev_test", &led_fops)
if(Test_Major > 0)
{
 //静态申请设备号
}
else
{
//动态申请
  Test_Major = ret;
}


5、自动创建设备文件  #include <linux/device.h>
//手动创建设备文件   #mknod /dev/led c 250 0
创建类class://查看 #cat /sys/class/class_led 
struct class * class_create(struct module *owner, const char *name)
参数说明:
    struct module *owner  --->class输入哪一个module。一般THIS_MODULE
    const char *name --->自定义类的名字
返回:
    创建好的类。

销毁类:class
void class_destroy(struct class *cls);


创建device:
struct device *device_create(struct class *cls, struct device *parent,
   dev_t devt, void *drvdata,const char *fmt, ...)
参数说明:
     struct class *cls  --->创建的device是输入哪个class
     struct device *parent  --->父设备  一般 NULL
     dev_t devt --->设备号
     void *drvdata  ---->设备的数据
     const char *fmt ---->设备的名称,也就是设备文件的名字


返回值:
     struct device * ---->创建的device
销毁设备:
void device_destroy(struct class *cls, dev_t devt);


//例: static struct class *cls;
static struct device *dev;
cls=class_create(THIS_MODULE,"led_class");
dev=device_create(cls,NULL,dev_no,NULL,"chrdev");
//device_destroy(cls,dev_no);
//class_destroy(cls);

6、io内存 #include <linux/io.h>
//在linux中,CPU访问外设的方法与访问内存的方法一样,都是根据地址来访问。
//即外设可以内存是同一编址,都是通过地址值去访问。


使用思路:申请IO内存区---> 动态映射--->使用虚拟地址-->解除动态映射--->释放IO内存区
//例: static struct resource * GPJ2_LED;
#define GPJ2CON_PA 0xe0200280//物理地址
static unsigned int *GPJ2CON_VA;
GPJ2_LED = request_mem_region(GPJ2CON_PA,8,"GPJ2_LED");//申请io内存区
GPJ2CON_VA = ioremap(GPJ2CON_PA,8);//将物理地址映射为虚拟地址
//release_mem_region(GPJ2CON_PA,8);
//iounmap(GPJ2CON_VA);

1)将内核空间的数据拷贝给用户空间 --->驱动程序的read函数
static inline long copy_to_user(void __user *to,
const void *from, unsigned long n)


2)将用户空间的数据拷贝给内核空间 --->驱动程序的write函数
static inline long copy_from_user(void *to,
const void __user * from, unsigned long n)


3)访问虚拟地址的函数
方法1:
unsigned ioread32(volatile void __iomem *addr)
void iowrite32(u32 data, volatile void __iomem *addr)
方法2:
unsigned readl(volatile void __iomem *addr)
void writel(u32 data, volatile void __iomem *addr)
方法3:
unsigned __raw_readl(volatile void __iomem *addr)
void __raw_writel(u32 data, volatile void __iomem *addr)
//例: unsigned int temp;
temp =readl(GPD0DAT_VA);
//temp |=(1<<0);
writel(temp,GPD0DAT_VA);


7、GPIO  #include <linux/gpio.h>
//gpio号:linux/arch/arm/mach-s5pv210/include/mach/gpio.h
1)GPIO的申请-->2)设置GPIO的方向-->3)设置gpio的输出值-->4)获得gpio的输入值-->5)释放gpio
//例: ret = gpio_request(S5PV210_GPJ2(0), "gec210_led1");
gpio_direction_output(S5PV210_GPJ2(0), 1);//1为初始值
gpio_set_value(S5PV210_GPJ2(0), 0);
//gpio_get_value(S5PV210_GPJ2(0));
gpio_free(S5PV210_GPJ2(0));

8、ioctl
应用程序:ioctl()  --->系统调用:SWI 54 ----> 系统调用: sys_ioctl() 
--->虚拟文件系统:vfs_ioctl()--->驱动程序:file_operations.ioctl=beep_ioctl()
//例: #define GEC210_KEY_READ_IOR('C', 0x03, char [4])//与应用程序一致
unlocked_ioctl :static int led_ioctl( struct file *file,unsigned int cmd, unsigned long arg)
 ioctl:  static int led_ioctl(struct inode *inode, struct file *file,unsigned int cmd, unsigned long arg){
switch(cmd){
case GEC210_LED_ON:
gpio_set_value(gec210_led[arg-1].gpio_num, 0);
break;
case GEC210_LED_OFF:
gpio_set_value(gec210_led[arg-1].gpio_num, 1);
break;}
//应用程序:ioctl(fd, GEC210_LED_ON, 4);


1)static ssize_t beep_write (struct file *file, const char __user *data,
size_t len, loff_t *ppos)
2)static ssize_t beep_write (struct file *file, char __user *data,
size_t len, loff_t *ppos)

9、杂项设备misc  #include <linux/miscdevice.h>
杂项设备又叫混杂设备,是将普通字符设备做了一个封装,在linux内核中形成了杂项设备的模型。
特点:
1)不需要class_create()和device_create(),也可以自动生成设备文件。
2)杂项设备也是字符设备,是对普通字符设备的封装
3)杂项设备的主设备号是10。


杂项设备的设计流程:
1)定义一个杂项设备
static  struct miscdevice  led_device = {
.minor = MISC_DYNAMIC_MINOR,//动态分配次设备号
.name = "led",
.fops = &led_fops,
}; 
2)定义一个文件操作集
3)杂项设备注册
ret=misc_register(&led_misc);
misc_deregister(&led_misc);


10、irq       //#include <linux/interrupt.h>
申请中断:
inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
   const char *name, void *dev)


参数说明:
unsigned int irq --->中断号。在linux内核中,每个中断源都有自己的中断号(类似GPIO号)
irq_handler_t handler --->中断处理程序,每个中断源设置一个中断服务程序
unsigned long flags --->中断的标志
const char *name --->中断的名字,自定义的一个名称
void *dev ---> 向中断服务程序传递的一个参数


返回值:
0 --->中断申请成功
负数的错误码 --->中断申请失败


1)中断号
中断号也是和一个具体硬件平台相关的。
linux/arch/arm/plat-s5p/include/plat/irqs.h
(1)外部中断
#define IRQ_EINT(x) ((x) < 16 ? ((x) + S5P_EINT_BASE1) \
: ((x) - 16 + S5P_EINT_BASE2))
(2)定时器
#define IRQ_TIMER0 S5P_TIMER_IRQ(0)
#define IRQ_TIMER1 S5P_TIMER_IRQ(1)
(3)UART接收数据的中断号
#define IRQ_S3CUART_RX0 IRQ_S5P_UART_RX0
#define IRQ_S3CUART_RX1 IRQ_S5P_UART_RX1


2)中断服务程序
irqreturn_t (*irq_handler_t)(int, void *);
参数说明:
int -->响应该中断
void * --->注册中断的时候,向中断服务程序传递的参数(void *dev)
返回值:
/**
 * enum irqreturn
 * @IRQ_NONE interrupt was not from this device
 * @IRQ_HANDLED interrupt was handled by this device//常用
 * @IRQ_WAKE_THREAD handler requests to wake the handler thread
 */
 
3)中断的标志 ----unsigned long flags


(1)设置外部中断的触发方式
IRQF_TRIGGER_RISING  ---> 上升沿触发
IRQF_TRIGGER_FALLING
IRQF_TRIGGER_HIGH
IRQF_TRIGGER_LOW
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING --->双边沿触发

(2)设置中断的工作方式
IRQF_DISABLED --->在响应当前的中断时,其它的中断会被关闭,直到中断响应完成
IRQF_SHARED --->如果一个中断源被注册两次以上

4)、中断释放
void free_irq(unsigned int, void *);
//查看已注册的中断号  # cat /proc/interrupts


5)、中断的开关
void disable_irq(unsigned int irq)
void disable_irq_nosync(unsigned int irq)
void enable_irq(unsigned int irq)


//disable_irq()与disable_irq_nosync()区别
disable_irq()必须在中断处理程序完成,才可以使用此函数关闭当前的中断,否则就会死机。
disable_irq_nosync(),没有disable_irq的要求


6) 中断的上下半部
(1)上半部
中断服务程序就是中断的上半部,当中断发生,就会立即响应上半部;中断的上半部处于中断的上下文。
在中断上下文需要注意的问题:不能使用可能阻塞的函数(不能产生睡眠--->死机)
如:ssleep()  msleep()  获得一个互斥锁mutex_lock()   获得一个信号量down()
//中断服务程序必须原子过程....


(2)下半部
中断处理过程比较复杂,我们一个把一部分工作推后,当linux系统不忙的时候,再去处理推后的工作;那么推后工作就是下半部。
//中断下半部的机制: 1)softirq//少用   2)tasklet   3)workqueue


tasklet(小任务):
1)定义一个tasklet
struct tasklet_struct  my_tasklet;
2)初始化tasklet
tasklet_init(&my_tasket, tasklet_work, 5);
3)定义tasketlet的处理函数
void tasklet_work(unsigned long data)
{
printk("in tasklet , data = %d \n", data); //data = 5
}
4)在中断服务程序中,调度tasklet
tasklet_schedule(&my_tasklet);
5)tasklet的特点:
(1)tasklet中推后的工作使用内核线程:ksoftirqd
(2)tasklet是与中断服务程序一样,处于中断上下文的,也不是使用可能产生阻塞的函数。
(3)在ISR中是不能响应中断,而在tasklet中,可以响应中断的。
(4)tasklet适合于推后的工作处理时间不长的情况。推后的工作workqueue。


workqueue (工作队列):


1)workqueue的特点
(1)workqueue是运行进程上下文,这样在workqueue中是可以使用可能产生阻塞的函数
(2)workqueue适合于推后的工作占用时间比较长的场合
(3)workqueue的处理也是由内核线程完成的--->events
(4)在workqueue的工作过程中,是可以再次响应中断的。
(5)工作队列可以使用linux内核中自带,可以直接向工作队列中添加工作。(常见)
(6)也可以自己创建工作队列及工作队列的处理线程。(少见)


2)workqueue的使用 --->使用内核自带的工作队列
#include <linux/workqueue.h>     ---在模型上,与tasklet是一致的。
(1)创建一个work
struct work_struct my_work;


(2)初始化work
INIT_WORK(&my_work, key2_work);


(3)定义work的处理函数
void key2_work(struct work_struct *work)
{
ssleep(2);
}
(4)在中断处理程序中,调度work
schedule_work(&my_work);


//workqueue 可堵塞,可使用mdelay()来为中断去抖


-----------------------------------------------------------------------------------
11、内核时钟
在设计linux设备驱动的时候,有些驱动模块:如ADC、wdt、 timer、IIC、uart、IIS、nand........
都需要一个硬件时钟,这些时钟是作为该模块的时钟源,如果时钟源没有打开,硬件模块不工作。
HZ:
一秒钟内核时钟中断的次数。在linux内核中是一个常数,修改完HZ,内核需要重新编译。
jiffies:
从linux内核启动到当前,内核中断的次数
jffies是一个32bits的整数,所以会有“回绕”现象,多长时间回绕一次?//HZ=256  2^32 /256 /60/60/24= x天
//linux系统重启后,jiffies的值会归0,再重新计数。
1)忙等待延时
void ndelay(unsigned long x)//纳秒
void udelay(unsigned long x)//微秒
void mdelay(unsigned long x)//毫秒


2)睡眠延时
#include <linux/delay.h>
void ssleep(unsigned int seconds)
void msleep(unsigned int msecs);
long msleep_interruptible(unsigned int msecs);


//inux内核时钟的颗粒度 HZ=256, T=4ms。所以睡眠延时时间颗粒度(精度)是4ms
//睡眠延时是依赖与内核定时器的,内核定时器的精度是1/HZ.


3)clock
例:
ADC ----->linux/arch/arm/mach-s5pv210/adc.c
adc_clock = clk_get(&pdev->dev, "adc");  //"adc" ---》ADC的时钟
if (IS_ERR(adc_clock)) {
//
goto err_clk;
}
clk_enable(adc_clock);
l4)inux内核中的时钟树(与具体的硬件平台相关,就是对裸机的时钟进行管理)
//linux/arch/arm/mach-s5pv210/clock.c
static struct clk init_clocks_disable[] = {
{
.name = "rot",   //时钟的名字
.id = -1,
.parent = &clk_hclk_dsys.clk,
.enable = s5pv210_clk_ip0_ctrl,
.ctrlbit = (1<<29),
}, {
.name = "otg",//时钟的名字
.id = -1,
.parent = &clk_hclk_psys.clk,
.enable = s5pv210_clk_ip1_ctrl,
.ctrlbit = (1<<16),
},


-----------------------------------------------------------------------
12、platform
   设备驱动也可以不使用platform 模型,如果不采用platform 模型,我们的device和driver就不能分开,
它们是在同一个程序中,并且生成一个ko。
linux内核自带的设备驱动程序都是采用platform 模型。


//设备驱动模型的三个要素
1、总线
linux内核的设备驱动中,除了platform bus,还有IIC总线、usb总线、SPI总线。。。
platform bus总线上面挂载了platform device和platform driver
负责管理platform device和platform driver,完成platform device和platform driver的匹配
struct bus_type {
.....................
int (*match)(struct device *dev, struct device_driver *drv);
.......................
}
1)match()实现device和driver的匹配。只有driver和device匹配成共,设备驱动才可以正常工作,缺一不可....
2)device和driver都是安装到内核中bus上,由bus来管理。
3)device和driver可以是两个不同的模块,可以分别安装,但是没有安装顺序的。
4)deivce和driver是通过名字进行匹配的。
5)bus不需要用户创建,linux内核在初始化过程中,已经创建了总线,并做了初始化。


2、设备
device是描述设备驱动的硬件信息,该信息叫做resource,包括:物理地址(如:GPJ2CON和GPJ2DAT)
GPIO号(S5PV210_GPJ2(0))、中断号(IRQ_EINT(16))、MAC地址,液晶屏分辨率、液晶屏的时序参数........
struct device {
....................................
struct bus_type*bus;
struct class *class;
....................................
}
(1)资源,即resource
一段IO内存区 --->SFR的物理地址
GPIO号
中断号


(2)平台数据,即platform data
液晶屏:分辨率、8个时序参数、4个信号的极性.......
网卡:网卡的工作模式(16bits or 8bits)、网卡的MAC地址......


3、驱动
driver是描述设备驱动的软件信息,申请内存区、IO内存的动态映射、创建设备设备驱动模型、创建类、
创建device、建立杂项设备驱动模型.....
struct device_driver {
......................................................
struct bus_type*bus;
int (*probe) (struct device *dev);  //驱动的入口函数
int (*remove) (struct device *dev); //驱动的出口函数
......................................................
}
============================================
//device 的使用
(1)定义resource
static struct resource key_resource[]={
[0]={
.start = S5PV210_GPH2(0),
.end   = S5PV210_GPH2(3),
.name  = "GPH2_GPIO",
.flags = IORESOURCE_MEM,
},
};
(2)定义device
struct platform_device key_device ={
.name = "gec210_key",
.id   = -1, //自动分配
//.dev = { .release = key_release,},
.num_resources = ARRAYSIZE(key_resource),
.resource = key_resource,
};
static int __init key_dev_init(void)
{
printk("key_dev init ok!\n");
return platform_device_register(&key_device);
}
static void __exit key_dev_exit(void)
{
printk("key_devv exit!\n");
return platform_device_unregister(&key_device);
}
==============================================
//driver 


struct resource *key_res;
static char  key_irq_no;


static struct file_operations key_fops = {
.owner  = THIS_MODULE,
.unlocked_ioctl = key_ioctl,
};
 
static struct  miscdevice key_misc = {
.minor = MISC_DYNAMIC_MINOR,
.fops =  &key_fops,
.name = "key_drv",
};


static int __devinit key_probe(struct platform_device *dev) 
{
key_res = platform_get_resource(dev, IORESOURCE_IRQ, 0);
key_irq_no = key_res->start;
}


static int __devexit key_remove(struct platform_device *dev){}


struct platform_driver key_driver ={
.probe  = key_probe,
.remove = __devexit_p(key_remove),
.driver = {
.name  = "gec210_key",
.owner = THIS_MODULE,
},
};


static int __init key_drv_init(void)
{
printk("key_dev init ok!\n");
return platform_driver_register(&key_driver);
}


static void __exit key_drv_exit(void)
{
printk("key_devv exit!\n");
return platform_driver_unregister(&key_driver);
}

0 0