[課程筆記]Linux Driver正點原子課程筆記4 - Led燈驅動實驗

Posted by John on 2021-03-03
Words 1k and Reading Time 5 Minutes
Viewed Times

〖想觀看更多課程筆記,至[課程筆記]課程筆記系列總覽可以看到目前已發布的所有文章!〗

Course 4 - Led燈驅動實驗

嘗試透過driver去操作led register來達到led開關的實驗。

地址映射

Linux下無法直接對physical address做讀寫操作,因為linux會enable MMU(Memory Manage Unit),MMU的功能為

  1. virtual address (VA) <-> physical address (PA)的mapping
  2. 內存保護、register的訪問權限

對於32 bit的processor來說,virtual address的range是2^32=4GB。假設開發版有512MB的physical address,則MMU可以將512MB映射到4GB的虛擬空間。

  • 但PA有512MB,VA有4GB,要如何mapping? 這邊先不討論,有興趣的可以去看MMU的詳細介紹

因此,如果要對physical address做讀寫,必須要該PA對應的VA,這邊會用到兩個function:

  • ioremap(): 獲得PA對應的VA
    • 第一個參數: 物理地址起始位置,第二個參數: 轉換的char數量
  • iounmap(): 釋放VA
    • 參數:要釋放的VA
0
1
2
3
4
5
6
7
8
/* arch/arm/include/asm/io.h */
void __iomem *ioremap(resource_size_t res_cookie, size_t size);
#define ioremap ioremap
#define ioremap_nocache ioremap

...

void iounmap(volatile void __iomem *iomem_cookie);
#define iounmap iounmap
0
1
2
3
4
5
/* arch/arm/mm/ioremap.c */
void __iomem *ioremap(resource_size_t res_cookie, size_t size)
{
return arch_ioremap_caller(res_cookie, size, MT_DEVICE,
__builtin_return_address(0));
}

接下來按照影片教學,試著寫一套驅動&app來透過他們家的開發版操控led燈,但我沒有他們的開發版,所以就照著寫了一遍code練習

裡面register的addr是參照影片來寫的,所以如果不能動不要問這值是哪裡來的
Q_Q

driver

  1. init相關設置(e.g. init led燈需要init gpio, clk..)可以放在*fops的open或module init,端看情境
    • 初始化如需讀寫register,記得要取得PA對應的VA
  2. linux不推薦直接對取得的va addr做讀寫,而是用提供的api來操作(readb(), readw(), readl(), writeb(), writew(), writel())
  3. 關燈的控制不能寫在led_release(),why?
    • 因為如果寫在led_release(),每次app close()時都會關閉燈,情境上不太適合
0
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
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>

#define LED_MAJOR 200
#define LED_NAME "led"

/* register PA */
#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)

/* pointer of VA */
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;

#define LEDOFF 0
#define LEDON 1

void led_switch(u8 sta)
{
u32 val = 0;
if (databuf[0] == LEDOFF) {
val = readl(GPIO1_DR);
val |= ~(1 << 3);
writel(val, GPIO1_DR); // close led
} else {
val = readl(GPIO1_DR);
val &= ~(1 << 3);
writel(val, GPIO1_DR); // open led
}
}

static int led_open(struct inode *inode, struct file *filp)
{
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)
{
int ret;
int val = 0;
unsigned char databuf[1];
ret = copy_from_user(databuf, buf, count);
if (ret < 0){
printk("kernel write fail\n");
return -EFAULT;
}

led_switch(databuf[0]);

return 0;
}

static const struct file_operations *fops
{
.owner = THIS_MODULE,
.write = led_write,
.open = led_open,
.release = led_close,
};

static int __init led_init(void)
{
int ret = 0;
int val = 0;
printk("led init\n");

/* 1. get va of register */
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);

/* 2. init */
val = readl(IMX6U_CCM_CCGR1);
val &= ~(3 << 26); // clr IMX6U_CCM_CCGR1[26:27]
val |= 3 << 26; // set IMX6U_CCM_CCGR1[26:27] to 1
writel(val, IMX6U_CCM_CCGR1);

writel(0x5, SW_MUX_GPIO1_IO03);
writel(0x10B0, SW_PAD_GPIO1_IO03);

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

val = readl(GPIO1_DR);
val &= ~(1 << 3);
writel(val, GPIO1_DR); // open led

/* 2. register chrdev */
ret = register_chrdev(LED_MAJOR, LED_NAME, &fops);
if (ret < 0) {
printk("chrdev register fail\n");
return -EIO;
}

}

static void __exit led_exit(void)
{
int val = 0;
val = readl(GPIO1_DR);
val |= ~(1 << 3);
writel(val, GPIO1_DR); // close led

/* 1. unmap va */
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);

/* 2. unregister */
unregister_chrdev(LED_MAJOR, LED_NAME);
printk("chrdev unregister\n");
}

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("john");

app

0
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
/**
* ./ledAPP <filename> <num>
* @num: 0 代表關燈, 1代表開燈
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define LEDOFF 0
#define LEDON 1

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

if (argc != 3) return -1;

filename = argv[1];

/* open() return a file descriptor */
fd = open(filename, O_RDWR);

if (fd < 0)
printf("Can not open file %s\n", filename);
return -1;

databuf[0] = atoi(argv[2]);
ret = write(fd, databuf, sizeof(databuf));
if (ret < 0) {
printk("led control fail\n");
close(fd);
return -1;
}

close(fd);

return 0;
}

>