概念
U-boot:启动内核
内核:启动应用
应用:只调用open, read, write...等标准接口操作硬件,不去关心硬件相关的具体操作
驱动:以led为例,与应用接口对应包含led_open, led_read, led_write...
框架
1. 写出led_open, led_write函数。
2. 如何告诉内核?
A. 定义一个file_operations结构体,并填充它。
B. 调用register_chardev向内核注册这个结构体。
B1. 谁来调用register_chardev? -> 驱动的入口函数:led_init
B2. 为什么led_init是入口函数? -> 通过module_init来修饰led_init
B2.1 module_init是什么?一个宏,定义一个结构体,里边有个函数指针指向led_init,当加载驱动时,内核会自动找到这个结构体,然后调用这个指针指向的入口函数。
3. 卸载驱动函数:
出口函数调用unregister_chardev,用module_exit来修饰这个函数,以向内核表明这是一个出口函数。
4. 应用程序调用open如何找到相应的file_operations结构体?
根据设备类型,主设备号查找。由此,可引发对register_chardev简单实现方式的推断:
内核中有一个chardev[]数组,register_chardev根据传入的major填充这个数组中的相应元素。
5. 自动创建设备节点:udev机制,对于busybox来说是mdev。
使用mdev机制:根据系统信息自动创建设备节点。
怎么提供系统信息?
入口函数中调用:class_create , class_device_create
出口函数中调用:class_device_unregister, class_destroy
这会导致在/sys/目录下生成相应的信息,mdev即可根据这些信息自动创建设备节点。
杂项
cat /proc/devices 查看内核当前支持的设备,打印信息的第一列是主设备号,第二列是名字
mknod /dev/xxx c major minor 手工创建设备节点
驱动程序中不能直接操作物理地址,需要用ioremap映射到虚拟地址,再去操作。
问:应用如何找到对应的驱动?
答:根据打开文件的属性。以字符设备为例,设备文件属性有:字符设备、主设备号等。根据这两点,就可以在前述的chardev[]数组中找到,之后的read, write, ioctl就会使用这个数组项中的成员函数。
exec 5</dev/buttons 打开/dev/buttons定位到5,可用ls -l /proc/771/fd查看;771表示sh的PID,可用ps命令获得
exec 5<&- 关闭上面打开的/dev/buttons
源码
驱动:
/*
* 引脚:底板 - PH6, PH7
* 核心板 - PB4
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/leds.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/module.h>
#include <linux/pinctrl/consumer.h>
#include <linux/err.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/uaccess.h>
#include <asm/io.h>
static int major;
static struct class *leds_class;
static struct device *led_device[4];
static volatile unsigned int *gpio_base = NULL;
static volatile unsigned int *gpiob_dir, *gpiob_dataout;
static volatile unsigned int *gpioh_dir, *gpioh_dataout;
static volatile unsigned int *sys_base = NULL;
static volatile unsigned int *sys_gpb_mfpl;
static volatile unsigned int *sys_gph_mfpl;
static int leds_open(struct inode *inode, struct file *filp)
{
int minor = MINOR(inode->i_rdev);
switch (minor)
{
case 0:
{
*sys_gpb_mfpl &= ~(0xf << (4 * 4));
*sys_gph_mfpl &= ~((0xf << (6 * 4)) | (0xf << (7 * 4)));
*gpiob_dir |= 1 << 4;
*gpioh_dir |= (1 << 6) | (1 << 7);
break;
}
case 1:
{
*sys_gpb_mfpl &= ~(0xf << (4 * 4));
*gpiob_dir |= 1 << 4;
break;
}
case 2:
{
*sys_gph_mfpl &= ~(0xf << (6 * 4));
*gpioh_dir |= (1 << 6);
break;
}
case 3:
{
*sys_gph_mfpl &= ~(0xf << (7 * 4));
*gpioh_dir |= (1 << 7);
break;
}
default:
{
return -EINVAL;
}
}
return 0;
}
static ssize_t leds_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
int val;
int minor = MINOR(filp->f_dentry->d_inode->i_rdev);
copy_from_user(&val, buf, count);
switch (minor)
{
case 0:
{
if (val == 0)
{
*gpiob_dataout |= (1 << 4);
*gpioh_dataout |= (1 << 6) | (1 << 7);
}
else
{
*gpiob_dataout &= ~(1 << 4);
*gpioh_dataout &= ~((1 << 6) | (1 << 7));
}
break;
}
case 1:
{
if (val == 0)
{
*gpiob_dataout |= (1 << 4);
}
else
{
*gpiob_dataout &= ~(1 << 4);
}
break;
}
case 2:
{
if (val == 0)
{
*gpioh_dataout |= (1 << 6);
}
else
{
*gpioh_dataout &= ~(1 << 6);
}
break;
}
case 3:
{
if (val == 0)
{
*gpioh_dataout |= (1 << 7);
}
else
{
*gpioh_dataout &= ~(1 << 7);
}
break;
}
default:
{
return -EINVAL;
}
}
return 0;
}
static struct file_operations leds_fops = {
.owner = THIS_MODULE,
.open = leds_open,
.write = leds_write,
};
static int leds_init(void)
{
int i;
major = register_chrdev(0, "leds", &leds_fops);
leds_class = class_create(THIS_MODULE, "leds");
led_device[0] = device_create(leds_class, NULL, MKDEV(major, 0), NULL, "leds");
for (i = 1; i < 4; i++)
{
led_device[0] = device_create(leds_class, NULL, MKDEV(major, i), NULL, "led%d", i);
}
gpio_base = (volatile unsigned int *)ioremap(0xb8003000, 0x400);
gpiob_dir = gpio_base + 0x40 / 4;
gpiob_dataout = gpio_base + 0x44 / 4;
gpioh_dir = gpio_base + 0x1c0 / 4;
gpioh_dataout = gpio_base + 0x1c4 / 4;
sys_base = (volatile unsigned int *)ioremap(0xb0000000, 0x200);
sys_gpb_mfpl = sys_base + 0x78 / 4;
sys_gph_mfpl = sys_base + 0xa8 / 4;
return 0;
}
static void leds_exit(void)
{
int i;
iounmap(gpio_base);
iounmap(sys_base);
for (i = 0; i < 4; i++)
{
device_destroy(leds_class, MKDEV(major, i));
}
class_destroy(leds_class);
unregister_chrdev(major, "leds");
}
module_init(leds_init);
module_exit(leds_exit);
MODULE_LICENSE("GPL");
测试程序:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
/* ./ledstest </dev/led*> <on|off|blink> */
int main(int argc, char *argv[])
{
int fd;
int val = 0;
if (argc != 3)
{
printf("Usage:\n");
printf("%s </dev/led*> <on|off>\n", argv[0]);
return -1;
}
fd = open(argv[1], O_RDWR);
if (fd < 0)
{
printf("Can't open %s\n", argv[1]);
return -1;
}
if (strcmp(argv[2], "on") == 0)
{
val = 1;
}
else if (strcmp(argv[2], "off") == 0)
{
val = 0;
}
else if (strcmp(argv[2], "blink") == 0)
{
val = 1;
while (1)
{
write(fd, &val, sizeof(val));
if (val)
{
usleep(50000);
}
else
{
sleep(2);
}
val ^= 0x1;
}
}
else
{
printf("Usage:\n");
printf("%s </dev/led*> <on|off>\n", argv[0]);
return -1;
}
write(fd, &val, sizeof(val));
return 0;
}