linux驱动入门

目录

1.前言

2.驱动开发流程

3.设备树

 4.驱动程序编写

1.确定主设备号

2.定义file_operations结构体

3.完善file_operations结构体

4.注册file_operations结构体

5.实现入口函数

6.实现出口函数

7.完善其他信息

5.实验

6. 结束语

1.前言

        之前使用过STM32的开发板,对嵌入式的裸机开发有一定的了解,在此基础上,又认真学习了嵌入式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$

打开设备树文件, vi 100ask_imx6ull_qemu.dts,这里我之前已经指定好了qemu中所有的led资源,注意设备树文件编写的格式。

100ask_led@0 {
                compatible = "100as,leddrv";
                pin = <GROUP_PIN(5, 3)>;
        };

        100ask_led@1 {
                compatible = "100as,leddrv";
                pin = <GROUP_PIN(1, 3)>;
        };

        100ask_led@2 {
                compatible = "100as,leddrv";
                pin = <GROUP_PIN(1, 5)>;
        };

        100ask_led@3 {
                compatible = "100as,leddrv";
                pin = <GROUP_PIN(1, 6)>;
        };

这里编写的时候也可以用到GPIO和Pinctrl子系统概念,由于是简单led,不再赘述。

之后就需要重新编译设备树文件,并把它放在qemu目录下。

 

 随后,可以在~/ubuntu-18.04_imx6ul_qemu_system/ imx6ull-system-image下看到该设备树文件

 4.驱动程序编写

1.确定主设备号

一般初始化为0,也可以让内核来分配。

/* 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灯,需要从用户态往内核写数据,主要完善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 %d\n", __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 %d\n", __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 %d\n", 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<<30);

			/* GPIO5  IOMUXC*/
	   		 IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x2290014, 4);
			gpio5 = ioremap(0x20AC000, sizeof(struct imx6ull_gpio));
				switch(PIN(g_ledpins[which]))
				{
					case 3:
					{
						*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = 5;
						gpio5->gdir |= (1<<3);
						break;
					}
					default : break;
				}
			break;
		}
		case 1:{
			/* 1. enable GPIO1 
			* CG13, b[27:266] = 0b11
			*/
			*CCM_CCGR1 |= (3<<26);

			iomux = ioremap(0x20e0000, sizeof(struct iomux)); //gpio1 iomux baseaddr
			gpio1 = ioremap(0x209C000, sizeof(struct imx6ull_gpio));
				switch(PIN(g_ledpins[which]))
				{
					case 3:
					{
						iomux->IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 = 5;
						gpio1->gdir |= (1<<3);
						break;
					}
					case 5:
					{
						iomux->IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO05 = 5;
						gpio1->gdir |= (1<<5);
						break;
					}
					case 6:
					{
						iomux->IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO06 = 5;
						gpio1->gdir |= (1<<6);
						break;
					}
					default : break;
				}
			break;
		}
		default : break;
	}
		
	return 0;
}

static int board_led_ctl(int which,char status)
{
	    printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");
	switch(GROUP(g_ledpins[which]))
	{
		case 1:
		{
			switch(PIN(g_ledpins[which]))
			{
				case 3:
				{
					if (status)  /* on : output 0 */
					gpio1->dr &= ~(1<<3);
					else         /* on : output 1 */
					gpio1->dr |= (1<<3);
					break;
				}
				case 5:
				{
					if (status)  /* on : output 0 */
					gpio1->dr &= ~(1<<5);
					else         /* on : output 1 */
					gpio1->dr |= (1<<5);
					break;
				}
				case 6:
				{
					if (status)  /* on : output 0 */
					gpio1->dr &= ~(1<<6);
					else         /* on : output 1 */
					gpio1->dr |= (1<<6);
					break;
				}
				default : break;
			}
			break;
		}
		case 5:
		{
			switch(PIN(g_ledpins[which]))
			{
				case 3:
				{
					if (status)  /* on : output 0 */
					gpio5->dr &= ~(1<<3);
					else         /* on : output 1 */
					gpio5->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 %d\n", __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 %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "qemu_100ask_led");
		return -1;
	}
		return 0;
}

6.实现出口函数

static void __exit led_exit(void)
{
	printk("%s %s line %d\n", __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 -t nfs -o nolock,vers=3  10.0.2.2:/home/book/nfs_rootfs /mnt

#####################

 加载驱动时一定要注意依赖关系,不然会报错。

#####################

(3)加载驱动,如下所示:

加载chip_led.ko时显示忙,可能是我电脑原因,但实际上加载成功了。

 (4)查看设备树指定的硬件资源已经转换为设备节点,用于验证驱动是否正确。红框里已经标注,确实有四个设备节点。

 (5)点亮led灯

注意四个led灯全是熄灭状态。

开始点亮第一个led灯   ./ledtest /dev/100ask_led0 on

6. 结束语

        本文以一个简单的led为实验对象,描述了基本的驱动开发流程,并最终验证了驱动程序编写的正确性。

        文中只给出了一部分核心的代码用于介绍驱动开发的步骤,且这部分代码写于我研一初始嵌入式Linux,有不足之处还请多多体谅。

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值