字符设备驱动-led

这部分的内容,在上一节的基础上,实际的操作一个 led

MMU、地址映射与内存读写

地址映射 api

虚拟内存到物理内存的映射。

512M DDR 挂载物理地址的 0x80000000-0x9fffffff 上,芯片里外设的寄存器也是挂在物理地址上。

但是 32 位机器跑了操作系统后,每个进程可以使用的地址空间是 4GB,0x00000000 - 0xffffffff。

想点灯,就要操作 GPIO 相关的寄存器,GPIO 的寄存器在物理地址上,但是进程里操作的都是虚拟地址,所以首先需要知道 GPIO 相关的寄存器的物理地址在虚拟地址里面是什么。

这个需求是合理的,所以内核源码里提供了这个功能,在 arch/arm/include/asm/io.h

  • #define ioremap(cookie,size) __arm_ioremap((cookie), (size), MT_DEVICE)

    • cookie 需要映射到虚拟空间的起始物理地址

    • size 要映射的内存空间大小

    • 返回映射后虚拟空间的首地址

  • void iounmap (volatile void __iomem *addr)

    • 驱动卸载时,要释放内存

    • size 要映射的内存空间大小

    • 返回映射后虚拟空间的首地址

I/O 内存访问 api

独立编址与存储器映射IO

  • x86 体系下,内存地址空间和IO地址空间独立,读写内存的指令是 MOV ,读写寄存器的指令是 INOUT

  • ARM 体系下,全部都在内存地址空间上,即存储器映射IO,

读写映射后的内容的叫法

  • 外面的寄存器或者存储器映射到 IO 地址空间,这时候叫I/O端口

  • 外面的寄存器或者存储器映射到内存地址空间,这时候叫I/O内存

虽然使用 ioremap 映射后,用户空间已经可以直接使用指针来访问了,但是为了安全考虑,linux kernel 提供了一组 api 来操作映射后的内存

  • 读操作

    • u8 readb(const volatile void __iomem *addr)

    • u16 readw(const volatile void __iomem *addr)

    • u32 readl(const volatile void __iomem *addr)

  • 写操作

    • void writeb(u8 value, volatile void __iomem *addr)

    • void writew(u16 value, volatile void __iomem *addr)

    • void writel(u32 value, volatile void __iomem *addr)

led 原理图与相关寄存器

参考硬件原理图与芯片寄存器手册。

LED 接在 GPIO1_IO03,需要关注的点

  • 配置引脚复用为 GPIO,要关注的寄存器

    • SW_MUX_CTL_PAD_ 配置引脚复用功能

    • SW_PAD_CTL_PAD_ 配置引脚电气属性

  • GPIO 时钟的使能

    • CCM 有 7 个寄存器用来开关时钟

    • GPIO1 时钟配置在 CCM_CCGR1 寄存器中 bit27-26

  • 查看 Chapter 28 关于 GPIO 寄存器的介绍,需要关注的寄存器有

    • GPIO direction register (GPIO_GDIR) 配置方向

    • Data register (GPIO_DR) 数据

不使用引脚中断的话关注这些就够用了。

手册中,相关寄存器对应的地址

  • CCM_CCGR1

    • 0x020C406C

  • SW_MUX_CTL_GPIO1_IO03

    • 0x020E0068

  • SW_PAD_CTL_GPIO1_IO03

    • 0x020E02F4

  • GPIO1_DR

    • 0x0209C000

  • GPIO1_GDIR

    • 0x0209C004

led 基本驱动

  • 字符设备注册

  • 设备号分配

class 下的 led 驱动

  • 私有数据

misc 下的 led 驱动

设备树下的 led 驱动

使用设备树向 linux 内核传递寄存器物理地址,驱动通过 of 函数从设备树中获得需要的属性值,然后通过这些属性来初始化相关的寄存器。

  • .dts 设备树中新增 led 节点及相关属性

  • 驱动程序中使用 of 获得相关属性值

  • 初始化对应寄存器

设备树根节点中,添加内容。


  • linux 模块,虚拟设备

  • 字符设备驱动框架

  • 常用 api

驱动思路,分层设计

一个关键的结构体,linux/fs.h

驱动的加载有两种方式,一种是直接编译到内核里去,最后就在 zImage 里。

也可以编译成模块,即 *.ko 调试的时候,我们可以编译成模块,测试的时候加载模块就好了。

设备号

设备号 dev_t 是一个 u32 类型整数。高12位主设备号,低20位次设备号。

主设备号为一类设备。如 IIC 设备类型下面,挂许多具体的器件。

设备号操作的相关宏定义。

此函数,只制定了主次设备号。比较老的函数了。需要手动去看那些号没使用。 static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)

使用 cat /proc/devices 查看主设备号。

字符设备驱动注册的另一种方法

前面注册用的 api 我们手动分配了主设备号,在分配前还要手动查一下哪个号没有被用掉。因此,需要一些更好用的 api。

  • 自动分配一个设备号,可以用下面这个函数

    • int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

  • 指定设备号,也可以使用和前面不一样的这个 api

    • int register_chrdev_region(dev_t from, unsigned count, const char *name)

  • 设备注销,使用下面这个函数释放

    • void unregister_chrdev_region(dev_t from, unsigned count)

最后更新于