Linux 驱动开发(一)基础

1 基础指令

加载模块			modprobe xxx.ko(加载一个新模块时先要使用depmod命令)
卸载模块 		rmmod	xxx.ko
查看模块 		lsmod
查看设备使用		cat /proc/devices
查看设备树节点	ls /proc/device-tree
创建设备节点		mknod /dev/chrdevbase c 200 0
				其中“mknod”是创建节点命令,“/dev/chrdevbase”是要创建的节点文件,
				“c”表示这是个字符设备,“200”是设备的主设备号,“0”是设备的次设备号
是否加载成功		ls /dev 目录下是否存在

2 一个基本的字符设备驱动框架

搭建步骤
(1)首先需要指出驱动入口、出口、模块license、author

static int __init led_init(void)
{
	return 0}

static int __init led_exit(void)
{}

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("jimmy");

(2)定义设备结构体

	struct gpioled_dev
	{
	    dev_t devid;
	    int minor;
	    int major;
	    struct cdev cdev;
	    struct class *class;
	    struct device *device;
	};
	struct gpioled_dev gpioled;

(3)注册和取消注册字符设备驱动(成对操作)

  • 处理设备号
    先设定主设备号为0,若定义了设备号直接注册设备号;否则向系统申请设备号
	gpioled.major = 0;
    if(gpioled.major)
    {
        gpioled.devid = MKDEV(gpioled.major, 0);
        register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
    }
    else
    {
        alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);
        gpioled.major = MAJOR(gpioled.major);
        gpioled.minor = MINOR(gpioled.minor);
    }
    printk("major=%d, minor=%d\r\n", gpioled.major, gpioled.minor);

在驱动入口函数中注册好设备号之后,需要在驱动出口函数中取消注册设备号

	unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
  • 定义字符设备操作集
	static int led_open(struct inode *inode, struct file *filp)
	{
	    filp->private_data = &gpioled;
	    return 0;
	}
	
	static int led_release(struct inode *inode, struct file *filp)
	{
	    return 0;
	}
	
	static ssize_t led_write(struct file *filp, const char __user *buf, 
							 size_t count, loff_t *ppos)
	{
	    return 0;
	}
	
	/* 字符设备操作集 */
	static const struct file_operations led_fops = {
	    .owner   =  THIS_MODULE,
	    .open    =  led_open,
	    .write   =  led_write,
	    .release =  led_release,
	};
  • 初始化并添加cdev
	gpioled.cdev.owner = THIS_MODULE;
    cdev_init(&gpioled.cdev, &led_fops);
    cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);

同样地,在驱动出口函数中需要删除cdev

	cdev_del(&gpioled.cdev);
  • 创建类
	gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
    if(IS_ERR(gpioled.class))
    {
        return PTR_ERR(gpioled.class);
    }

同样地,在驱动出口函数中需要摧毁类

	class_destroy(gpioled.class);
  • 创建设备
	gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, 
									NULL, GPIOLED_NAME);
    if(IS_ERR(gpioled.device))
    {
        return PTR_ERR(gpioled.device);
    }

同样地,在驱动出口函数中需要摧毁设备

	device_destroy(gpioled.class, gpioled.devid);

3 使用pinctrl子系统和gpio子系统使用IO流程

pinctrl 子系统主要工作内容如下:
①、获取设备树中 pin 信息
②、根据获取到的 pin 信息来设置 pin 的复用功能
③、根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等

gpio子系统:
pinctrl 子系统重点是设置 PIN(有的 SOC 叫做 PAD)的复用和电气属性,如果 pinctrl 子系统将一个 PIN 复用为 GPIO 的话,那么接下来就要用到 gpio 子系统了。 gpio 子系统顾名思义,就是用于初始化 GPIO 并且提供相应的 API 函数,比如设置 GPIO为输入输出,读取 GPIO 的值等。 gpio 子系统的主要目的就是方便驱动开发者使用 gpio,驱动开发者在设备树中添加 gpio 相关信息,然后就可以在驱动程序中使用 gpio 子系统提供的 API函数来操作 GPIO, Linux 内核向驱动开发者屏蔽掉了 GPIO 的设置过程,极大的方便了驱动开发者使用 GPIO。

  • 添加pinctrl信息
