驱动
===========================================================
一、驱动模块 基本框架
相关头文件:
#include<linux/init.h>
#include<linux/module.h>
(一)函数实现:
1、入口函数
static int __init hellokernel_init(void)
2、出口函数
static void __exit hellokernel_exit(void)
(二)模块声明:
module_init(hellokernel_init);
module_exit(hellokernel_exit);
(三)/*添加模块的许可声明*/
MODULE_LICENSE("GPL");//必须加
===========================================================
二、GPIO 操作相关函数
相关头文件:
#include<asm/gpio.h>
#include<plat/gpio-cfg.h>
(一)函数实现:
1、申请GPIO资源
int gpio_request(unsigned gpio,char* name);
参数:gpio:GPIO编号:
S5PV210_GPC0(3)
S5PV210_GPC1(3)
name:标识
返回值:成功返回0,失败返回负值
2 、释放已经申请的GPIO资源
void gpio_free(unsigned gpio)
参数:gpio:GPIO编号
3 、设置GPIO的为输入口
int gpio_direction_input(unsigned gpio);
参数:gpio:GPIO编号
4 、设置GPIO为输出口
int gpio_direction_output(unsigned gpio,intvalue);
参数:gpio:GPIO编号
value: 对应GPIO默认输出的值
5 、设置GPIO的输出状态
void gpio_set_value(unsigned gpio,int value)
参数:gpio:GPIO编号
value: 对应GPIO输出的值 0/1
6 、读取GPIO状态
int gpio_get_value(unsigned gpio);
参数:gpio:GPIO编号
返回值:当前引脚的状态信息 0/1
===========================================================
三、字符设备 驱动编写流程之(手动创建设备节点)
相关头文件:
#include<linux/fs.h> //structfile_operations
#include<linux/cdev.h> //struct cdev
1、定义 申请 设备号
static dev_t dev; //定义设备号
alloc_chrdev_region(&dev,0,1,"led_cdev"); //向内核申请设备号
2、定义 初始化 硬件操作方法对象
struct file_operations led_fops = {
.open = led_open,
.release = led_close,
.read = led_read,
.write = led_write
};
3、定义初始化 字符设备对象
struct cdev led_cdev; //定义对象
cdev_init(&led_cdev,&led_fops); //初始化字符设备对象
结果: led_cdev.ops = &led_fops
初始化字符设备对象,给字符设备对象添加硬件操作方法
4、注册字符设备驱动到内核
cdev_add(&led_cdev, 申请的设备号, 次设备号的个数);
至此: 一旦安装完毕,内核就有一个真实的字符设备驱动,并且这个驱动给用户提供了相关的操作方法(open,close,read,write)
5、编写以上四个接口函数, 这四个接口里面就是对硬件操作的细节
6、字符设备不再使用时,要卸载:
1、卸载字符设备驱动
cdev_del(&led_cdev);
2、释放设备号
unregister_chrdev_region(dev,1); //释放设备号
===========================================================
四、字符设备 驱动编写流程之(自动创建设备节点)
相关头文件:
#include <linux/device.h> //设备文件自动创建
(一)创建设备类(长树枝)
struct class* cdev_class; //定义设备类指针
cdev_class =class_create(THIS_MODULE,"name");
例:cls= class_create(THIS_MODULE, "tarena");
第一个参数类的所有者模块,一般传:THIS_MODULE
第二个参数时类目录名,在/sys/class下创建类目录
(二)创建设备文件(长苹果)
例:device_create(cls,NULL, dev, NULL, "myled");
struct device *device_create(
structclass *cls,
struct device *parent,
dev_tdevt, void *drvdata,
const char *fmt, ...)
@cls:struct class指针
@parent:该设备的parent指针,一般为NULL
@devt:字符设备的设备号
@drvdata:被添加到该设备回调的数据,一般为NULL
@“const char *fmt, ...” :设备名字,对应设备文件名
eg:“myled” ---> /dev/myled
eg:“myled1” ---> /dev/myled1
(三)删除设备文件
device_destroy(cdev_class,devt);
(四)删除设备类
class_destroy(cdev_class);
(五)设备号操作相关的宏函数
设备号 = MKDEV(主设备号,次设备号)
主设备号 = MAJOR(设备号)
次设备号 = MINOR(设备号)
===========================================================
五、linux字符设备驱动硬件操作接口--read
(一)应用程序read系统调用
char buf[1024]={0};
int ret = read(fd,buf,1024);
从fd对应设备读取数据,存放到用户buf缓冲区,要读1024个字节
ret表示实际读到的字节数
(二)对应的底层驱动操作接口函数
structfile_operations {
ssize_t (*read)(struct file *file, char__user *buff,
size_tcount, loff_t *off);
}
@file:文件指针,对应fd
@buff:用户缓冲区的首地址,内核代码不能直接访问,如果需要将内核缓冲区的数据拷贝到用户缓冲区使用,需要使用内核函数:
copy_to_user()。
@count: 要拷贝字节个数
@off:偏移,记录上一次的读写位置。
一般用于多次读操作:
eg:
loff_t pos = *off;//读到了100字节
*off = pos + 100;
相关头文件:
#include<asm/uaccess.h> //copy_from_user
(三)内核函数copy_to_user
int copy_to_user(void __user *to,const void*from,
unsignedlong n);
函数功能:将内核缓冲区的数据拷贝到用户缓冲区
@to:目标地址,保存的是用户缓冲区的首地址
@from:源地址,保存内核缓冲区的首地址
@n:拷贝字节数
返回值:成功返回0,失败返回非0(没有拷贝成功后剩余的字节数)
案例:读取灯的状态
0:LED1、LED2都关
1:LED1开LEED2关
2:LED1关LED2开
3:LED1、LED2都开
===========================================================
六、linux字符设备操作硬件接口--write
(一)应用程序write系统调用
char buf[1024] = "hello world!";
write(fd,buf,strlen(buf));
(二)对应底层系统调用接口
ssize_t (*write)(struct file *file, constchar __user *buf,
size_tcount, loff_t *off);
@file:文件指针
@buf:用户缓冲区的首地址
@count:要写入的字节个数
@off:记录上一次读写位置
相关头文件:
#include<asm/uaccess.h> //copy_from_user
(三)内核函数copy_from_user
copy_from_user(void* to,const void __user*from,
unsignedlong n);
函数功能:将用户缓冲区的数据拷贝到内核缓冲区
@to:目标地址,内核缓冲区的首地址
@from:源地址,用户缓冲区的首地址
@n:拷贝的字节数
返回值:成功返回0,失败返回非0(拷贝剩余的字节数);
案例:
用户进程写1,打开LED1和LED2
用户进程写0,关闭LED1和LED2
练习:用户能够控制某个灯的开关
struct led_cmd{
intcmd;//控制开关
intindex;//灯的编号
};
struct led_cmd cmd;
write(fd,&cmd,sizeof(cmd));
===========================================================
七、linux字符设备驱动硬件接口--ioctl
(一)应用程序中ioctl系统调用
int ioctl(int fd, int cmd,...);
函数功能:向设备发送控制命令,能够和设备读/写操作
@fd:设备文件描述符
@cmd:向设备发送的控制命令,命令由程序员自己单独的定义
@...:表示可选参数,一般传递用户缓冲区的首地址,驱动程
序中通过copy_to/from_user进行数据交互。
案例:通过ioctl控制led开关,应用程序:
#define LED_ON 0x100001
#define LED_OFF 0x100002
int fd=open("/dev/myled",...)
//仅仅 开/关 灯
ioctl(fd,LED_ON);
ioctl(fd,LED_OFF);
//指定开/关哪一个灯
int index = 1;
ioctl(fd,LED_ON,&index);
ioctl(fd,LED_OFF,&index);
(二)对应底层驱动的ioctl接口
long (*unlocked_ioctl) (struct file *file,
unsignedint cmd, unsigned long arg);
参数:
@file:和前面一样,对应文件描述符
@cmd:保存用户向设备发送的控制命令
switch(cmd){
caseLED_ON:
开灯操作
break;
caseLED_OFF:
关灯操作
break;
}
@arg:保存用户缓冲区的首地址,内核程序不能直接使用
案例:通过ioctl控制某个指定led开/关,驱动程序实现
===========================================================
八、混杂设备驱动(misc)--编写步骤
相关头文件:
#include<linux/device.h> //设备文件自动创建
#include<linux/miscdevice.h> //structmiscdevice
(一)定义初始化混杂设备对应的硬件操作方法
static structfile_operations led_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = led_ioctl // 发送命令/ 读/ 写
};
(二)定义初始化混杂设备对象
struct miscdeviceled_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "myled",
.fops = &led_fops
};
(三)安装混杂设备对象到内核
misc_register(&led_misc);
(四)卸载混杂设备对象
misc_deregister(&led_misc);
//切记:本质上还是字符设备驱动
===========================================================
九、linux内核中断编程
相关头文件:
#include<linux/irq.h>
#include<linux/interrupt.h> //中断相关头文件
#include<linux/input.h> //标准按键值
(一)注册中断
int request_irq(unsigned int irq,irq_handler_t handler,
unsignedlong flags,const char *name, void *dev)
函数功能:向内核申请硬件中断资源,然后注册硬件中断的
中断处理函数到内核。
参数:
@irq:硬件中断对应的软件编号,称为中断号
//linux/arch/arm/plat-s5p/include/plat/irqs.h
//linux/arch/arm/mach-s5pv210/include/mach/irqs.h
eg:按键“UP”中断号
查看硬件电路原理图-->硬件中断XEINT0
对应的软件中断编号-->IRQ_EINT(0)
@handler:中断处理函数(中断处理例程/中断服务程序)
Typedef irqreturn_t (*irq_handler_t)(int//中断号, void *//参数);
typedef enum irqreturn irqreturn_t;
enum irqreturn {
IRQ_NONE,//中断不做处理,异常
IRQ_HANDLED,//中断被处理,正常
IRQ_WAKE_THREAD,
};
@flags:中断标志,中断触发方式
IRQ_TRIGGER_FALLING:下降沿
IRQ_TRIGGER_RISING:上升沿
IRQ_TRIGGER_HIHG:高电平
IRQ_TRIGGER_LOW:低电平
多个标志,可以做位运算
eg:按键按下和抬起都产生中断
IRQ_TRIGGER_FALLING| IRQ_TRIGGER_RISING
@name:中断名称
@dev:给中断处理函数传递的参数
返回值:成功返回0,失败返回负值:-EINVAL、-EBUSY
(二)释放中断
void free_irq(int irq,void* dev)
@irq:中断号
@dev:中断处理函数的参数 //和注册时候传递的参数一致