首页 > 代码库 > Communicating with Hardware 《LDD3 学习笔记》
Communicating with Hardware 《LDD3 学习笔记》
Communicating with Hardware
Using I/O Ports
I/O ports are the means by which drivers communicate with many devices, at least part of the time. This section covers the various functions available for making use of I/O ports; we also touch on some portability issues.
I/O Port Allocation
As you might expect, you should not go off and start pounding on I/O ports without first ensuring that you have exclusive access to those ports. The kernel provides a registration interface that allows your driver to claim the ports it needs. The core function in that interface is request_region:
#include <linux/ioport.h> struct resource *request_region(unsigned long first, unsigned long n, const char *name);
This function tells the kernel that you would like to make use of n ports, starting with first . The name parameter should be the name of your device. The return value is non- NULL if the allocation succeeds. If you get NULL back from request_region, you will not be able to use the desired ports.
All port allocations show up in /proc/ioports. If you are unable to allocate a needed set of ports, that is the place to look to see who got there first. When you are done with a set of I/O ports (at module unload time, perhaps), they should be returned to the system with:
void release_region(unsigned long start, unsigned long n);
There is also a function that allows your driver to check to see whether a given set of I/O ports is available:
int check_region(unsigned long first, unsigned long n);
Here, the return value is a negative error code if the given ports are not available. This function is deprecated because its return value provides no guarantee of whether an allocation would succeed; checking and later allocating are not an atomic operation. We list it here because several drivers are still using it, but you should
always use request_region, which performs the required locking to ensure that the allocation is done in a safe, atomic manner.
I/O Memory Allocation and Mapping
I/O memory regions must be allocated prior to use. The interface for allocation of memory regions (defined in <linux/ioport.h>) is:
struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);
This function allocates a memory region of len bytes, starting at start . If all goes well, a non- NULL pointer is returned; otherwise the return value is NULL . All I/O memory allocations are listed in /proc/iomem.
Memory regions should be freed when no longer needed:
void release_mem_region(unsigned long start, unsigned long len);
There is also an old function for checking I/O memory region availability:
int check_mem_region(unsigned long start, unsigned long len);
But, as with check_region, this function is unsafe and should be avoided.
Allocation of I/O memory is not the only required step before that memory may be accessed.You must also ensure that this I/O memory has been made accessible to the kernel. Getting at I/O memory is not just a matter of dereferencing a pointer; on many systems, I/O memory is not directly accessible in this way at all. So a mapping must be set up first.
编译器优化可能会引起访问IO端口的操作被优化掉...
解决办法:
The solution to compiler optimization and hardware reordering is to place a memory barrier between operations that must be visible to the hardware (or to another processor) in a particular order. Linux provides four macros to cover all possible ordering needs:
#include <linux/kernel.h> void barrier(void)
This function tells the compiler to insert a memory barrier but has no effect on the hardware. Compiled code stores to memory all values that are currently modified and resident in CPU registers, and rereads them later when they are needed. A call to barrier prevents compiler optimizations across the barrier but leaves the hardware free to do its own reordering.
Manipulating I/O ports
After a driver has requested the range of I/O ports it needs to use in its activities, it must read and/or write to those ports. To this end, most hardware differentiates between 8-bit, 16-bit, and 32-bit ports. Usually you can’t mix them like you normally do with system memory access. *
A C program, therefore, must call different functions to access different size ports. As suggested in the previous section, computer architectures that support only memory-mapped I/O registers fake port I/O by remapping port addresses to memory addresses, and the kernel hides the details from the driver in order to ease portabil-
ity.
The Linux kernel headers (specifically, the architecture-dependent header <asm/io.h>) define the following inline functions to access I/O ports:
unsigned inb(unsigned port); void outb(unsigned char byte, unsigned port); Read or write byte ports (eight bits wide). The port argument is defined as unsigned long for some platforms and unsigned short for others. The return type of inb is also different across architectures. unsigned inw(unsigned port); void outw(unsigned short word, unsigned port);
These functions access 16-bit ports (one word wide); they are not available when compiling for the S390 platform, which supports only byte I/O.
unsigned inl(unsigned port); void outl(unsigned longword, unsigned port);
这里是一个和硬件通信的简单demo:
http://blog.csdn.net/cinmyheart/article/details/38136375
用字符设备驱动控制LED灯(因为只是很简单的demo,没有考虑资源抢占的问题,所以下面给予我完善的版本)
/************************************************************** code writer : EOF code date : 2014.08.26 code file <span style="white-space:pre"> </span>: led_by_EOF.c e-mail : jasonleaster@gmail.com code purpos: This code is a demo for beginner how to write a character device to drive IO port. If you find there is something wrong with my code and change it into a better version , please touch me by e-mail. Thank you. **************************************************************/ #include <linux/init.h> #include <linux/slab.h> #include <linux/fs.h> #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/kernel.h> /* for 'printk()'*/ #include <asm/uaccess.h> /* for 'copy_from_user()' */ #include <linux/cdev.h> #include <linux/kdev_t.h> #include <linux/types.h> #include <mach/gpio-bank-m.h> #include <mach/regs-gpio.h> #include <mach/map.h> #include <linux/pci.h> #include <linux/device.h> #include <linux/delay.h> /* for 'msleep()' */ MODULE_AUTHOR("EOF"); MODULE_LICENSE("Dual BSD/GPL"); #define DEVICE_NAME "led_by_EOF" #define DEVICE_MAJOR_NUMBER 0 #define PORT_NUM 3 #define USE_IMMEDIATE static dev_t dev_number = DEVICE_MAJOR_NUMBER; static struct class* led_class; static struct cdev my_led_cdev; static int led_open(struct inode* node,struct file* file) { printk(KERN_ALERT "Device Opened successful!\n"); return 0; } static ssize_t led_write(struct file* file,const char __user* buf,size_t count,loff_t* ppos) { int kbuf; int ret = 0; kbuf = readl(S3C64XX_GPMCON); kbuf &= (~0xFFFF); kbuf |= 0x1111; writel(kbuf,S3C64XX_GPMCON); printk(KERN_ALERT "before writing... &kbuf:%p buf:%p\n",&kbuf,buf); ret = __copy_from_user(&kbuf,buf,count); if(ret != 0) { printk(KERN_ALERT "'__copy_from_user' failed! ret:%d\n",ret); return -1; } printk(KERN_ALERT "writing... kbuf:%x \n",kbuf); writel(kbuf,S3C64XX_GPMDAT); return 0; } static int led_release(struct inode* inode,struct file*file) { printk(KERN_ALERT "Device released\n"); return 0; } static struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_open, .write = led_write, .release= led_release, }; int led_init(void) { int kbuf; /* Attention! ** If you want to use ports that you wanted, ** you should request them firstly. */ if(!request_region((unsigned long)S3C64XX_GPM_BASE,PORT_NUM,DEVICE_NAME)) { printk(KERN_ALERT "led_by_EOF : can't get I/O port address %x\n",(unsigned int)S3C64XX_GPM_BASE); return -ENODEV; } /* Here we register our device - should not failed thereafter */ if(alloc_chrdev_region(&dev_number,0,1,DEVICE_NAME) < 0) { printk(KERN_ALERT "Error in function '%s' : can't register device\n",__FUNCTION__); return -1; } led_class = class_create(THIS_MODULE,DEVICE_NAME); if(IS_ERR(led_class)) { printk(KERN_ALERT "Bad class create\n"); return -1; } cdev_init(&my_led_cdev,&led_fops); /* ** GPM0~GPM3 pull up */ kbuf = readl(S3C64XX_GPMPUD); kbuf &= (~0xFF); kbuf |= 0xaa;//1010 1010 writel(kbuf,S3C64XX_GPMPUD); /* ** GPM0~3 output mode */ kbuf = readl(S3C64XX_GPMCON); kbuf &= (~0xFFFF); kbuf |= 0x1111; writel(kbuf,S3C64XX_GPMCON); /* ** GPM0~GPM3 output 0 and light up all LED */ kbuf = __raw_readl(S3C64XX_GPMDAT); kbuf |= 0x10; writel(kbuf,S3C64XX_GPMDAT); if(cdev_add(&my_led_cdev,dev_number,1)) { printk(KERN_ALERT "Bad cdev add\n"); return 1; } device_create(led_class,NULL,dev_number,NULL,DEVICE_NAME); return 0; } void led_cleanup(void) { device_destroy(led_class,MKDEV(MAJOR(dev_number),0)); class_destroy(led_class); unregister_chrdev_region(MAJOR(dev_number),1); /* release the ports that we allocated */ release_region((unsigned long)S3C64XX_GPM_BASE,PORT_NUM); printk(KERN_ALERT "See you! My LED\n"); } module_init(led_init); module_exit(led_cleanup);
Communicating with Hardware 《LDD3 学习笔记》