目录
1.序言
之前使用过STM32的开发板redhat linux 9.0,对嵌入式的裸机开发有一定的了解,在此基础上,又认真学习了嵌入式linux开发的过程。本文以字符设备驱动为例,从设备树和内核这一层面介绍简单led驱动开发过程。特备谢谢韦东山老师《嵌入式Linux应用开发完全指南》。
2.驱动开发流程
(1)确定主设备号,也可以让内核分配;
(2)定义file_operations结构体,用于绑定内核来操作你的硬件;
(3)把驱动程序中的drv_open/drv_read/drv_write等函数,填入file_operations结构体;
(4)把file_operations结构体注册进内核;
(5)实现入口函数:安装驱动程序时,才会去调用这个入口函数;
(6)实现出口函数:卸载驱动程序时,出口函数调用unregister_chrdev;
(7)其他建立:提供设备信息,手动创建设备节点。
3.设备树
对于板级资源,须要加载硬件资源的头文件,每每更换一次GPIO,就必须更改代码,重新编译和加载驱动,这会在Linux内核当中留下大量残余文件。于是,设备树的出现就解决了这一问题。设备树,即给内核当中的驱动指定硬件资源。
下边是我编撰的qemu的设备树文件
首先找到设备树文件储存位置
book@100ask:~/100ask_imx6ull-qemu/linux-4.9.88/arch/arm/boot/dts$
打开设备树文件,vi100ask_imx6ull_qemu.dts,这儿我之前早已指定好了qemu中所有的led资源,注意设备树文件编撰的格式。
100ask_led@0 {
compatible = "100as,leddrv";
pin = ;
};
100ask_led@1 {
compatible = "100as,leddrv";
pin = ;
};
100ask_led@2 {
compatible = "100as,leddrv";
pin = ;
};
100ask_led@3 {
compatible = "100as,leddrv";
pin = ;
};
这儿编撰的时侯也可以用到GPIO和Pinctrl子系统概念,因为是简单led,不再赘言。
然后就须要重新编译设备树文件嵌入式linux驱动程序实战开发,并把它放到qemu目录下。
此后,可以在~/ubuntu-18.04_imx6ul_qemu_system/imx6ull-system-image下看见该设备树文件
4.驱动程序编撰1.确定主设备号
通常初始化为0嵌入式linux驱动程序实战开发,也可以让内核来分配。
/* 1. 确定主设备号 */
static int major = 0;
2.定义file_operations结构体
static struct file_operations led_drv = {
.owner = THIS_MODULE,
.open = qemu_led_drv_open,
.read = qemu_led_drv_read,
.write = qemu_led_drv_write,
.release = qemu_led_drv_close,
};
3.建立file_operations结构体
由于是照亮led灯红旗linux桌面版,须要从用户态往内核写数据,主要建立write函数。
static ssize_t qemu_led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
int err;
char status;
struct inode *inode = file_inode(file); //转换设备节点
int minor = iminor(inode); //得到次设备号
printk("%s %s line %dn", __FILE__, __FUNCTION__, __LINE__);
err = copy_from_user(&status, buf, 1);
p_led_opr->ctl(minor,status); //led控制函数
return 0;
}
依据用户态的指令,还须要初始化硬件
static ssize_t led_drv_open(struct inode * node, struct file * file)
{
int minor = iminor(node);
printk("%s %s line %dn", __FILE__, __FUNCTION__, __LINE__);
p_led_opr->init(minor); //根据次设备号,初始化该组GPIO
return 0;
}
以下代码为指定硬件资源的初始化和控制函数,置于chip_led.c文件。
static int board_led_init(int which)
{
printk("init gpio: group %d, pin %dn", GROUP(g_ledpins[which]), PIN(g_ledpins[which]));
if (!CCM_CCGR1)
{
CCM_CCGR1 = ioremap(0x20C406C, 4);
}
switch(GROUP(g_ledpins[which]))
{
case 5:{
/* 1. enable GPIO5
* CG15, b[31:30] = 0b11
*/
*CCM_CCGR1 |= (3<gdir |= (1<<3);
break;
}
default : break;
}
break;
}
case 1:{
/* 1. enable GPIO1
* CG13, b[27:266] = 0b11
*/
*CCM_CCGR1 |= (3<IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 = 5;
gpio1->gdir |= (1<IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO05 = 5;
gpio1->gdir |= (1<IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO06 = 5;
gpio1->gdir |= (1<dr &= ~(1<dr |= (1<dr &= ~(1<dr |= (1<dr &= ~(1<dr |= (1<dr &= ~(1<dr |= (1<<3);
break;
}
}
break;
}
default : break;
}
return 0;
}
接出来,就是重要的设备树解析:内核解析dtb文件,把每一个节点都转换为device_node结构体;对于个别device_node结构体,会被转换为platform_device结构体。这样就可以得到硬件资源。
static int chip_demo_gpio_probe(struct platform_device *pdev)
{
struct device_node *np;
int err = 0;
int led_pin;
np = pdev->dev.of_node;
if (!np)
return -1;
err = of_property_read_u32(np, "pin", &led_pin);
g_ledpins[g_ledcnt] = led_pin;
led_class_create_device(g_ledcnt);
g_ledcnt++;
return 0;
}
4.注册file_operations结构体
major = register_chrdev(0,"100ask_led", &led_drv);
5.实现入口函数
static int __init qemu_led_init(void)
{
int err;
printk("%s %s line %dn", __FILE__, __FUNCTION__, __LINE__);
major = register_chrdev(0,"qemu_100ask_led", &led_drv);
led_class = class_create(THIS_MODULE, "100ask_led_class");
err = PTR_ERR(led_class);
if(IS_ERR(led_class))
{
printk("%s %s line %dn", __FILE__, __FUNCTION__, __LINE__);
unregister_chrdev(major, "qemu_100ask_led");
return -1;
}
return 0;
}
6.实现出口函数
static void __exit led_exit(void)
{
printk("%s %s line %dn", __FILE__, __FUNCTION__, __LINE__);
class_destroy(led_class);
unregister_chrdev(major, "qemu_100ask_led");
}
7.建立其他信息
创建设备节点和用户操作函数
void led_class_create_device(int minor)
{
device_create(led_class, NULL, MKDEV(major,minor), NULL, "qemu_100ask_led%d",minor);
}
void led_class_destroy_device(int minor)
{
device_destroy(led_class, MKDEV(major,minor));
}
void register_led_operations(struct led_operations *opr)
{
p_led_opr = opr;
}
EXPORT_SYMBOL(led_class_create_device);
EXPORT_SYMBOL(led_class_destroy_device);
EXPORT_SYMBOL(register_led_operations);
5.实验
(1)在此之前,须要编撰Makefile文件。我的理解是,make可以把驱动编译链接为linux内核可以辨识的模块。
KERN_DIR = /home/book/100ask_imx6ull-qemu/linux-4.9.88
#配置内核
all:
make -C $(KERN_DIR) M=`pwd` modules
$(CROSS_COMPILE)gcc -o ledtest ledtest.c
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
rm -f ledtest
bj-m += leddrv.o chip_led.o
编辑好以后make一下
把chip_led.ko、leddrv.ko、ledtest装入nfs文件系统。
book@100ask:~/winFile/qemu_bus_led_devicetree$ cp ledtest chip_led.ko leddrv.ko ~/nfs_rootfs/
(2)完成以上工作以后,启动qemu,挂载ubantu目录。
mount-tnfs-onolock,vers=310.0.2.2:/home/book/nfs_rootfs/mnt
#####################
加载驱动时一定要注意依赖关系,不然会报错。
#####################
(3)加载驱动,如下所示:
加载chip_led.ko时显示忙,可能是我笔记本缘由,但实际上加载成功了。
(4)查看设备树指定的硬件资源早已转换为设备节点,用于验证驱动是否正确。红框里早已标明,确实有四个设备节点。
(5)照亮led灯
注意四个led灯全是熄灭状态。
开始照亮第一个led灯./ledtest/dev/100ask_led0on
6.结束语
本文以一个简单的led为实验对象,描述了基本的驱动开发流程,并最终验证了驱动程序编撰的正确性。
文中只给出了一部份核心的代码用于介绍驱动开发的步骤,且这部份代码写于我研一初始嵌入式Linux,有不足之处还请多多责怪。