前言
记录linux内核与驱动相关学习内容
一、linux驱动编写
首先将相应gpio引脚号转为中断号,并调用request_irq函数来申请一个中断。
//获得中断号
button_device->irqno = gpio_to_irq(button_device->gpiono);
printk("<kernel> irqno=%d\n", button_device->irqno);
//申请中断
ret = request_irq(button_device->irqno, button_handler,
IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, "xyd_pwr_key_int", NULL);
if (ret) {
printk("request_irq fail\n");
ret = -EINVAL;
goto err_request_irq;
}
关于所用到的变量对其进行封装,并初始化一个结构体对象实例(static的作用是让外部不可见,防止污染内核)。
struct rk3399_button {
unsigned int major ; //主设备号
dev_t devno; //设备号
struct class *cls;
struct device *dev;
int gpiono;
int irqno;
struct cdev *cdev;
struct button_event event; //按键的事件对象
wait_queue_head_t wq_head; //等待队列头对象
int have_data; // 表示设备是否有数据, 1--有 0--无
struct tasklet_struct tasklet; //下半部对象
};
static struct rk3399_button *button_device; //声明一个对象
接下来就是中断服务函数的编写,本代码是以板子上的gpio0_A5引脚为例(是一个按键)
irqreturn_t button_handler(int irq, void *dev)
{
int value;
printk("call %s name=%s pid=%d\n",__func__, current->comm, current->pid);
//设置event对象
button_device->event.code = KEY_A;
value = gpio_get_value(button_device->gpiono);
if (value)
button_device->event.value = 0;
else
button_device->event.value = 1;
//调度下半部
tasklet_schedule(&button_device->tasklet);
return IRQ_HANDLED;
}
其中current是一个全局结构体变量,我打印了其中的一些成员以便观察对比。当然还有read驱动的编写如下。这个驱动的目的在于将中断发生时内核空间发生改变的结构体event通过copy_to_user传入用户空间,那边定义好相应结构体接收即可,然后判断并打印相应信息。
static ssize_t button_read (struct file *filp, char __user *buf, size_t size, loff_t *flag)
{
int ret;
printk("call %s() name=%s pid=%d\n", __func__, current->comm, current->pid);
//判断open的打开方式是否非阻塞,是否有数据可读
if (filp->f_flags & O_NONBLOCK && !button_device->have_data)
return -EAGAIN;
//2)判断是否有数据可读,不可读阻塞调用进程, 这个点调用进程休眠,到等待队列休眠
wait_event_interruptible(button_device->wq_head, button_device->have_data);
//拷贝event到用户空间
ret = copy_to_user(buf, &button_device->event, size);
if (ret > 0) {
printk("copy_to_user fail\n");
return -EFAULT;
}
memset(&button_device->event, 0, sizeof(button_device->event));
button_device->have_data = 0; //读了数据就没有了
printk("call %s()\n", __func__); //内核用的printf
return size;
}
如下为应用层代码与内核中驱动层相匹配使用。
ret = read(fd, &event, sizeof(event));//阻塞
printf("read ret=%d\n", ret);
if (ret < 0) {
perror("read");
printf("errno=%d\n", errno);
}
if (event.code == KEY_A) {
if (event.value) {
printf("power key pressed\n");
} else {
printf("power key release\n");
}
}
装载完驱动后(目前用手动装载的方式),当按下按键,结果如下
二、各函数使用方法说明
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char*name, void *dev_id);
参数说明:
irq
:要请求的硬件中断号。handler
:当中断发生时调用的中断处理函数。flags
:指定中断处理的一些属性,如 IRQF_SHARED 表示中断可以被共享,IRQF_DISABLED 表示中断处理函数禁用本地中断等。name
:设备名称,通常用于 /proc/interrupts 文件中的描述。dev_id
:一个指向设备数据的指针,当中断处理程序被调用时,这个指针会被传递给它。
request_irq
函数会尝试注册一个中断处理程序。如果成功,该函数返回 0;如果失败,它返回一个错误代码。
一旦中断处理程序被注册,当与 irq
参数指定的中断号相对应的硬件中断发生时,内核就会调用相应的处理程序
static ssize_t button_read (struct file *filp, char __user *buf, size_t size, loff_t *flag)
-
struct file *filp
: 这是一个指向file
结构的指针,它代表了打开的文件。通过这个结构,你可以访问到与文件相关联的各种信息,比如文件的偏移量、文件的所有者等。 -
char __user *buf
: 这是一个指向用户空间缓冲区的指针,用于存储从设备读取的数据。在内核空间与用户空间之间进行数据传输时,需要使用特殊的指针类型(如__user
)来确保内核不会错误地访问用户空间的内存。 -
size_t size
: 这是一个表示用户缓冲区大小的变量。它告诉内核用户为读取操作分配了多少空间。 -
loff_t *flag
: 这是一个指向文件偏移量的指针。读取操作可能会改变文件的当前读取位置,这个偏移量会更新以反映新的位置
irqreturn_t button_handler(int irq, void *dev)
-
int irq
: 这是中断号,它唯一标识了发生中断的硬件设备。 -
void *dev
: 这是一个指向设备特定数据的指针。当注册中断处理程序时,你可以传递一个指针给request_irq
函数,这个指针会在中断处理程序被调用时传递给它。这通常用于让中断处理程序能够访问设备的私有数据。另外补充struct file_operations fops = { .owner = THIS_MODULE, //该对象和当前模块对象绑定,避免卸载模块 .open = button_open, .release = button_close, .write = button_write, .unlocked_ioctl = button_ioctl, .read = button_read, .poll = button_poll, }; - 另外补充注册设备号以及file_operations成员的初始化(需要用到的初始化即可)
-
struct file_operations fops = { .owner = THIS_MODULE, //该对象和当前模块对象绑定,避免卸载模块 .open = button_open, .release = button_close, .write = button_write, .unlocked_ioctl = button_ioctl, .read = button_read, .poll = button_poll, }; register_chrdev(0, "button_drv", &fops);
总结
留此文以便日后开发时重拾