【嵌入式Linux驅動程式-基礎篇】- 驅動與硬體層間的通訊
驅動與硬體層間的通訊
1 IO埠和IO記憶體
目前大多數處理器外設都是通過讀寫暫存器操作晶片外設,這些暫存器處於記憶體地址或者I/O地址上。從硬體角度考慮,記憶體和IO區域沒有概念上的區別,均是通過地址匯流排、資料匯流排和控制匯流排(讀寫訊號)來進行讀寫操作。
並非所有處理器廠商將IO埠和IO記憶體給予獨立的地址空間,但有些廠商認為IO埠屬於外設,有別於記憶體,需要將兩者的地址區別開來。inter處理器的IO埠和IO記憶體是分開的,通過特殊的CPU指令存取埠。ARM處理器則沒有將兩者地址區分。
2 操作I/O埠(適用於埠和記憶體分開的處理器)
埠和記憶體處於通過地址空間依然適用該節內容。當然也可像記憶體操作是一樣的,可以使用後面的操作記憶體介面。
2.1 I/O埠分配
核心提供了一個註冊介面允許驅動工程師來宣告所需的埠,如下所示:
#include <linux/ioport.h>
struct resource *request_region(unsinged long first, unsigned long n, const char *name);
這和函式通知核心,你將使用n個埠,從first地址開始,name引數是裝置名。如果分配成功將返回一個非NULL的值。如果返回的值是NULL,則說明你無法這片埠。
所有的埠分配顯示在/proc/ioports中。如果你不能分配一個需要的埠,檢視此檔案。
當你用完了一組I/O埠(不再使用埠或者模組解除安裝時),我們應當將該資源釋放,以供其他模組使用,應當使用如下的核心介面:
void release_region(unsigned long start, unsigned long n);
當我們的驅動檢查一個給定的I/O埠是否可用可用使用如下的介面:
int check_reigon(unsigned long first, unsigned long n);
如果給定的埠不可用,則介面會返回一個負的錯誤碼。這個函式不推薦使用,該返回值不能真正地保證是否一個分配會成功。因為檢查介面check_region和分配request_region不是同一個原子操作。
2.2 操作I/O埠
核心標頭檔案定義了下列行內函數來存取IO埠:
(1) 讀寫位元組埠(8bits)
unsigned inb(unsigned port);
void outb(unsigned char byte, unsigned port);
port引數和inb返回值型別依平臺而定,例如32位平臺則採用unsigned long型別。
(2) 讀寫半字(16bits)
unsigned inw(unsigned port);
void outw(unsigned shortword, unsigned port);
(3) 讀寫字(32bits)
unsigned inl(unsigned port);
void outl(unsigned longword, unsigned port);
3 使用I/O記憶體
埠和記憶體均處於同個地址空間,則使用與記憶體相同的介面。相當於埠規劃在記憶體之中。
3.1 I/O記憶體分配
I/O記憶體區必須在使用前先分配。分配記憶體的介面是(linux/ioport.h中定義):
struct resource *request_mem_region(unsigned long start, unsigned long len, name);
這個函式分配一個len位元組的記憶體區,從start開始,如果一些順利,一個非NULL指標返回;否則返回值是NULL,所有的I/O記憶體分配來/proc/iomen中列出。
記憶體區不再需要時應當釋放:
void release_mem_region(unsigned long start, unsigned long len);
檢查I/O記憶體是否keyo可用的函式:
int check_mem_region(unnsigned long start, unsigned long len);
但是,對於check_region,這個函式是不安全和應當避免的。
3.2 I/O記憶體對映(實體地址到虛擬地址的對映介面)
在對記憶體進行操作前,我們首先要做的工作是將已經分配成功的記憶體進行對映。我們ying應當採用ioremap()介面,將記憶體實體地址對映到虛擬地址。ioremap介面如下:
void __iomem *ioremap(unsigned long phy_addr, unsigned long size);
ioremap介面接收到一個實體地址和一個整個I/O埠的大小,返回一個虛擬地址,這個虛擬地址對應一個size大小的實體地址空間。使用ioremap介面後,實體地址被對映到虛擬地址空間,所以讀寫I/O端就像讀取記憶體中的資料一樣。通過ioremap介面shen申請的虛擬地址,需要使用iounmap介面來釋放,該介面如下:
void iounremap(volatioe void __iomem *addr);
iounmap介面接收到ioremap介面申請的虛擬地址作為引數,並取消實體地址到虛擬地址的對映。雖然ioremapjiek介面是返回的虛擬dizh地址,但是不能直接當做指標使用。
3.3 I/O記憶體的讀寫
核心提供了一組介面來完成虛擬地址的讀寫,這些介面如下:
// 讀寫8位I/O記憶體
unsigned int ioread8(void __iomem *addr);
void iowrite8(u8 b, void __iomem *addr);
// 讀寫16位I/O記憶體
unsigned int ioread16(void __iomem *addr);
void iowrite16(u16 b, void __iomem *addr);
// 讀寫32位I/O記憶體
unsgined int ioread32(void __iomem *addr);
void iowrite32(u32 b, void __iomem *addr);
對於大儲存量的裝置,可以通過以上介面重複多次讀寫來完成大量資料的傳送。當然,核心提供了一組介面來讀寫一系列的值,這些介面就是上述介面的重複呼叫,介面如下:
// 以下3個介面讀取一串I/O記憶體的值
#define ioread8_rep(p,d,c) __raw_readsb(p,d,c)
#define ioread16_rep(p,d,c) __raw_readsw(p,d,c)
#define ioread32_rep(p,d,c) __raw_readsl(p,d,c)
// 以下3個介面寫入一串I/O記憶體的值
#define iowrite8_rep(p,s,c __raw_writesb(p,s,c)
#defien iowrite16_rep(p,d,c) __raw_writesw(p,s,c)
#deifne iowrite32_rep(p,d,c) __raw_writesl(p,s,c)
如果我們通覽核心原始碼,我們會發現許多呼叫舊的介面操作記憶體,當使用I/O記憶體時,這些函式仍然可以工作,但是它們在新程式碼中的使用不推薦,除了別的外,它們較少安全,因為它們不進行通用的型別檢查,但是我們還是要了解:
unsigned readb(addr);
unsigned readw(addr);
unsigned readl(addr);
void writeb(unsigned value, addr);
void writew(unsigned value, addr);
void writel(unsigned value, addr);
後言
埠和記憶體處於相同地址空間的,埠既可以適用I/O埠相關的介面,也可以適用I/O記憶體的相關介面,個人建議的話,埠使用I/O埠的介面吧,畢竟I/O埠分配後的在/proc/ioports中可以查詢到。當然若是採用I/O記憶體分配,也可以在/proc/iomem中查詢到。
附上例子程式碼,如下:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <mach/regs-gpio.h> // include <mahc/gpio-nrs.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <linux/io.h>
#include <linux/ioport.h>
int led_major = 0;
static struct cdev led_cdev;
volatile unsigned long *GPBCON, *GPBDAT, *GPBUP;
#define GPIO_REG_START_ADDR 0x56000010
unsigned long *led_iomem;
#define LED_MAGIC 'k'
#define IOCTL_LED_ON _IOW(LED_MAGIC, 1, int)
#define IOCTL_LED_OFF _IOW(LED_MAGIC, 2, int)
#define IOCTL_LED_RUN _IOW(LED_MAGIC, 3, int)
#define IOCTL_LED_SHINE _IOW(LED_MAGIC, 4, int)
#define IOCTL_LED_ALLON _IOW(LED_MAGIC, 5, int)
#define IOCTL_LED_ALLOFF _IOW(LED_MAGIC, 6, int)
static unsigned long led_table[] = {
S3C2410_GPB(5),
S3C2410_GPB(6),
S3C2410_GPB(7),
S3C2410_GPB(8),
};
void leds_all_on(void)
{
int i;
for(i=0; i < 4; i++)
{
s3c2410_gpio_setpin(led_table[i], 0);
}
}
void leds_all_off(void)
{
int i;
for(i = 0; i < 4; i++)
{
s3c2410_gpio_setpin(led_table[i], 1);
}
}
static int s3c2440_leds_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
unsigned int data;
if(__get_user(data, (unsigned int __user *) arg))
return -EFAULT;
switch(cmd)
{
case IOCTL_LED_ON:
/* s3c2410_gpio_setpin(led_table[data], 0); */
outl(inl((unsigned long)(led_iomem+4)) & ~(1 <<5 | 1 << 6 | 1 <<7 | 1<< 8),(unsigned long)(led_iomem)+4);
// *GPBDAT &= ~(1 <<5 | 1 << 6 | 1 <<7 | 1<< 8);
return 0;
case IOCTL_LED_OFF:
/* s3c2410_gpio_setpin(led_table[data], 1); */
outl(inl((unsigned long)(led_iomem+4)) | (1 <<5 | 1 << 6 | 1 <<7 | 1<< 8),(unsigned long)(led_iomem)+4);
// *GPBDAT |= (1 <<5 | 1 << 6 | 1 <<7 | 1<< 8);
return 1;
case IOCTL_LED_RUN:{
int i,j;
for(i = 0; i < data; i++)
{
for(j = 0; j < 4; j++)
{
s3c2410_gpio_setpin(led_table[j], 0);
mdelay(400);
s3c2410_gpio_setpin(led_table[j], 1);
mdelay(400);
}
}
return 0;
}
case IOCTL_LED_SHINE:
{
int i, j;
leds_all_off();
printk("IOCTL_LED_SHINE\n");
for(i = 0; i < data; i++)
{
for(j = 0; j < 4; j++)
{
s3c2410_gpio_setpin(led_table[j], 0);
}
mdelay(400);
for(j = 0; j < 4; j++)
{
s3c2410_gpio_setpin(led_table[j], 1);
}
mdelay(400);
}
return 0;
}
case IOCTL_LED_ALLON:
leds_all_on();
return 0;
case IOCTL_LED_ALLOFF:
leds_all_off();
return 0;
default:
return -EINVAL;
}
}
static int s3c2440_leds_open(struct inode *inode, struct file *file)
{
int i;
/* for(i = 0; i < 4; i ++)
{
s3c2410_gpio_cfgpin(led_table[i], S3C2410_GPIO_OUTPUT);
} */
outl(inl((unsigned long)led_iomem) | 1 <<10 | 1 << 12 | 1 <<14 | 1<< 16, (unsigned long)led_iomem); // 配置LED GPIO
outl(inl((unsigned long)(led_iomem)+4) | 1 <<5 | 1 << 6 | 1 <<7 | 1<< 8, (unsigned long)(led_iomem)+4); // LED_GPIO 預設高電平
outl(inl((unsigned long)(led_iomem)+8) | 1 <<5 | 1 << 6 | 1 <<7 | 1<< 8, (unsigned long)(led_iomem)+8); // LED_GPIO 上拉
// GPBCON = (unsigned long *)((unsigned long)led_iomem + 0x00);//指定需要操作的三個暫存器的地址
// GPBDAT = (unsigned long *)((unsigned long) led_iomem + 0x04);
// GPBUP = (unsigned long *)((unsigned long) led_iomem + 0x08);
// *GPBCON |= (1 << 10)|(1<<12)|(1<<14)|(1<<16); //output 輸出模式
// *GPBDAT |= (1 <<5 | 1 << 6 | 1 <<7 | 1<< 8);
// *GPBUP |= (1 <<5 | 1 << 6 | 1 <<7 | 1<< 8); //禁止上拉電阻
return 0;
}
static struct file_operations s3c2440_leds_fops = {
.owner = THIS_MODULE,
.open = s3c2440_leds_open,
.ioctl = s3c2440_leds_ioctl,
};
static int led_setup_cdev(struct cdev *dev, int minor, struct file_operations *fops)
{
int err = 0, devno = MKDEV(led_major, minor);
cdev_init(dev, fops);
dev->owner = THIS_MODULE;
dev->ops = fops;
err = cdev_add(dev, devno, 1);
if(err)
printk(KERN_NOTICE "Error %d adding led%d\n", err, minor);
return err;
}
static int __init s3c2440_leds_init(void)
{
int result = 0;
dev_t dev = MKDEV(led_major, 0);
char dev_name[] = "led";
if(!request_region(GPIO_REG_START_ADDR, 3*4, "LED")) // 請求分配I/O埠
{
result = -EBUSY; // 請求失敗
printk(KERN_WARNING "Fail to request region at %x\n", GPIO_REG_START_ADDR);
goto err_map;
}
led_iomem = ioremap(GPIO_REG_START_ADDR, 3*4); // 地址對映
if(led_major)
result = register_chrdev_region(dev, 1, dev_name);
else{
result = alloc_chrdev_region(&dev, 0, 1, dev_name);
led_major = MAJOR(dev);
}
if(result < 0)
{
printk(KERN_WARNING "leds: can't get major %d\n", led_major);
goto err_register_region;
}
result = led_setup_cdev(&led_cdev, 0, &s3c2440_leds_fops);
if(result)
goto err_add_cdev;
printk("led deivce installed, with major %d\n", led_major);
printk("The device name is : %s\n", dev_name);
return 0;
err_add_cdev:
unregister_chrdev_region(MKDEV(led_major, 0), 1);
iounmap(led_iomem);
release_region(GPIO_REG_START_ADDR, 3*4);
err_map:
err_register_region:
return result;
}
static void __exit s3c2440_leds_exit(void)
{
cdev_del(&led_cdev);
unregister_chrdev_region(MKDEV(led_major, 0), 1);
iounmap(led_iomem);
release_region(GPIO_REG_START_ADDR, 3*4);
printk("led device unistalled!\n");
}
module_init(s3c2440_leds_init);
module_exit(s3c2440_leds_exit);
MODULE_AUTHOR("S");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("s3c2440 led driver");