ARM嵌入式学习(二十一)--- Platform总线结合dts、gpio子系统、中断和错误处理

目录

[一 、Platform总线结合dts](#一 、Platform总线结合dts)

与之前的区别:

1.相当于把driver.c和plat.c结合起来,通过of_device_id函数和paltform来进行匹配

2.在初始化中注册palt_driver,在probe函数中(即匹配成功后)写此设备号注册以及查找设备节点(of_find_node_by_path函数)

二、gpio子系统

函数解释:

设备树:

1.这里面是引脚配置和电气属性

2.表示上电后的默认电平

三、中断

函数解释:

设备树:

睡眠与唤醒机制:

四、错误处理

五、总结与补充

遇到的问题:

(1)模块加载不进去,返回错误码-16(busy)

(2)模块加载不进去,返回错误码-2

[(3)为什么按键不用of_get_named_gpio显式获取 GPIO](#(3)为什么按键不用of_get_named_gpio显式获取 GPIO)

补充:

(1)Vim替换字符串:

(2)IRQF_ONESHOT中断宏:


一 、Platform总线结合dts

在上一篇文章中,我们使用的是仅 Platform 总线和仅dts的方法。

**现在我们使用如今最新的方法:**Platform总线结合dts

  • 传统方式(仅 Platform ) :每支持一块新板子,就要在 arch/arm/mach-xxx/ 下新增一个 C 文件(或少则几百行,多则上千行),里面充满 platform_device_registerresource 定义、gpio 数组等。这些代码大多重复且难以阅读。

  • DTS 方式(Platform总线结合dts :板级描述变成几百行结构化的文本,不再需要在内核源码里为每块板子增加 C 文件。主流架构的 mach-xxx 目录如今只剩下少量核心代码,大量板级文件已被删除。

结果:内核更干净,维护者更容易审查硬件描述。

设备树这种方式添加到总线的好处在于,它会匹配上了再寻找设备节点,少做很多无用功

匹配的是compatible变量,跟名字pt_led和name1没关系。

代码为:

复制代码
#include <linux/init.h>
#include <linux/printk.h>
#include <linux/kdev_t.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/export.h>
#include <asm/uaccess.h>
#include <asm/string.h>
#include <asm/io.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/of.h>

#define DEV_NAME "led"
static volatile unsigned int * sw_mux;
static volatile unsigned int * sw_pad;
static volatile unsigned int * gpio1_dr;
static volatile unsigned int * gpio1_gdir;

static void led_init(void)
{
    *sw_mux = 0x05;
    *sw_pad = 0x10b0;
    *gpio1_gdir |= (1 << 3);
    *gpio1_dr |= (1 << 3);
}

static void led_on(void)
{
    *gpio1_dr &= ~(1 << 3);
}

static void led_off(void)
{
    *gpio1_dr |= (1 << 3);
}

static int open(struct inode * node, struct file * file)
{
	led_init();
	printk("led  open...\n");
	return 0;
}

static ssize_t read(struct file * file, char __user * buf, size_t len, loff_t * offset)
{
	//copy_to_user();
	printk("led  read...\n");
	return 0;
}

static ssize_t write(struct file * file, const char __user * buf, size_t len, loff_t * offset)
{
	// "ledon"  on       "ledoff"   off
	unsigned char data[10] = {0};
	size_t len_cp = len < sizeof(data) ? len : sizeof data;
	int size_cp = copy_from_user(data, buf, len_cp);
	if(size_cp < 0)
		return size_cp;

	if(!strcmp(buf, "ledon"))
		led_on();
	else if(!(strcmp(buf, "ledoff")))
		led_off();
	else
		return -EINVAL;

	printk("led  write...\n");

	return size_cp;
}

static int close(struct inode * node, struct file * file)
{
	led_off();
	printk("led  close...\n");
	return 0;
}

static struct file_operations fops = 
{
	.owner = THIS_MODULE,
	.open = open,
	.read = read,
	.write = write,
	.release = close
};

static struct miscdevice misc = 
{
	.minor = MISC_DYNAMIC_MINOR,
	.name = DEV_NAME,
	.fops = &fops
};

static const struct of_device_id match_table[] = 
{
    [0] = {.compatible = "pt-led"}
};

static struct platform_driver drv = 
{
	.probe = probe,
	.remove = remove,
	.driver = 
	{
		.name = DEV_NAME
        .of_match_table = match_table           
	}
};

static int __init led1_init(void)
{
	int ret = platform_driver_register(&drv);
	if(ret < 0)
		goto err_reg;

	printk("platform_driver_register  ...\n");
	return 0;

err_reg:
	platform_driver_unregister(&drv);
	printk("platform_driver_register  failed\n");
	return ret;
}

static int probe(struct platform_device * pdev)
{

	struct device_node * pnode;
	const char * pcom;
	const char * pname1;
	u32 led_array[8] = {0};
	int ret = misc_register(&misc);
	if(ret < 0)
		goto err_misc;

	pnode = of_find_node_by_path("/pt_led");
	if(pnode == NULL)
	{
		printk("of_find_node_by_path  err\n");
		return -1;
	}
	
	of_property_read_string(pnode, "compatible", &pcom);
	of_property_read_string(pnode, "name1", &pname1);
	printk("led compatible = %s  name1 = %s\n", pcom, pname1);

	of_property_read_u32_array(pnode, "reg", led_array, sizeof(led_array) / sizeof(led_array[0]));

	sw_mux = ioremap(led_array[0], led_array[1]);
	sw_pad = ioremap(led_array[2], led_array[3]);
	gpio1_gdir = ioremap(led_array[4], led_array[5]);
	gpio1_dr = ioremap(led_array[6], led_array[7]);

    printk("led_init    ##############\n");
    return 0;

err_misc:
	misc_deregister(&misc);
	printk("led_init failed  ret = %d\n", ret);
	return ret;
}

static void __exit led1_exit(void)
{
	iounmap(gpio1_gdir);
	iounmap(gpio1_dr);
	iounmap(sw_pad);
	iounmap(sw_mux);
	misc_deregister(&misc);
    printk("led_exit    ##############\n");
}

module_init(led1_init);
module_exit(led1_exit);
MODULE_LICENSE("GPL");

与之前的区别:

1.相当于把driver.c和plat.c结合起来,通过of_device_id函数和paltform来进行匹配

2.在初始化中注册palt_driver,在probe函数中(即匹配成功后)写此设备号注册以及查找设备节点(of_find_node_by_path函数)

二、gpio子系统

Linux 内核提供了 GPIO 子系统,用于统一管理各个 SoC 的 GPIO 控制器,并为驱动开发者提供标准 API。

这样我就不需要使用 ioremap函数转换地址了,只需要调用 GPIO 子系统提供的标准 API 即可。GPIO 控制器驱动内部已经做了 ioremap。

需要用到这两个头文件

#include <linux/of_gpio.h>

#include <linux/gpio.h>

将上面的代码改写为:

复制代码
#include <linux/init.h>
#include <linux/printk.h>
#include <linux/kdev_t.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/export.h>
#include <asm/uaccess.h>
#include <asm/string.h>
#include <asm/io.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>

#define DEV_NAME "led"
static int led_gpio;
#define LED_ON 0
#define LED_OFF 1

static void led_init(void)
{
	gpio_direction_output(led_gpio, LED_OFF);
}

static void led_on(void)
{
	gpio_set_value(led_gpio, LED_ON);
}

static void led_off(void)
{
	gpio_set_value(led_gpio, LED_OFF);
}

static int open(struct inode * node, struct file * file)
{
	led_init();
	printk("led  open...\n");
	return 0;
}

static ssize_t read(struct file * file, char __user * buf, size_t len, loff_t * offset)
{
	//copy_to_user();
	printk("led  read...\n");
	return 0;
}

static ssize_t write(struct file * file, const char __user * buf, size_t len, loff_t * offset)
{
	// "ledon"  on       "ledoff"   off
	unsigned char data[10] = {0};
	size_t len_cp = len < sizeof(data) ? len : sizeof data;
	int size_cp = copy_from_user(data, buf, len_cp);
	if(size_cp < 0)
		return size_cp;

	if(!strcmp(buf, "ledon"))
		led_on();
	else if(!(strcmp(buf, "ledoff")))
		led_off();
	else
		return -EINVAL;

	printk("led  write...\n");

	return size_cp;
}

static int close(struct inode * node, struct file * file)
{
	led_off();
	printk("led  close...\n");
	return 0;
}

static struct file_operations fops = 
{
	.owner = THIS_MODULE,
	.open = open,
	.read = read,
	.write = write,
	.release = close
};

static struct miscdevice misc = 
{
	.minor = MISC_DYNAMIC_MINOR,
	.name = DEV_NAME,
	.fops = &fops
};

static int probe(struct platform_device * pdev)
{
	struct device_node * pnode;

	int ret = misc_register(&misc);
	if(IS_ERR_VALUE(ret))
		goto err_misc;

	pnode = of_find_node_by_path("/pt_gpioled");
	if(IS_ERR(pnode))
	{
		ret = PTR_ERR(pnode);
		goto err_find_node;
	}
	
	led_gpio = of_get_named_gpio(pnode, "led-gpio", 0);
	gpio_request(led_gpio, "red_led");

	gpio_direction_output(led_gpio, LED_OFF);

    printk("probe   led misc_register    ##############\n");
    return 0;


err_find_node:
	printk("of_find_node_by_path  err\n");

err_misc:
	printk("led  probe failed  ret = %d\n", ret);
	misc_deregister(&misc);
	return ret;
}

static int remove(struct platform_device * pdev)
{
	gpio_free(led_gpio);
	misc_deregister(&misc);
    printk("remove  led misc_deregister    ##############\n");

	return 0;
}
	
static const struct of_device_id match_table[] = 
{
	[0] = {.compatible = "pt-gpioled"}
};

static struct platform_driver drv = 
{
	.probe = probe,
	.remove = remove,
	.driver = 
	{
		.name = DEV_NAME,
		.of_match_table = match_table
	}
};

static int __init led1_init(void)
{
	int ret = platform_driver_register(&drv);
	if(ret < 0)
		goto err_reg;

	printk("platform_driver_register  ...\n");
	return 0;

err_reg:
	printk("platform_driver_register  failed ret = %d\n", ret);
	platform_driver_unregister(&drv);
	return ret;
}

static void __exit led1_exit(void)
{
	platform_driver_unregister(&drv);
	printk("platform_driver_unregister  ...\n");
}

module_init(led1_init);
module_exit(led1_exit);
MODULE_LICENSE("GPL");

函数解释:

(1)led_gpio = of_get_named_gpio(pnode, "led-gpio", 0);//查找设备树的led信息获取gpio编号:

  • np:设备树节点指针

  • propname:属性名,如 "led-gpio" 这个必须和设备树的变量名一致

  • index(可以理解为数组下标:索引(因为属性可能包含多个 GPIO,如 gpios = <&gpio1 3 0>, <&gpio1 4 0>;

  • 返回值:GPIO 编号(整型),负数表示错误。

(2)gpio_request(led_gpio, "red_led"); //获取gpio,后面的是标签随意写

(3)gpio_direction_output(led_gpio, LED_OFF);//设置为输出模式,后面是输出1(宏)

(4)gpio_direction_output(led_gpio)//设置为输入模式

(5)gpio_free(led_gpio);//释放gpio

设备树:

1.这里面是引脚配置和电气属性

2.表示上电后的默认电平

三、中断

通过led的学习我们同样可以写一个按键的驱动代码:

复制代码
#include <linux/init.h>
#include <linux/printk.h>
#include <linux/kdev_t.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/export.h>
#include <asm/uaccess.h>
#include <asm/string.h>
#include <asm/io.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>

#define DEV_NAME "key"
static int key_gpio;
static unsigned int irq_key_num;
#define key_ON 0
#define key_OFF 1
static int arg = 100;

static irqreturn_t key_irq_handler(int dev_num, void * dev)
{
	printk("dev_num = %d  dev = %d\n", dev_num, *(int *)dev);
	return IRQ_HANDLED;
}

static int open(struct inode * node, struct file * file)
{
	printk("key  open...\n");
	return 0;
}

static ssize_t read(struct file * file, char __user * buf, size_t len, loff_t * offset)
{
	//copy_to_user();
	printk("key  read...\n");
	return 0;
}

static int close(struct inode * node, struct file * file)
{
	printk("key  close...\n");
	return 0;
}

static struct file_operations fops = 
{
	.owner = THIS_MODULE,
	.open = open,
	.read = read,
	.release = close
};

static struct miscdevice misc = 
{
	.minor = MISC_DYNAMIC_MINOR,
	.name = DEV_NAME,
	.fops = &fops
};

static int probe(struct platform_device * pdev)
{
	struct device_node * pnode;

	int ret = misc_register(&misc);
	if(IS_ERR_VALUE(ret))
		goto err_misc;

	pnode = of_find_node_by_path("/pt_key");
	if(IS_ERR(pnode))
	{
		ret = PTR_ERR(pnode);
		goto err_find_node;
	}
	
	key_gpio = of_get_named_gpio(pnode, "key-gpio", 0);
	if(key_gpio < 0)
	{
		ret = key_gpio;
		goto err_get_gpio;
	}

	ret = gpio_request(key_gpio, "pt_key");
	if(ret < 0)
		goto err_gpio_request;
	
	irq_key_num = gpio_to_irq(key_gpio);
	if(irq_key_num < 0)
	{
		ret = irq_key_num;
		goto err_gpio_to_irq;
	}

	ret = request_irq(irq_key_num, key_irq_handler, IRQF_TRIGGER_FALLING, "key_irq", &arg);
	if(ret < 0)
		goto err_request_irq;

    printk("probe   key misc_register    ##############\n");
    return 0;

err_request_irq:
	disable_irq(irq_key_num);
	free_irq(irq_key_num, &arg);
	printk("key err_request_irq\n");
err_gpio_to_irq:
	printk("key err_gpio_to_irq\n");
err_gpio_request:
	printk("key err_gpio_request\n");
err_get_gpio:
	printk("key err_get_gpio\n");
err_find_node:
	printk("of_find_node_by_path  err\n");

err_misc:
	printk("key  probe faikey  ret = %d\n", ret);
	misc_deregister(&misc);
	return ret;
}

static int remove(struct platform_device * pdev)
{
	disable_irq(irq_key_num);
	free_irq(irq_key_num, &arg);
	gpio_free(key_gpio);
	misc_deregister(&misc);
    printk("remove  key misc_deregister    ##############\n");

	return 0;
}
	
static const struct of_device_id match_table[] = 
{
	[0] = {.compatible = "pt-key"}
};

static struct platform_driver drv = 
{
	.probe = probe,
	.remove = remove,
	.driver = 
	{
		.name = DEV_NAME,
		.of_match_table = match_table
	}
};

static int __init key1_init(void)
{
	int ret = platform_driver_register(&drv);
	if(ret < 0)
		goto err_reg;

	printk("platform_driver_register  ...\n");
	return 0;

err_reg:
	printk("platform_driver_register  faikey ret = %d\n", ret);
	platform_driver_unregister(&drv);
	return ret;
}

static void __exit key1_exit(void)
{
	platform_driver_unregister(&drv);
	printk("platform_driver_unregister  ...\n");
}

module_init(key1_init);
module_exit(key1_exit);
MODULE_LICENSE("GPL");

函数解释:

(1)static irqreturn_t key_irq_handler(int dev_num, void * dev)中断触发后的句柄,后面的void*dev是中断请求传的参数

(2)irq_key_num = gpio_to_irq(key_gpio);//获取中断号

(3)ret = request_irq(irq_key_num, key_irq_handler, IRQF_TRIGGER_FALLING, "key_irq", &arg);

参数1:中断号

参数2:中断句柄

参数3:触发方式

参数4:中断名

参数5:传入的参数,代码这里写的全局变量static int arg = 100;

(4)结束中断

disable_irq(irq_key_num); //关闭中断

free_irq(irq_key_num, &arg);//释放中断

设备树:

在我们的dts中添加这两个信息

睡眠与唤醒机制:

我们可以在原来的代码里面使用标志位然后用while来循环判断标志位来实现各种功能,但这种方法非常浪费资源,CPU一直空转,这里我们加入一个wait_queue_head_t,它 是 Linux 内核中 等待队列头 的类型,定义在 <linux/wait.h> 中。

相关函数:

init_waitqueue_head(&wq); //初始化等待队列

wait_event_interruptible(wq, condition); //睡眠,condition为我们的标志位,为0则一直休眠,为1则唤醒

wake_up_interruptible(&wq); //唤醒,判断标志位是否为1,不为1继续休眠

加入机制后的中断代码为:

复制代码
#include <linux/init.h>
#include <linux/printk.h>
#include <linux/kdev_t.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/export.h>
#include <asm/uaccess.h>
#include <asm/string.h>
#include <asm/io.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#include <linux/of_irq.h>
#include <linux/wait.h>
#include <linux/sched.h>

#define DEV_NAME "key"
static unsigned int irq_key_num;
static int arg = 100;
static wait_queue_head_t wq;
static int condition;

static irqreturn_t key_irq_handler(int dev_num, void * dev)
{
	condition = 1;
	wake_up_interruptible(&wq);
	printk("dev_num = %d  dev = %d\n", dev_num, *(int *)dev);
	return IRQ_HANDLED;
}

static int open(struct inode * node, struct file * file)
{
	printk("key  open...\n");
	return 0;
}

static ssize_t read(struct file * file, char __user * buf, size_t len, loff_t * offset)
{
	condition = 0;
	wait_event_interruptible(wq, condition);
	//copy_to_user();
	printk("key  read...\n");
	return 0;
}

static int close(struct inode * node, struct file * file)
{
	printk("key  close...\n");
	return 0;
}

static struct file_operations fops = 
{
	.owner = THIS_MODULE,
	.open = open,
	.read = read,
	.release = close
};

static struct miscdevice misc = 
{
	.minor = MISC_DYNAMIC_MINOR,
	.name = DEV_NAME,
	.fops = &fops
};

static int probe(struct platform_device * pdev)
{
	struct device_node * pnode;

	int ret = misc_register(&misc);
	if(IS_ERR_VALUE(ret))
		goto err_misc;

	pnode = of_find_node_by_path("/pt_key");
	if(IS_ERR(pnode))
	{
		ret = PTR_ERR(pnode);
		goto err_find_node;
	}

	irq_key_num = irq_of_parse_and_map(pnode, 0);
	if(irq_key_num < 0)
	{
		ret = irq_key_num;
		goto err_of_parse;
	}

	ret = request_irq(irq_key_num, key_irq_handler, IRQF_TRIGGER_FALLING, "key_irq", &arg);
	if(ret < 0)
		goto err_request_irq;

	init_waitqueue_head(&wq);

    printk("probe   key misc_register    ##############\n");
    return 0;

err_request_irq:
	disable_irq(irq_key_num);
	free_irq(irq_key_num, &arg);
	printk("key err_request_irq\n");
err_of_parse:
	printk("key err_of_parse\n");
err_find_node:
	printk("of_find_node_by_path  err\n");
err_misc:
	printk("key  probe faikey  ret = %d\n", ret);
	misc_deregister(&misc);
	return ret;
}

static int remove(struct platform_device * pdev)
{
	disable_irq(irq_key_num);
	free_irq(irq_key_num, &arg);
	misc_deregister(&misc);
    printk("remove  key misc_deregister    ##############\n");

	return 0;
}
	
static const struct of_device_id match_table[] = 
{
	[0] = {.compatible = "pt-key"}
};

static struct platform_driver drv = 
{
	.probe = probe,
	.remove = remove,
	.driver = 
	{
		.name = DEV_NAME,
		.of_match_table = match_table
	}
};

static int __init key1_init(void)
{
	int ret = platform_driver_register(&drv);
	if(ret < 0)
		goto err_reg;

	printk("platform_driver_register  ...\n");
	return 0;

err_reg:
	printk("platform_driver_register  faikey ret = %d\n", ret);
	platform_driver_unregister(&drv);
	return ret;
}

static void __exit key1_exit(void)
{
	platform_driver_unregister(&drv);
	printk("platform_driver_unregister  ...\n");
}

module_init(key1_init);
module_exit(key1_exit);
MODULE_LICENSE("GPL");

这样在我们应用层read的时候函数就会阻塞(休眠),当我们中断后就会继续执行了。

四、错误处理

内核里面的出错必须处理,不处理系统会崩

某些函数的返回值是指针,这种怎么判断它的错误码?

这里需要用到我们的错误处理函数:

宏/函数 输入类型 输出/判断 适用场景
IS_ERR_VALUE(ret) unsigned long 是否为错误值(来自负数错误码) 整数返回值函数
IS_ERR(pnode) void * 是否为错误指针 指针返回值函数
PTR_ERR(pnode) void * 错误码(负数) 从错误指针提取错误码

示例:

五、总结与补充

遇到的问题:

(1)模块加载不进去,返回错误码-16(busy)

解决办法:把默认设备树中的key和led相关的全部删掉,使用我们自己写的即可

(2)模块加载不进去,返回错误码-2

解决办法:检查driver是否与设备节点匹配(compatible、of_find_node_by_path和of_get_named_gpio)

(3)为什么按键不用of_get_named_gpio显式获取 GPIO

我们的按键中断代码只关心中断事件,并不需要直接读取按键的 GPIO 电平(比如做消抖、轮询)。

补充:

(1)Vim替换字符串:

(2)IRQF_ONESHOT中断宏:

数据过来的时候会触发中断,在没有把数据读空之前,再有数据来就不触发中断

相关推荐
CC城子2 小时前
EtherCAT的igh学习与研究(一)
学习·ethercat
xian_wwq2 小时前
【学习笔记】GB/T 20986-2023 详解,10 类网络安全事件分类
笔记·学习·web安全
鱼鳞_2 小时前
Java学习笔记_Day27(Stream流)
java·笔记·学习
_李小白2 小时前
【OSG学习笔记】Day 42: OSG 动态场景安全修改
笔记·学习·安全
H_老邪2 小时前
Docker 学习之路-从入门到放弃:7
学习·docker·容器
头疼的程序员2 小时前
计算机网络:自顶向下方法(第七版)第八章 学习分享(四)
学习·计算机网络
m0_677904842 小时前
K8s学习
java·学习·kubernetes
|_⊙2 小时前
红黑树 (C++)
开发语言·c++·学习
ByteCraze3 小时前
手写高性能虚拟列表(详解!!!)
javascript·学习