/* jimmy LED */
pinctrl_led: ledgrp {
        fsl,pins = <
        MX6UL_PAD_GPIO1_IO03__GPIO1_IO03        0x10B0 /* LED0 */
        >;
        };
  • 检查当前设备树中要使用的IO有没有被其他设备使用,如果有要进行处理(很重要)
  • 添加设备节点,在设备节点中创建一个属性,此属性描述所使用的gpio
	gpioled	{
			compatible = "alientek,gpio_led";
			pinctl-names = "default";
			pinctl-0 = <&pinctrl_led>;
			led-gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
			status = "okay";
		};
  • 编写驱动,获取对应的gpio编号,并申请IO,成功以后即可使用IO
	/* 获取LED所对应的GPIO */
    gpioled.led_gpio = of_get_named_gpio(gpioled.nd,"led-gpios", 0);
    if(gpioled.led_gpio < 0){
        printk("cannot find led gpio\r\n");
        ret = -EINVAL;
        goto file_findnode;
    }
    printk("led gpio num = %d\r\n", gpioled.led_gpio);

    /* 申请IO (使用后需要释放) */
    ret = gpio_request(gpioled.led_gpio, "led-gpio");
    if(ret){
        printk("failed to request the led gpio!\r\n");
        ret = -EINVAL;
        goto file_findnode;
    }

    /* 使用IO,设置为输出 */
    ret = gpio_direction_output(gpioled.led_gpio, 1); //低电平点亮
    if(ret){
        goto file_setoutput;
    }

    /* 输出低电平,点亮LED */
    gpio_set_value(gpioled.led_gpio, 0);

4 Linux中断

4.1 API函数

  • 首先需要知道所使用的的中断的中断号
  • 申请中断,使用 request_irq 函数,此函数会激活中断
  • 不使用时释放中断,使用 free_irq 函数
  • 中断处理函数 irqreturn_t (*irq_handler_t) (int, void *)
  • 使能和禁止中断
    void enable_irq(unsigned int irq)
    void disable_irq(unsigned int irq)

4.2 上半部与下半部

在使用request_irq 申请中断的时候注册的中断服务函数属于中断处理的上半部,只要中断触发,那么中断处理函数就会执行。我们都知道中断处理函数一定要快点执行完毕,越短越好,但是现实往往是残酷的,有些中断处理过程就是比较费时间,我们必须要对其进行处理,缩小中断处理函数的执行时间。

上半部:上半部就是中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可
以放在上半部完成。
下半部:如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部
去执行,这样中断处理函数就会快进快出。

因此, Linux 内核将中断分为上半部和下半部的主要目的就是实现中断处理函数的快进快
出,那些对时间敏感、执行速度快的操作可以放到中断处理函数中,也就是上半部。剩下的所
有工作都可以放到下半部去执行,比如在上半部将数据拷贝到内存中,关于数据的具体处理就
可以放到下半部去执行。至于哪些代码属于上半部,哪些代码属于下半部并没有明确的规定,
一切根据实际使用情况去判断。这里有一些可以借鉴的参考点:A)如果要处理的内容不希望被其他中断打断,那么可以放到上半部。B)如果要处理的任务对时间敏感,可以放到上半部。C)如果要处理的任务与硬件有关,可以放到上半部。D)除了上述三点以外的其他任务,优先考虑放到下半部。

上半部处理很简单,直接编写中断处理函数就行了,关键是下半部该怎么做呢? Linux 内核提供了多种下半部机制:① 软中断 ② tasklet ③ 工作队列

5 阻塞IO与非阻塞IO

这里的IO并不是指GPIO而是input/output,是应用程序对驱动设备的输入/输出操作。
阻塞:当资源不可用的时候,应用程序就会挂起。当资源可用的时候唤醒。
非阻塞:当资源不可用的时候,应用程序轮询等待或放弃。
阻塞IO访问示意图:
在这里插入图片描述
非阻塞IO访问示意图:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值