引言
- 轮询: 好资源
- 中断, 陷入
- poll, 增加超时
- 异步通知:signal, 驱动程序通知应用程序
概念介绍
应用程序与内核, 驱动的关系
- u-boot: 启动内核
- 内核: 启动应用程序
- 应用: 读写文件, 硬件操作 (点灯, 获取按键值), 写应用程序的不应该知道芯片手册, 以及怎么接线, 而是应用使用 open read write 这种标准的接口, 这种接口就是由驱动实现的
- c 库: 包装了 open read write,
swi val
引发一个中断, 进入异常处理程序 - 系统调用接口 (system call interface): 根据发生中断的原因, 调用不同的处理函数: sys_open, sys_write, sys_read
- VFS: 虚拟文件系统
- 驱动:led_open, led_read, led_write
int main() {
int fd1, fd2;
int val = 1;
fd1 = open("/dev/led", O_RDWR);
write(fd1, &val, 4);
fd2 = open("hello.txt", O_RDWR);
write(fd2, &val, 4);
}
框架
第 1 个驱动程序: 点灯
电路图
- 接地(0)可以使灯亮
- GPF 接到电路上了,让 GPF 中的位为0,可使灯亮
- GPFCON 的地址是
0x56000050
, 占两个字节,用于控制 IO 端口复用的类型(输入,输出,中断还是保留) - GPFDAT 的地址是
0x56000054
, 占一个字节,用于设置或者读取数据的
源码分析: 驱动框架, 硬件操作 (ioremap), 用户空间与内核空间的数据传输
- 写出对应的 led_write, led_open
- 定义结构
struct file_operations
- 通过结构注册到系统中 (告诉系统有这些操作),
register_chrdev
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
static struct class *firstdrv_class;
static struct class_device *firstdrv_class_dev;
volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;
static int first_drv_open(struct inode *inode, struct file *file)
{
//printk("first_drv_open\n");
/* 配置 GPF4, 5, 6 为输出 */
*gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2)));
*gpfcon |= ((0x1<<(4*2)) | (0x1<<(5*2)) | (0x1<<(6*2)));
return 0;
}
static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
int val;
//printk("first_drv_write\n");
copy_from_user(&val, buf, count); // copy_to_user();
if (val == 1)
{
// 点灯
*gpfdat &= ~((1<<4) | (1<<5) | (1<<6));
}
else
{
// 灭灯
*gpfdat |= (1<<4) | (1<<5) | (1<<6);
}
return 0;
}
static struct file_operations first_drv_fops = {
.owner = THIS_MODULE, /* 这是一个宏, 推向编译模块时自动创建的__this_module 变量 */
.open = first_drv_open,
.write = first_drv_write,
};
int major;
static int first_drv_init(void)
{
major = register_chrdev(0, "first_drv", &first_drv_fops); // 注册, 告诉内核
firstdrv_class = class_create(THIS_MODULE, "firstdrv");
firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */
gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
gpfdat = gpfcon + 1;
return 0;
}
static void first_drv_exit(void)
{
unregister_chrdev(major, "first_drv"); // 卸载
class_device_unregister(firstdrv_class_dev);
class_destroy(firstdrv_class);
iounmap(gpfcon);
}
module_init(first_drv_init);
module_exit(first_drv_exit);
MODULE_LICENSE("GPL");
怎么编译驱动:Makefile
测试
设备名称 设备类型 主设备号 次设备号
mknode /dev/xxx c 111 0
register_chrdev
第一个参数为 0 的话, 系统会自动分配一个空缺的主设备号.
insmod xxx.ko # 安装新得驱动
cat /proc/devices # 查看设备
rmmod xxx # 卸载驱动, 后面没有 .ko
lsmod # 查看安装的模块
ls -l /dev/xxx # 查看设备的详细信息: 主设备号, 设备名称
如果 /dev/xxx
存在, 但是打不开, 就要看下主设备号和注册的是否一致.
- 驱动程序中可以自动分配主设备号, 也可以手动指定空闲的主设备号
- 应用程序打开文件的时候, 是没有的, 是手工建立的
mknod /dev/xxx c 主设备号 次设备号
; 另外可以通过udev
,mdev
根据系统信息 (ls /sys/devices
) 来自动创建 cd /sys/class/firstdrv/xyz
,cat dev
- 为啥可以自动创建设备呢? 因为在
rcS
中将/sbin/mdev
这个路径写入了/proc/sys/kernel/hotplug
这个文件中, 一旦有设备修改, 就会通知到/sbin/mdev
.
// 自动创建设备节点 /dev/xyz
firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */
完善硬件操作
- 查看原理图
- 看 2440 手册
- 写代码: 单片机是直接操作物理地址, 驱动中操作的是虚拟地址 (用 ioremap 将物理地址映射为虚拟地址)
ioremap, iounmap, copy_from_user, copy_to_user
- GPFCON: 配置 GPF 4, 5, 6 为输出
- GPFDAT: 设置
小结
- 写驱动框架
- 完善硬件的操作: 单片机是直接操作物理地址, Linux 是将物理地址映射到虚拟地址, 然后操作虚拟地址的
- 看原理图, 看芯片手册
可以看下 linux 源码中的 s3c2440 中的点灯驱动.
第 2 个驱动程序: 查询的方式获取按键值
驱动的框架整体和点灯的那个差不多
源码分析
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
static struct class *seconddrv_class;
static struct class_device *seconddrv_class_dev;
volatile unsigned long *gpfcon;
volatile unsigned long *gpfdat;
volatile unsigned long *gpgcon;
volatile unsigned long *gpgdat;
static int second_drv_open(struct inode *inode, struct file *file)
{
/* 配置 GPF0, 2 为输入引脚 */
*gpfcon &= ~((0x3<<(0*2)) | (0x3<<(2*2)));
/* 配置 GPG3, 11 为输入引脚 */
*gpgcon &= ~((0x3<<(3*2)) | (0x3<<(11*2)));
return 0;
}
ssize_t second_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
/* 返回 4 个引脚的电平 */
unsigned char key_vals[4];
int regval;
if (size != sizeof(key_vals))
return -EINVAL;
/* 读 GPF0, 2 */
regval = *gpfdat;
key_vals[0] = (regval & (1<<0)) ? 1 : 0;
key_vals[1] = (regval & (1<<2)) ? 1 : 0;
/* 读 GPG3, 11 */
regval = *gpgdat;
key_vals[2] = (regval & (1<<3)) ? 1 : 0;
key_vals[3] = (regval & (1<<11)) ? 1 : 0;
copy_to_user(buf, key_vals, sizeof(key_vals));
return sizeof(key_vals);
}
static struct file_operations sencod_drv_fops = {
.owner = THIS_MODULE, /* 这是一个宏, 推向编译模块时自动创建的__this_module 变量 */
.open = second_drv_open,
.read = second_drv_read,
};
int major;
static int second_drv_init(void)
{
major = register_chrdev(0, "second_drv", &sencod_drv_fops);
seconddrv_class = class_create(THIS_MODULE, "second_drv");
seconddrv_class_dev = class_device_create(seconddrv_class, NULL, MKDEV(major, 0), NULL, "buttons"); /* /dev/buttons */
gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
gpfdat = gpfcon + 1;
gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
gpgdat = gpgcon + 1;
return 0;
}
static void second_drv_exit(void)
{
unregister_chrdev(major, "second_drv");
class_device_unregister(seconddrv_class_dev);
class_destroy(seconddrv_class);
iounmap(gpfcon);
iounmap(gpgcon);
return 0;
}
module_init(second_drv_init);
module_exit(second_drv_exit);
MODULE_LICENSE("GPL");
测试
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
/* seconddrvtest
*/
int main(int argc, char **argv)
{
int fd;
unsigned char key_vals[4];
int cnt = 0;
fd = open("/dev/buttons", O_RDWR);
if (fd < 0)
{
printf("can't open!\n");
}
while (1)
{
read(fd, key_vals, sizeof(key_vals));
if (! key_vals[0] || ! key_vals[1] || ! key_vals[2] || ! key_vals[3])
{
printf("%04d key pressed: %d %d %d %d\n", cnt++, key_vals[0], key_vals[1], key_vals[2], key_vals[3]);
}
}
return 0;
}
主要就是那个 while(1)
不断地循环读取.
第 3 个驱动程序: 中断的方式获取按键值
- 按键按下
- CPU 发生中断
- 跳转到异常向量的入口执行
- 保存被中断的现场; 执行中断处理函数; 恢复被中断的现场
Linux 中断体系架构
异常向量, 中断注册, 清中断, 上边沿, 下边沿, 外部中断, 中断屏蔽, 内部中断
- trap_init 拷贝异常向量
- vector_irq 是用一个宏来实现的
- irq_handler
- asm_do_IRQ
单片机里面的:
- 分辨是哪个中断
- 调用处理函数
- 清中断
asm_do_IRQ 中的流程:
irq_desc[irq]
- set_irq_handler
- s3c24xx_init_irq
- chip->ack(irq)
free_irq 卸载中断
源码分析: 休眠, 唤醒
测试
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
static struct class *thirddrv_class;
static struct class_device *thirddrv_class_dev;
volatile unsigned long *gpfcon;
volatile unsigned long *gpfdat;
volatile unsigned long *gpgcon;
volatile unsigned long *gpgdat;
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
/* 中断事件标志, 中断服务程序将它置 1, third_drv_read 将它清 0 */
static volatile int ev_press = 0;
struct pin_desc{
unsigned int pin;
unsigned int key_val;
};
/* 键值: 按下时, 0x01, 0x02, 0x03, 0x04 */
/* 键值: 松开时, 0x81, 0x82, 0x83, 0x84 */
static unsigned char key_val;
struct pin_desc pins_desc[4] = {
{S3C2410_GPF0, 0x01},
{S3C2410_GPF2, 0x02},
{S3C2410_GPG3, 0x03},
{S3C2410_GPG11, 0x04},
};
/*
* 确定按键值
*/
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
struct pin_desc * pindesc = (struct pin_desc *)dev_id;
unsigned int pinval;
pinval = s3c2410_gpio_getpin(pindesc->pin);
if (pinval)
{
/* 松开 */
key_val = 0x80 | pindesc->key_val;
}
else
{
/* 按下 */
key_val = pindesc->key_val;
}
ev_press = 1; /* 表示中断发生了 */
wake_up_interruptible(&button_waitq); /* 唤醒休眠的进程 */
return IRQ_RETVAL(IRQ_HANDLED);
}
static int third_drv_open(struct inode *inode, struct file *file)
{
/* 配置 GPF0, 2 为输入引脚 */
/* 配置 GPG3, 11 为输入引脚 */
request_irq(IRQ_EINT0, buttons_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]);
request_irq(IRQ_EINT2, buttons_irq, IRQT_BOTHEDGE, "S3", &pins_desc[1]);
request_irq(IRQ_EINT11, buttons_irq, IRQT_BOTHEDGE, "S4", &pins_desc[2]);
request_irq(IRQ_EINT19, buttons_irq, IRQT_BOTHEDGE, "S5", &pins_desc[3]);
return 0;
}
ssize_t third_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
if (size != 1)
return -EINVAL;
/* 如果没有按键动作, 休眠 */
wait_event_interruptible(button_waitq, ev_press);
/* 如果有按键动作, 返回键值 */
copy_to_user(buf, &key_val, 1);
ev_press = 0;
return 1;
}
int third_drv_close(struct inode *inode, struct file *file)
{
free_irq(IRQ_EINT0, &pins_desc[0]);
free_irq(IRQ_EINT2, &pins_desc[1]);
free_irq(IRQ_EINT11, &pins_desc[2]);
free_irq(IRQ_EINT19, &pins_desc[3]);
return 0;
}
static struct file_operations sencod_drv_fops = {
.owner = THIS_MODULE, /* 这是一个宏, 推向编译模块时自动创建的__this_module 变量 */
.open = third_drv_open,
.read = third_drv_read,
.release = third_drv_close,
};
int major;
static int third_drv_init(void)
{
major = register_chrdev(0, "third_drv", &sencod_drv_fops);
thirddrv_class = class_create(THIS_MODULE, "third_drv");
thirddrv_class_dev = class_device_create(thirddrv_class, NULL, MKDEV(major, 0), NULL, "buttons"); /* /dev/buttons */
gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
gpfdat = gpfcon + 1;
gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
gpgdat = gpgcon + 1;
return 0;
}
static void third_drv_exit(void)
{
unregister_chrdev(major, "third_drv");
class_device_unregister(thirddrv_class_dev);
class_destroy(thirddrv_class);
iounmap(gpfcon);
iounmap(gpgcon);
return 0;
}
module_init(third_drv_init);
module_exit(third_drv_exit);
MODULE_LICENSE("GPL");
按键驱动深化: poll 或 select 操作
源码分析 poll 机制
测试
按键驱动深化: 同步, 互斥, 阻塞
源码分析: 机制分析
测试
按键驱动深化: 异步通知机制
源码分析: 机制分析
测试
定时器防抖
本文链接地址:字符设备驱动程序,英雄不问来路,转载请注明出处,谢谢。
有话想说:那就赶紧去给我留言吧。
文章评论