1. 程式人生 > >【嵌入式Linux驅動程式-基礎篇】- 驅動與硬體層間的通訊

【嵌入式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");