文章目录
本人学习完韦老师的视频,因此来复习巩固,写以笔记记之。
韦老师的课比较难,第一遍不知道在说什么,但是坚持看完一遍,再来复习,基本上就水到渠成了。
看完视频复习的同学观看最佳!
基于 IMX6ULL-PRO
参考视频 Linux快速入门到精通视频
参考资料:01_嵌入式Linux应用开发完全手册V5.1_IMX6ULL_Pro开发板.pdf
一、设备树
1-1 设备树的引入与作用
设备树DTS(device tree source):设备树只是用来给内核里的驱动程序,指定硬件的信息
。比如 LED驱动,在内核的驱动程序里去操作寄存器,但是操作哪一个引脚?这由设备树指定。
一种描述硬件 连接信息的数据结构,采用**结点(node)和属性(property)**嵌套构成树形结构。
编写设备树文件,需要编译为dtb(device tree blob)文件,内核使用的是dtb文件。
/sys/firmware/devicetree 目录下是以目录结构程现的 dtb 文件 , 根节点对应base目录,base目录下ls可呈现出子节点。
arm板是设备树信息 cd /sys/firmware/devicetree/base/
字符串打印:使用cat命令;数值打印:使用hexdump命令
总线树结构
1-2 设备树dts
/dts-v1: 表示版本信息
/:根节点
cpus: CPU节点
memory: memory节点
格式:name=value
value可以为32位数据,可以没有,可以是字符串。
(1) cell表示设备树中数据的最小单位,一个32位数据,用尖括号包围
interrupts = <17 0xc>;
(2) 64bit 数据使用 2 个cell 来表示,用尖括号包围
reg= <0 0x20000000>;
(3) 字符串,用双引号包围起来
compatible="ns16550";
(4) 字节序列用中括号包围起来
local mac address = [00 00 12 34 56 78]; //每个byte使用2个16进制数来表示
(5) 可以是各种值的组合 , 用逗号隔开
example = <0xf00f0000 19>, "a st range property format";
1-3 设备树dts语法
(1) 基本单元node
设备树中的基本单元,被称为”node"
[label:] node name[@unit address] {
[properties definitions]
[child nodes]
};
label是标号,可以省略。 label的作用是为了方便地引用node
形如
uart0: uart@fe001000 {
compatible="ns16550";
reg=<0xfe001000 0x100>;
};
可以在根节点外使用uart0引用uart
&uart0{
status = “disabled”;
};
(2) 属性
① #address cells 、#size cells
cell指一个32位的数值
address cells :address要用多少个32位数来表示起始地址
size cells: size要用多少个32位数来表示大小
② reg
reg的本意是register,用来表示寄存器地址。
用多少个32位的数来表示address和size,由其父节点的address cells 、size cells 决定。
/{
#address cells = <1>;
#size cells = <1>;
memory{
reg = <0x80000000 0x20000000>;
};
};
③ compatible
表示兼容,compatible的值,建议取这样的形式:“manufacturer, model”,即“厂家名 模块名”。
④ model
model属性与 compatible 属性有些类似,但是有差别。model用来准确地定义这个硬件是什么。
/{
compatible = "samsung,smdk2440", "samsung,
model = "jz2440_v3";
};
⑤ status
定义设备的运行状态
okay:设备正常允许;disabled:不可操作;fail:发生严重错误,需修复
&uart0{
status = “disabled”;
};
1-4 内核对设备树的处理
一个单板启动时,uboot 先运行,它的作用是启动内核。 Uboot 会把内核和设备树文件都读入内存,然后启动内核。在启动内核时会把设备树在内存中的地址告诉内核。
内核处理过程
1、配置文件dts在 PC机上被编译为 dtb文件;( 编译设备树命令 make dtbs)
2、u-boot把 dtb文件传给内核;
3、内核解析 dtb文件,把每一个节点都转换为 device_node
结构体;
4、对于某些 device_node结构体,会被转换为 platform_device
结构体。
根节点被保存在全局变量of_root 中,从of_root 开始可以访问到任意节点
哪些设备树节点会被转换为 platform_device
① 根节点下含有 compatile属性的子节点
② 含有特定 compatile属性的节点的子节点
一个节点的compatile 属性 ,它的值是4者之一:“simple-bus”, “simple-mfd”, “isa”, “arm,amba-bus”, 那么它的子节点(需含compatile 属性)也可以转换为 platform_device
③ 总线 I2C、SPI节点下的子节点不转换为platform_device
platform_device 中含有resource 数组 , 它来自device_node的reg, interrupts 属性
platform_device.dev.of_node 指向 device_node, 可以通过它获得其他属性
device_node结构体转换为platform_device结构体图解
含有设备树信息的platform_devic
e和platform_driver
完整匹配过程
比较设备树信息如下
platform_device结构体
struct platform_device->struct device dev->struct device_node *of_node
platform_driver结构体
struct platform_driver->struct device_driver driver->const struct of_device_id *of_match_table
设备树中的一些函数
设备树中的reg属性、interrupts 属性也会被转换为resource。这时使用这个函数取出这些资源。
struct resource *platform_get_resource(struct platform_device *dev,
unsigned int type, unsigned int num)
根据路径找到节点,根据路径找到节点,比如“/”就对应根节点,“ /memory ”对应 memory 节点。
static inline struct device_node *of_find_node_by_path(const char *path);
根据名字找到节点,节点如果定义了name 属性,那我们可以根据名字找到它。
extern struct device_node *of_find_node_by_name(struct device_node *from,const char *name);
1-5 设备树的LED驱动程序
设备树LED驱动框架
一般性匹配方法,设备树中的compatible属性与platform_driver结构体中的struct device_driver driver
中的const struct of_device_id *of_match_table
中的成员变量compatible进行匹配
读某个整数 u32/u64 函数原型为
extern int of_property_read_u32(const struct device_node *np,
const char *propname, u32 *out_value);
在设备树中,节点大概是这样
xxx_node {
name2 = <0x60000000>;
};
调用 of_property_read_u32 (np, “name2”, &val) 时,val将得到值0x60000000
(1) 查看注册进内核的所有platform_device
/sys/devices/platform
一个设备对应一个目录,进入某个目录后,如果它有"driver
"子目录软连接,就
表示这个 platform_device跟某个platform_driver配对了。
(2) 查看注册进内核的所有platform_driver
/sys/bus/platform/drivers
只需要在设备树中设置硬件信息,platform_dirver中获取硬件信息
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; /* associated device tree 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;
}
二、按键
2-1 APP读取按键的4种方法
在做单片机开发时,要读取GPIO 按键,我们通常是执行一个循环,不断地检测 GPIO 引脚电平有没有发生变化。
但是在Linux系统中,读取GPIO按键要考虑到效率,引入了很多种方法:查询方式(非阻塞)、休眠-唤醒(阻塞方式) 、poll方式、异步通知方式。
(1) 查询方式
(2) 休眠-唤醒方式
驱动程序中构造、注册一个file_operations 结构体,里面提供有对应的open、read函数。
(1) APP调用open时,导致驱动中对应的open函数被调用,在里面配置GPIO为输入引脚;并且注册GPIO的中断处理函数。
(2) APP调用read时,导致驱动中对应的read函数被调用,如果有按键数据则直接返回给APP;否则APP在内核态休眠。
当用户按下按键时,GPIO中断被触发,导致驱动程序之前注册的中断服务程序被执行。它会记录按键数据,并唤醒休眠中的APP
。
APP被唤醒后继续在内核态运行,即继续执行驱动代码,把按键数据返回给APP的用户空间。
(3) poll 方式
APP调用poll 或 select 函数,意图是“查询”是否有数据,这2个函数都可以指定一个超时时间,即在这段时间内没有数据的话就返回错误。这会导致驱动中对应的poll 函数被调用,如果有按键数据则直接返回给APP ;否则APP在内核态休眠一段时间。
(1) 当用户按下按键时,GPIO中断被触发,导致驱动程序之前注册的中断服务程序被执行。它会记录按键数据,并唤醒休眠中的APP 。
(2) 如果用户没按下按键,但是超时时间到了,内核也会唤醒APP 。
所以APP被唤醒有 2 种原因:用户操作了按键
或者超时
。被唤醒的APP在内核态继续运行,即继续执行驱动代码,把“状态”返回给APP的用户空间 。
(3) APP得到poll/select 函数的返回结果后,如果确认是有数据的,则再调用read函数,这会导致驱动中的read函数被调用,这时驱动程序中含有数据,会直接返回数据。
(4) 异步通知方式
异步通知的实现原理是:内核给APP发信号。信号有很多种,如:SIGIO( I/O 事件发生)。
驱动程序中构造、注册一个file_operations 结构体,里面提供有对应的open、read、fasync 函 数。
(1) APP调用open时,导致驱动中对应的open函数被调用,在里面配置GPIO为输入引脚;并且注册GPIO的中断处理函数。
(2) APP给信号SIGIO注册自己的处理函数:my_signal_fun
(3) APP调用fcntl函数,把驱动程序的flag 改为FASYNC,这会导致驱动程序的fasync 函数被调用,它只是简单记录进程PID 。
(4) 当用户按下按键时,GPIO中断被触发,导致驱动程序之前注册的中断服务程序被执行。它会记录按键数据,然后给进程PID发送SIGIO信号。
(5) APP收到信号后会被打断,先执行信号处理函数:在信号处理函数中可以去调用read函数读取按键值。
(6) 信号处理函数返回后,APP会继续执行原先被打断的代码。
列出所有进程
ps -A
给某个进程发信号
kill -SIGIO 进程PID
2-2 查询方式的按键驱动程序框架
怎么操作寄存器?从芯片手册得到对应寄存器的物理地址,在驱动程序中使用ioremap函数映射得到虚拟地址。驱动程序中使用虚拟地址去访问寄存器。
(1) button_drv.c分配 /设置 /注册 file_operations结构体起承上启下的作用,向上提供button_open、button_read 供 APP调用。而这2个函数又会调用底层硬件提供的p_button_opr中的init、read函数操作硬件。
button_drv.c
static int major = 0;
static struct button_operations *p_button_opr;
static struct class *button_class;
static int button_open (struct inode *inode, struct file *file)
{
int minor = iminor(inode);
p_button_opr->init(minor);
return 0;
}
static ssize_t button_read (struct file *file, char __user *buf, size_t size, loff_t *off)
{
unsigned int minor = iminor(file_inode(file));
char level;
int err;
level = p_button_opr->read(minor);
err = copy_to_user(buf, &level, 1);
return 0;
}
static struct file_operations button_fops = {
.open = button_open,
.read = button_read,
};
void register_button_operation(struct button_operations *opr)
{
int i;
p_button_opr = opr; /*底层代码来向上一层提供这个结构体*/
for(i =0; i < opr->count; i++){
/*自动构造设备节点*/
device_create(button_class, NULL, MKDEV(major,i), NULL, "100ask_button%d", i);
}
}
void unregister_button_operation(void)
{
int i;
for(i =0; i < p_button_opr->count; i++){
device_destroy(button_class, MKDEV(major,i));
}
}
EXPORT_SYMBOL(register_button_operation);
EXPORT_SYMBOL(unregister_button_operation);
int __init button_init(void)
{
major = register_chrdev(0, "100ask_button", &button_fops);
button_class = class_create(THIS_MODULE, "cardman_4040");
if (IS_ERR(button_class))
return -1;
return 0;
}
void __exit button_exit(void)
{
class_destroy(button_class);
unregister_chrdev(major, "100ask_button");
}
module_init(button_init);
module_exit(button_exit);
MODULE_LICENSE("GPL");
(2) board_xxx.c分配 /设置 /注册 button_operations结构体,这个结构体是我们自己抽象出来的,里面定义单板xxx的按键操作函数。这样的结构易于扩展,对于不同的单板,只需要替换board_xxx.c 提供自己的button_operations 结构体即可。
board_xxx.c
static void board_xxx_button_init_gpio(int which)
{
printk("%s %s %d, init gpio for button %d\n", __FILE__, __FUNCTION__, __LINE__, which);
}
static int board_xxx_button_read_gpio(int which)
{
printk("%s %s %d, read gpio for button %d\n", __FILE__, __FUNCTION__, __LINE__, which);
return 1;
}
static struct button_operations my_buttons_ops = {
.count = 2,
.init = board_xxx_button_init_gpio,
.read = board_xxx_button_read_gpio,
};
int board_xxx_button_init(void)
{
register_button_operation(&my_buttons_ops);
return 0;
}
void board_xxx_button_exit(void)
{
unregister_button_operation();
}
module_init(board_xxx_button_init);
module_exit(board_xxx_button_exit);
MODULE_LICENSE("GPL");
2-3 具体单板的按键驱动程序(详解)
硬件配置部分
(1) 首先查看开发板的原理图,确认按键的GPIO引脚,如下图所示分别为GPIO5_IO01、GPIO4_IO14
(2) 使能GPIO引脚,找到芯片手册第18章CCM时钟控制模块,翻阅手册
得知GPIO5使能需要CCGR1寄存器的CG15、GPIO4使能需要CCGR3寄存器的CG6
因此GPIO5默认使能,最终只需要配置CCGR3寄存器的位12、13为1即可使能GPIO4。
(3) 设置GPIO5_IO01、GPIO4 _IO14为GPIO模式,访问IO端口复用章节,即:IOMUX Controller (IOMUXC)
通过Ctrl+F快速搜索
GPIO4 _IO14同理可得
(4) 设置GPIO5_IO01、GPIO4 _IO14为输入引脚,读取引脚电平。
查询28章General Purpose Input/Output (GPIO)。通过配置GPIOx_GDIR和GPIOx_PSR寄存器
struct imx6ull_gpio {
volatile unsigned int dr;
volatile unsigned int gdir;
volatile unsigned int psr;
volatile unsigned int icr1;
volatile unsigned int icr2;
volatile unsigned int imr;
volatile unsigned int isr;
volatile unsigned int edge_sel;
};
/* enable GPIO4 */
static volatile unsigned int *CCM_CCGR3;
/* enable GPIO5 */
static volatile unsigned int *CCM_CCGR1;
/* set GPIO5_IO03 as GPIO */
static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER1;
/* set GPIO4_IO14 as GPIO */
static volatile unsigned int *IOMUXC_SW_MUX_CTL_PAD_NAND_CE1_B;
//static struct iomux *iomux;
static struct imx6ull_gpio *gpio4;
static struct imx6ull_gpio *gpio5;
static void board_imx6ull_button_init (int which) /* 初始化button, which-哪个button */
{
if (!CCM_CCGR1)
{
CCM_CCGR1 = ioremap(0x20C406C, 4);
CCM_CCGR3 = ioremap(0x20C4074, 4);
IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER1 = ioremap(0x229000C, 4);
IOMUXC_SW_MUX_CTL_PAD_NAND_CE1_B = ioremap(0x20E01B0, 4);
//iomux = ioremap(0x20e0000, sizeof(struct iomux));
gpio4 = ioremap(0x020A8000, sizeof(struct imx6ull_gpio));
gpio5 = ioremap(0x20AC000, sizeof(struct imx6ull_gpio));
}
if (which == 0)
{
/* 1. enable GPIO5
* CG15, b[31:30] = 0b11
*/
*CCM_CCGR1 |= (3<<30);
/* 2. set GPIO5_IO01 as GPIO
* MUX_MODE, b[3:0] = 0b101
*/
*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER1 = 5;
/* 3. set GPIO5_IO01 as input
* GPIO5 GDIR, b[1] = 0b0
*/
gpio5->gdir &= ~(1<<1);
}
else if(which == 1)
{
/* 1. enable GPIO4
* CG6, b[13:12] = 0b11
*/
*CCM_CCGR3 |= (3<<12);
/* 2. set GPIO4_IO14 as GPIO
* MUX_MODE, b[3:0] = 0b101
*/
*IOMUXC_SW_MUX_CTL_PAD_NAND_CE1_B = 5;
/* 3. set GPIO4_IO14 as input
* GPIO4 GDIR, b[14] = 0b0
*/
gpio4->gdir &= ~(1<<14);
}
}
static int board_imx6ull_button_read (int which) /* 读button, which-哪个 */
{
//printk("%s %s line %d, button %d, 0x%x\n", __FILE__, __FUNCTION__, __LINE__, which, *GPIO1_DATAIN);
if (which == 0)
return (gpio5->psr & (1<<1)) ? 1 : 0;
else
return (gpio4->psr & (1<<14)) ? 1 : 0;
}
static struct button_operations my_buttons_ops = {
.count = 2,
.init = board_imx6ull_button_init,
.read = board_imx6ull_button_read,
};
int board_imx6ull_button_drv_init(void)
{
register_button_operations(&my_buttons_ops);
return 0;
}
void board_imx6ull_button_drv_exit(void)
{
unregister_button_operations();
}
module_init(board_imx6ull_button_drv_init);
module_exit(board_imx6ull_button_drv_exit);
MODULE_LICENSE("GPL");