linux wc -l 对io,linux设备驱动归纳总结(五):2.操作硬件——IO内存
linux設備驅動歸納總結(五):2.操作硬件——IO內存
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
在之前章節的驅動,都沒有對硬件進行操作,接寫來將從我之前學的裸板驅動開始,講解在linux系統下如何訪問硬件。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
一、IO端口與IO內存
x86體系和ARM體系的尋址方式是有差別的:
在x86下,為了能夠滿足CPU高速地運行,內存與CPU之間通過北橋相連并通過地址方式訪問,而外設通過南橋與CPU相連并通過端口訪問。
在ARM下也實現了類似的操作,通過兩條不同的總線(AHB
BUS和APB
BUS)來連接不同訪問速度的外設。但是它與x86不同,無論是內存還是外設,ARM都是通過地址訪問。
因為這兩種訪問方式的不同,linux分出了兩種不同的訪問操作:
以地址方式訪問硬件——使用IO內存操作。
以端口方式訪問硬件——使用IO端口操作。
在ARM下,訪問寄存器就像訪問內存一樣——從指定的寄存器地址獲取數據,修改。所以,ARM下一般是使用IO內存的操作。但這并不是說IO端口的操作在ARM下不能用,它們的代碼差不多,只是沒有使用的必要,下面也將介紹IO內存操作。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
二、如何使用IO內存獲得硬件的地址
之前已經說過,不能在linux使用實際的物理地址,要對指定的物理地址進行操作,必須要先將物理地址與虛擬地址對應,通過虛擬地址訪問。于是有了以下的物理地址映射函數:
#include
void
*ioremap(unsigned long phys_addr, unsigned long size);
其實這也是上一節介紹的內存分配的一種方式,它同樣會建立新頁表來管理虛擬地址。函數傳入兩個參數,需要訪問的物理內存(寄存器)的首地址phys_addr和這段內存區域的大小size,返回與該段物理地址對應的虛擬地址。這段地址可以多次被映射,當然,每次映射的虛擬地址也不一樣。
對應的也有撤銷映射關系的函數:
void
ioumap(void *addr);
接下來,我將會從一個裸板的ARMled驅動開始,講解linux下的操作和裸板有什么不一樣。
我的ARM裸板程序是在linux下編寫的,我不知道這跟win下使用ADS有什么區別,在裸板驅動中,一般我是通過這樣的辦法來操作寄存器的:
首先,先給個地址定義個容易記的名字:
#define
GPECON*(volatile unsigned long *) 0x56000040
接著,我就要操作這個GPECON寄存器了:
*GPECON
&= ~(3 << 24);//將24和25位清零
*GPECON
|= (1 << 24);//將24和25位分別賦值為1、0
可以看到,操作寄存器其實就是拿個地址出來進行操作。其實在linux下也是一樣,只是操作的時候不能使用物理地址,需要用映射出來的虛擬地址。
上個函數,這個程序我將要點亮連在我開發板上的led燈,這個燈接在我開發板的GPE12上,如果需要下載程序運行,需要改一下接口。
/*5th_mm_2/1st/test.c*/
1
#include
2
#include
3
4
#include //上面介紹的函數需要包含該頭文件
5
6
volatile unsigned long virt, phys;
//用于存放虛擬地址和物理地址
7
volatile unsigned long *GPECON, *GPEDAT, *GPEUP;
//用與存放三個寄存器的地址
8
9
void led_device_init(void)
10
{
11
phys = 0x56000000;
//1、指定物理地址
12
virt = (unsigned long)ioremap(phys,
0x0c);
//2、通過ioremap獲得對應的虛擬地址
13
//0x0c表示只要12字節的大小
14
GPECON = (unsigned long *)(virt + 0x40);
//3、指定需要操作的三個寄存器的地址
15
GPEDAT = (unsigned long *)(virt + 0x44);
16
GPEUP = (unsigned long *)(virt + 0x48);
17
}
18
19
void led_configure(void)
//led配置函數
20
{
21
*GPECON &= ~(3 << 24);
//配置GPE12為輸出端口
22
*GPECON |= (1 << 24);
//先清零再賦值
23
24
*GPEUP |= (1 << 12);
//禁止上拉電阻
25
}
26
27
void led_on(void)
//點亮led
28
{
29
*GPEDAT &= ~(1 << 12);
30
}
31
32
void led_off(void)
//滅掉led
33
{
34
*GPEDAT |= (1 << 12);
35
}
36
37
static int __init test_init(void) //模塊初始化函數
38
{
39
led_device_init();
40
led_configure();
41
led_on();
42
printk("hello led!\n");
43
return 0;
44
}
45
46
static void __exit test_exit(void) //模塊卸載函數
47
{
48
led_off();
49
iounmap((void *)virt);
//注意,即使取消了映射,通過之前的虛擬地址還能訪問硬件,
50
printk("bye\n");//但不是肯定可以,只要該虛擬地址被內核改動后就不行了。
51
}
52
53
module_init(test_init);
54
module_exit(test_exit);
55
56
MODULE_LICENSE("GPL");
57
MODULE_AUTHOR("xoao bai");
58
MODULE_VERSION("v0.1");
從上面的程序可以看到,除了獲得地址有點和裸板驅動不一樣外,寄存器的操作還是一樣的。
接下來驗證一下:
[root:
1st]# insmod test.ko
hello
led!
//這時候燈亮了
[root:
1st]# rmmod test
bye
//燈滅了
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
三、改進函數,使用更好的內存訪問接口
為了實現更好的移植性,上面的程序就有缺陷了。內核建議,盡量使用內核提供的內存訪問接口:
#include
//從內存讀取數據,返回值是指定內存地址中的值
unsigned
int ioread8(void *addr)
unsigned
int ioread16(void *addr)
unsigned
int ioread32(void *addr)
//往指定內存地址寫入數據
void
iowrite8(u8 value, void *addr)
void
iowrite16(u16 value, void *addr)
void
iowrite32(u32 value, void *addr)
一般常用的是32位內存存取接口。
接下來就改進一下函數,其實實質沒有改變,上面的函數是根據對應的平臺體系結構編寫的,這樣可以提高驅動的移植性。
/*5th_mm_2/1st/test.c*/
1
#include
2
#include
3
4
#include
5
#include
6
7
volatile unsigned long virt, phys;
8
volatile unsigned long *GPECON, *GPEDAT, *GPEUP;
9
unsigned long reg;
10
11
void led_device_init(void)
12
{
13
phys = 0x56000000;
14
virt = (unsigned long)ioremap(phys,SZ_16);
//這里只是想介紹一下,在asm/sizes.h中有一下
15
//定義好用來表示內存大小的宏,這里其實我只
16
GPECON = (unsigned long *)(virt + 0x40);
//需要12個字節,并不需要16個字節。
17
GPEDAT = (unsigned long *)(virt + 0x44);
18
GPEUP = (unsigned long *)(virt + 0x48);
19
}
20
21
void led_configure(void)
22
{
23
//*GPECON &= ~(3 << 24);
24
//*GPECON |= (1 << 24);
25
reg = ioread32(GPECON);
26
reg &= ~(3 << 24);
27
reg |= (1 << 24);
28
iowrite32(reg, GPECON);
29
30
//*GPEUP |= (1 << 12);
31
reg = ioread32(GPEUP);
32
reg &= ~(3 << 12);
33
iowrite32(reg, GPEUP);
34
}
35
36
void led_on(void)
37
{
38
//*GPEDAT &= ~(1 << 12);
39
reg = ioread32(GPEDAT);
40
reg &= ~(1 << 12);
41
iowrite32(reg, GPEDAT);
42
}
43
44
void led_off(void)
45
{
46
//*GPEDAT |= (1 << 12);
47
reg = ioread32(GPEDAT);
48
reg |= (1 << 12);
49
iowrite32(reg, GPEDAT);
50
}
51
52
static int __init test_init(void) //模塊初始化函數
53
{
54
led_device_init();
55
led_configure();
56
led_on();
57
printk("hello led!\n");
58
return 0;
59
}
60
61
static void __exit test_exit(void) //模塊卸載函數
62
{
63
led_off();
64
iounmap((void *)virt);
65
printk("bye\n");
66
}
67
68
module_init(test_init);
69
module_exit(test_exit);
70
71
MODULE_LICENSE("GPL");
72
MODULE_AUTHOR("xoao bai");
73
MODULE_VERSION("v0.1");
會發現發現,程序將原來直接訪問內存的一句話變成了3句話,其他都沒有改變。
我就不驗證了,效果其實是一樣的。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
四、再改進一下程序:
在使用IO內存映射操作之前,其實還可以添加一個步驟:分配內存區域。
#include
struct
resource *request_mem_region(unsigned long start, unsinged long len,
char *name)
該函數從start開始分配len字節長的內存空間。如果成功,返回一個結構體指針,但這結構體我們沒必要用,如果失敗返回NULL。成功后,可以在.proc/iomem查看到name的信息。
其實調用request_mem_region()不是必須的,但是建議使用。該函數的任務是檢查申請的資源是否可用,如果可用則申請成功,并標志為已經使用,其他驅動想再申請該資源時就會失敗。
如果不再使用,需要調用釋放函數:
void
release_mem_region(unsigned long start, unsigned long len)
現在把這兩個函數加上去:
/*5th_mm_2/3rd/test.c*/
1
#include
2
#include
3
4
#include
5
#include
6
7
volatile unsigned long virt, phys;
8
volatile unsigned long *GPECON, *GPEDAT, *GPEUP;
9
unsigned long reg;
10
struct resource *led_resource;
11
12
void led_device_init(void)
13
{
14
phys = 0x56000000;
15
virt = (unsigned long)ioremap(phys, 0x0c);
16
17
GPECON = (unsigned long *)(virt + 0x40);
18
GPEDAT = (unsigned long *)(virt + 0x44);
19
GPEUP = (unsigned long *)(virt + 0x48);
20
}
21
22
void led_configure(void)
23
{
24
reg = ioread32(GPECON);
25
reg &= ~(3 << 24);
26
reg |= (1 << 24);
27
iowrite32(reg, GPECON);
28
29
reg = ioread32(GPEUP);
30
reg &= ~(3 << 12);
31
iowrite32(reg, GPEUP);
32
}
33
34
void led_on(void)
35
{
36
reg = ioread32(GPEDAT);
37
reg &= ~(1 << 12);
38
iowrite32(reg, GPEDAT);
39
}
40
41
void led_off(void)
42
{
43
reg = ioread32(GPEDAT);
44
reg |= (1 << 12);
45
iowrite32(reg, GPEDAT);
46
}
47
48
static int __init test_init(void) //模塊初始化函數
49
{
50
led_device_init();
51
52
led_resource = request_mem_region(phys, 0x0c, "LED_MEM");
53
if(NULL == led_resource){
54
printk("request mem error!\n");
55
return - ENOMEM;
56
}
57
58
led_configure();
59
led_on();
60
printk("hello led!\n");
61
return 0;
62
}
63
64
static void __exit test_exit(void) //模塊卸載函數
65
{
66if(NULL
!= led_resource){
67
led_off();
68
iounmap((void *)virt);
69release_mem_region(phys, 0x0c);
70
}
71
printk("bye\n");
72
}
73
74
module_init(test_init);
75
module_exit(test_exit);
76
77
MODULE_LICENSE("GPL");
78
MODULE_AUTHOR("xoao bai");
79
MODULE_VERSION("v0.1");
寫完就得驗證一下:
[root:
3rd]# insmod test.ko
hello
led!
//燈亮了
[root:
3rd]# cat /proc/iomem
19000300-19000310
: cs8900
19000300-19000310
: cs8900
。。。。
56000000-5600000b
: LED_MEM
//看到了
57000000-570000ff
: s3c2410-rtc
57000000-570000ff
: s3c2410-rtc
5a000000-5a0fffff
: s3c2440-sdi
[root:
3rd]# rmmod test
bye
//燈滅了
[root:
3rd]# cat /proc/iomem
//LED_MEM不見了
19000300-19000310
: cs8900
19000300-19000310
: cs8900
。。。。。。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
五、總結
今天介紹的內容不多,其實就幾個函數,下面重溫一下使用IO內存的步驟:
其中第一步和最后一步可以不做。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
總結
以上是生活随笔為你收集整理的linux wc -l 对io,linux设备驱动归纳总结(五):2.操作硬件——IO内存的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2023大学什么时候开学(大学一般什么时
- 下一篇: 高考多少分能上石家庄工程职业学院(202