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

2019-07-12 14:37发布

驱动与硬件层间的通信

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 "); 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 ", 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 ", 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 ", 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 ", led_major); printk("The device name is : %s ", 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! "); } module_init(s3c2440_leds_init); module_exit(s3c2440_leds_exit); MODULE_AUTHOR("S"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("s3c2440 led driver");