0%

Linux驱动学习日记(13) Linux 环境下点灯

原理分析

硬件原理

  在前面的学习日志中已经提到。

地址映射

  在编写驱动之前,我们需要先简单了解一下 MMU 这个神器,MMU 全称叫做 Memory Manage Unit,也就是内存管理单元。在老版本的 Linux 中要求处理器必须有 MMU,但是现在 Linux 内核已经支持无 MMU 的处理器了。MMU 主要完成的功能如下:
  ①、完成虚拟空间到物理空间的映射。
  ②、内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性。
其中地址映射指的是虚拟空间到物理空间的映射,对于32位的处理器来说,虚拟地址(VA)的范围是2^32 = 4 GB,而板载内存只有512MB,经过 MMU 可以将其映射到整个的 4GB 虚拟空间,如下图所示

图 1 地址映射

  Linux 内核启动的时候会初始化 MMU,设置好内存映射,设置好以后 CPU 访问的都是虚拟地址。比如 I.MX6ULL 的 GPIO1_IO03 引脚的复用寄存器 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 的地址为 0X020E0068。如果没有开启 MMU 的话直接向 0X020E0068 这个寄存器地址写入数据就可以配置 GPIO1_IO03 的复用功能。现在开启了 MMU,并且设置了内存映射, 因此就不能直接向 0X020E0068 这个地址写入数据了。我们必须得到 0X020E0068 这个物理地址在 Linux 系统里面对应的虚拟地址,这里就涉及到了物理内存和虚拟内存之间的转换,需要用到两个函数:ioremap 和 iounmap。

ioremap

  ioremap 函数用于获取指定物理地址空间对应的虚拟地址空间,定义在 arch/arm/include/asm/io.h 文件中,定义如下:

1
void __iomem *ioremap(resource_size_t res_cookie, size_t size);

  ioremap 是个宏,有两个参数:cookie 和 size,这里的 cookie 是要映射的物理起始地址,size 是要映射的内存空间大小。返回值:__iomem 类型的指针,指向映射后的虚拟空间首地址。 假如我们要获取 I.MX6ULL 的 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 寄存器对应的虚拟地址,使用如下代码即可:

#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
static void __iomem*  SW_MUX_GPIO1_IO03;
SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);

SW_MUX_GPIO1_IO03_BASE 是寄存器物理地址,SW_MUX_GPIO1_IO03 是映射后的虚拟地址。对于 I.MX6ULL 来说一个寄存器是 4 字节(32 位)的,因此映射的内存长度为 4。 映射完成以后直接对 SW_MUX_GPIO1_IO03 进行读写操作即可。

iounmap

  卸载驱动的时候需要使用 iounmap 函数释放掉 ioremap 函数所做的映射,iounmap 函数原 型如下:

void iounmap (volatile void __iomem *addr)

  iounmap 只有一个参数 addr,此参数就是要取消映射的虚拟地址空间首地址。假如我们现 在要取消掉 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 寄存器的地址映射,使用如下代码即可:

iounmap(SW_MUX_GPIO1_IO03);

I/O 内存访问函数

  这里说的 I/O 指的是输入输出。这里及到两个概念:I/O 端口和 I/O 内存。当外部寄存器或内存映射到 IO 空间时,称为 I/O 端口。 当外部寄存器或内存映射到内存空间时,称为 I/O 内存。但是对于 ARM 来说没有 I/O 空间这个概念,因此 ARM 体系下只有 I/O 内存(可以直接理解为内存)。使用 ioremap 函数将寄存器的物理地址映射到虚拟地址以后,我们就可以直接通过指针访问这些地址,但是 Linux 内核不建议这么做,而是推荐使用一组操作函数来对映射后的内存进行读写操作。

读操作函数

  读操作函数有以下几个,分别对应8bit、16bit、32bit的操作

u8  readb(const volatile void __iomem *addr)
u16 readw(const volatile void __iomem *addr)
u32 readl(const volatile void __iomem *addr)

写操作函数

写操作函数有如下几个,这三个函数分别对应 8bit、16bit 和 32bit 写操作,参数 value 是要 写入的数值,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)

程序编写

驱动文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define LED_MAJOR 200 // 设备号
#define LED_NAME "LED" // 设备名称

#define LED_OFF 0 // 关灯
#define LED_ON 1 // 开灯


// 寄存器物理地址
#define CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE (0X0209C000)
#define GPIO1_GDIR_BASE (0X0209C004)

static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

void Led_Switch(u8 sta)
{
u32 val = 0;
if(sta == LED_OFF)
{
val = readl(GPIO1_DR);
val &= ~(1 << 3);
writel(val,GPIO1_DR);
}
else if(sta == LED_ON)
{
val = readl(GPIO1_DR);
val |= (1 << 3);
writel(val,GPIO1_DR);
}
}

static int led_open(struct inode *inode,struct file *filp)
{
return 0;
}

static ssize_t led_read(struct file *filp, char __user *buf,size_t cnt,loff_t *offt)
{
return 0;
}

static ssize_t led_write(struct file *filp, const char __user *buf,size_t cnt,loff_t *offt)
{
int retvalue;
unsigned char databuf[1];
unsigned char ledstat;
retvalue = copy_from_user(databuf,buf,cnt);
if(retvalue < 0)
{
printk("kernel write failed\n");
return -EFAULT;
}
ledstat = databuf[0];

if(ledstat == LED_ON)
Led_Switch(LED_ON);
else if(ledstat == LED_OFF)
Led_Switch(LED_OFF);
return 0;
}

static int led_release(struct inode *inode, struct file *filp)
{
return 0;
}

static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};

static int __init led_init(void)
{
int retvalue = 0;
u32 val = 0;

IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE,4);
SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE,4);
SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);

val = readl(IMX6U_CCM_CCGR1);
val &= ~(3 << 26);
val |= (3 << 26);
writel(val,IMX6U_CCM_CCGR1);

writel(5, SW_MUX_GPIO1_IO03);

val = readl(GPIO1_GDIR);
val &= ~(1 << 3);
val |= (1 << 3);
writel(val, GPIO1_GDIR);

val = readl(GPIO1_DR);
val |= (1 << 3);
writel(val, GPIO1_DR);

retvalue = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
if(retvalue < 0){
printk("register chrdev failed!\r\n");
return -EIO;
}
return 0;
}

static void __exit led_exit(void)
{
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);

unregister_chrdev(LED_MAJOR,LED_NAME);

}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

测试文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

static char usrdata[] = {"usr data!"};

int main(int argc,char* argv[])
{
int fd, retvalue;
char *filename;
unsigned char databuf[1];

if(argc != 3)
{
printf("Error Usage!\n");
return -1;
}

filename = argv[1];
fd = open(filename, O_RDWR);
if(fd < 0){
printf("Can't open file %s\r\n", filename);
return -1;
}
databuf[0] = atoi(argv[2]); /* 要执行的操作:打开或关闭 */

/* 向/dev/led文件写入数据 */
retvalue = write(fd, databuf, sizeof(databuf));

retvalue = close(fd);
if(retvalue < 0){
printf("Can't close file %s\r\n", filename);
return -1;
}
return 0;
}

运行测试

  装载驱动后,运行 ./ledtest /dev/led 0(1) 即可控制LED灯的亮灭。