【嵌入式Linux驱动程序-基础篇】- 驱动与硬件层间的通信

驱动与硬件层间的通信

1 IO端口和IO内存

目前大多数处理器外设都是通过读写寄存器操作芯片外设,这些寄存器处于内存地址或者I/O地址上。从硬件角度考虑,内存和IO区域没有概念上的区别,均是通过地址总线、数据总线和控制总线(读写信号)来进行读写操作。

并非所有处理器厂商将IO端口和IO内存给予独立的地址空间,但有些厂商认为IO端口属于外设,有别于内存,需要将两者的地址区别开来。inter处理器的IO端口和IO内存是分开的,通过特殊的CPU指令存取端口。ARM处理器则没有将两者地址区分。

 

2 操作I/O端口(适用于端口和内存分开的处理器)

端口和内存处于通过地址空间依然适用该节内容。当然也可像内存操作是一样的,可以使用后面的操作内存接口。

2.1 I/O端口分配

内核提供了一个注册接口允许驱动工程师来声明所需的端口,如下所示:

#include 
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 
#include 
#include 
#include 
#include 
#include  // include 
#include 
#include 
#include 
#include 
#include 

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");

 

你可能感兴趣的:(嵌入式Linux)