目前大多数处理器外设都是通过读写寄存器操作芯片外设,这些寄存器处于内存地址或者I/O地址上。从硬件角度考虑,内存和IO区域没有概念上的区别,均是通过地址总线、数据总线和控制总线(读写信号)来进行读写操作。
并非所有处理器厂商将IO端口和IO内存给予独立的地址空间,但有些厂商认为IO端口属于外设,有别于内存,需要将两者的地址区别开来。inter处理器的IO端口和IO内存是分开的,通过特殊的CPU指令存取端口。ARM处理器则没有将两者地址区分。
端口和内存处于通过地址空间依然适用该节内容。当然也可像内存操作是一样的,可以使用后面的操作内存接口。
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);
端口和内存均处于同个地址空间,则使用与内存相同的接口。相当于端口规划在内存之中。
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,这个函数是不安全和应当避免的。
在对内存进行操作前,我们首先要做的工作是将已经分配成功的内存进行映射。我们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地址,但是不能直接当做指针使用。
内核提供了一组接口来完成虚拟地址的读写,这些接口如下:
// 读写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");