Linux Device Driver Learning (9)-communication with hardware

Source: Internet
Author: User

When learning about the I/O bus, you 'd better first look at the relevant knowledge:Internal bus from PC bus to arm

 

I/O port and I/O memory

Each type of peripherals is controlled through read/write registers.

On the hardware layer, there is no conceptual difference between the memory and I/O Areas: they are accessed by sending level signals to the address bus and the control bus, then, read and write data through the data bus.

Because peripherals must match the I \ O bus, most popular I/O buses are based on PC models (mainly x86 families: it provides independent lines and special CPU commands for reading and writing I/O Ports), so even if there is no separate I/O port address space for the processors, the read/write I \ O port must also be simulated when accessing peripherals. This feature is typically implemented by the peripheral chipset (North-South Bridge in the PC) or additional circuits in the CPU (embedded approach ).

Linux implements I/O Ports on all computer platforms. Not all devices map registers to I/O Ports. Although ISA devices generally use I/O Ports, most PCI devices map registers to a memory address zone, which is usually the preferred I/O memory method. Because it does not need to use special processor commands, the CPU core can access the memory more efficiently, and the compiler has more freedom to choose the register allocation and addressing mode when accessing the memory.

I/O registers and general memory

When entering this part of learning, we should first understand a concept: side effect. The marginal effect is translated in the book, and the side effect is translated in the second version. I think that no matter how it is translated, it is impossible to accurately express the meaning of the original author, so I personally think it is good to remember side effect. The following describes the meaning of side effect. I will first post two existing online statements (here, I would like to thank you for sharing them ):

The first statement:

3. side effect: the content of an address may change when the address is read. For example, some device's interrupt status registers are automatically cleared once they are read. The operation of the I/O register has a side effect. Therefore, you cannot perform operations on it and cannot use the CPU cache.

Http://qinbh.blog.sohu.com/62733495.html

Second statement:

My understanding: the I/O port is associated with the actual external device, and the access to the I/O port controls the external device. The "marginal effect" refers to the control device (read or write) effective, the main purpose of access to the I/O port is the marginal effect. Unlike accessing the common memory, it only stores or reads a value in one location, which has no other meaning. I understand it based on the ARM platform. In the second edition of Linux device drivers, the statement is "Side effects" rather than "marginal effects ".

Original Web site: http://linux.chinaunix.net/bbs/viewthread.php? Tid = 890636 & page = 1 # pid6312646

Based on the above two statements and my understanding of the Linux Device Driver (version 3rd), I personally think this can be explained as follows:

Side effect indicates that when accessing the I/O register, it will not only affect the value of the storage unit as it accesses the normal memory, more importantly, it may change the I/O port level of the CPU, the output sequence, or the CPU's reaction to the I/O port level, so as to implement the CPU control function. The significance of CPU in the circuit is to implement its side effect.

The main difference between I/O registers and Ram is that I/O register operations have side effect, while memory operations do not.

Because the access speed of the storage unit is crucial to the CPU performance, the compiler will optimize the source code, mainly by using the high-speed cache to save the value and rearranging the read/write command order. However, for I/O register operations, these optimizations may cause fatal errors. Therefore, the driver must ensure that high-speed cache is not used when operating the I/O registers, and the read/write instruction sequence cannot be re-arranged.

Solution:

Hardware cache: you only need to disable the hardware cache when the underlying hardware configuration (automatically or through Linux Initialization Code) is used to access the I/O Region (regardless of the memory or port.

Hardware Command Re-sorting: sets a memory barrier between the operations that the hardware (or other processors) must perform in a specific order ).

Linux provides the following macros to solve all possible sorting problems:

# Include <Linux/kernel. h> void barrier (void)/* informs the compiler to insert a memory barrier, but it has no impact on hardware. The compiled code saves all the modified values in the current CPU register to the memory and re-reads them as needed. It can block Compiler Optimization before and after the barrier, but the hardware can reorder itself. In fact, <Linux/kernel. h> does not have this function, because it is in the kernel. the header file compiler. */# include <Linux/compiler. h> # define barrier () _ memory_barrier () # include <ASM/system. h> void RMB (void);/* Ensure that any read that appears before the barrier is completed before any subsequent read is executed */void WMB (void ); /* Ensure that any write that appears before the barrier is completed before any subsequent write */void MB (void ); /* Ensure that any read/write operations that appear before the barrier are completed before any subsequent read/write operations */void read_barrier_depends (void ); /* a special and weaker form of read barrier. RMB blocks the re-sorting of all read commands before and after the barrier_depends block only the re-sorting of read commands that depend on the data returned by other read commands. The difference is small and does not exist in all systems. Unless you fully understand the differences and are sure that the full read barrier will increase system overhead, you should always use RMB. * // * The preceding commands are barrier supersets */void smp_ RMB (void); void smp_read_barrier_depends (void); void smp_wmb (void); void smp_mb (void ); /* The hardware barrier is inserted only when the kernel is compiled for the SMP system; otherwise, they are all extended into a simple barrier call. */

Typical applications:

Writel (Dev-> registers. ADDR, io_destination_address); writel (Dev-> registers. size, io_size); writel (Dev-> registers. operation, dev_read); WMB ();/* similar to a demarcation line, the write operation above will be completed before the write operation below, however, the sorting of the preceding three write operations cannot be ensured */writel (Dev-> registers. control, dev_go );

The memory barrier affects performance, so it should be used only where they are actually needed. Different types have different effects on performance. Therefore, you should try to use the desired type. It is worth noting that most kernel primitives that process synchronization, such as spin locks and atomic_t, can also be used as memory barrier.

Some systems allow assignment and memory barrier combinations to improve efficiency. They are defined as follows:

# Define set_mb (VAR, value) do {Var = value; MB ();} while 0/* the following macro definition does not exist in the arm system */# define set_wmb (VAR, value) do {Var = value; WMB ();} while 0 # define set_ RMB (VAR, value) do {Var = value; RMB ();} while 0

Using the do... while structure to construct a macro is a common method of Standard C. It ensures that the extended macro can be executed as a normal c statement in all context environments.

Use the I/O port

The I/O port is used by the driver to communicate with many devices.

I/O port allocation

Do not operate on the port before obtaining the dedicated access to the port. The kernel provides an interface for registration, allowing the driver to declare the port it needs:

# Include <Linux/ioport. h> struct resource * request_region (unsigned long first, unsigned long N, const char * Name);/* tells the kernel to use N ports starting from first, the name parameter is the device name. If the allocation is successful, a non-null value is returned. Otherwise, the required port cannot be used. * // * All ports are allocated in/proc/ioports. If you cannot allocate the required port, you can check who uses it first. * // * When the I/O port set is used up (when the module may be uninstalled), return them to the system */void release_region (unsigned long start, unsigned long N ); int check_region (unsigned long first, unsigned long n);/* check whether a given I/O port set is available. If not, the return value is a negative error code. Not recommended */

Operation I/O port

After the driver registers the I/O Ports, you can read and write these ports. Most hardware partitions 8, 16, and 32-bit ports, which cannot be mixed as accessing the system memory. The driver must call different functions to access ports of different sizes.

Computer systems that only support memory-mapped I/O registers disguise port I/O by re- ing I/O ports to memory addresses. To improve portability, the kernel hides these details from the driver. The Linux kernel header file (system-dependent header file <ASM/IO. h>) defines the following inline functions (some systems are macros and some do not exist) to access the I/O port:

Unsigned INB (unsigned port); void outb (unsigned char byte, unsigned port);/* read/write byte port (8-bit width ). Port parameter: Some platforms are defined as unsigned long and some as unsigned short. The return types of INB are also different. */Unsigned inw (unsigned port); void outw (unsigned short word, unsigned port);/* access a 16-bit port (one word width) */unsigned INL (unsigned port ); void outl (unsigned longword, unsigned port);/* access the 32-bit port. Longword declares that some platforms are unsigned long and some are unsigned Int. */

Access the I/O port in the user space

The above functions are mainly provided for device drivers, but they can also be used in user space, at least on PC. The gnu c library defines them in <sys/IO. h>. The following conditions must be met when used in user space code:

(1) The program must use the-O option for compilation to forcibly extend the inline function.

(2) You must use the ioperm and iopl system calls (# include <sys/perm. h>) to obtain the permission for port I/O operations. Ioperm is used to obtain independent port operation permissions, while iopl is used to operate the entire I/O space. (Exclusive to x86)

(3) The program uses root to call ioperm and iopl, or its parent process must use root to obtain port operation permissions. (Exclusive to x86)

If the platform does not have an ioperm or iopl system call, the user space can still access the I/O port by using the/dev/prot device file. Note: The definition of this file is system-related and the I/O port must be registered first.

String operation

In addition to I/O operations for one data transmission at a time, some processors implement special instructions for one data sequence at a time. The data unit in the sequence can be byte, word, or dual-word, this is a string operation command. They complete tasks faster than a C language loop. The following macros implement string I/O, some of which are implemented through a single machine command. However, if the target processor does not execute string I/O commands, it is implemented through a compact loop. The prototype of some systems is as follows:

void insb(unsigned port, void *addr, unsigned long count);void outsb(unsigned port, void *addr, unsigned long count);void insw(unsigned port, void *addr, unsigned long count);void outsw(unsigned port, void *addr, unsigned long count);void insl(unsigned port, void *addr, unsigned long count);void outsl(unsigned port, void *addr, unsigned long count); 

Note: they read or write byte streams directly from the port. When the port and the host system have different collation, unexpected results may occur. When using inw, you should convert the byte order to match the host's byte order as necessary.

Paused I/O

To match the speed of low-speed peripherals, sometimes if the I/O command is followed by another similar I/O command, a small delay must be inserted after the I/O command. In this case, you can use paused I/O functions to replace the common I/O functions. Their names end with _ p, such as inb_p and outb_p. These function definitions are supported by most systems, although they are often extended to the same code as non-paused I/O. Because if the system uses a reasonable modern peripheral bus, there is no need for additional suspension. For details, refer to the IO. h file in the ASM subdirectory of the platform. The macro definition in include \ ASM-Arm \ Io. H is as follows:

#define outb_p(val,port)    outb((val),(port))#define outw_p(val,port)    outw((val),(port))#define outl_p(val,port)    outl((val),(port))#define inb_p(port)        inb((port))#define inw_p(port)        inw((port))#define inl_p(port)        inl((port))#define outsb_p(port,from,len)    outsb(port,from,len)#define outsw_p(port,from,len)    outsw(port,from,len)#define outsl_p(port,from,len)    outsl(port,from,len)#define insb_p(port,to,len)    insb(port,to,len)#define insw_p(port,to,len)    insw(port,to,len)#define insl_p(port,to,len)    insl(port,to,len)

As we can see, because arm uses an internal BUS, there is no need to pause it, so the paused I/O function is extended to the same code as the non-paused I/O function.

Platform relevance

Due to its own characteristics, I/O commands are closely related to the processor and it is very difficult to hide the differences between systems. Therefore, most of the source code about port I/O is platform-dependent. The following is a summary of the functions used by x86 and arm:

IA-32 (x86)
X86_64
This system supports all the functions described above. The port number is of the unsigned short type.
Arm
Port ing to memory, supports all functions. String operations are implemented in C language. The port type is unsigned Int.

Use I/O memory

In addition to the commonly used I/O Ports on x86, another main mechanism for communication with devices is to use registers or device memory mapped to memory, collectively referred to as I/O memory. The difference between registers and memory is transparent to the software. The I/O memory is just like an area of Ram. The processor accesses this area through the bus to achieve Device Access.

Depending on the platform and bus, the I/O memory can be used to determine whether to access the Classification through the page table. If you access through a page table, the kernel must first arrange a physical address to make it visible to the device driver, and call ioremap before any I/O. If you do not use a page table, the I/O memory area is similar to the I/O port. You can use the appropriate function to access them. Because of the influence of "side effect,

Whether or not ioremap is required, direct I/O memory pointers are not encouraged. The use of dedicated I/O memory operation functions is not only secure on all platforms, but also optimized the direct use of pointers for I/O memory operations.

I/O memory allocation and ing

The I/O memory area must be allocated before use. The function interface is defined in <Linux/ioport. h>:

Struct resource * request_mem_region (unsigned long start, unsigned long Len, char * Name);/* allocates a memory area of len bytes starting from start. A non-null pointer is returned. Otherwise, null is returned. All I/O memory allocations are listed in/proc/iomem. * // * The I/O memory area should be released when it is no longer needed */void release_mem_region (unsigned long start, unsigned long Len ); /* an old function used to check the availability of the I/O memory zone. */INT check_mem_region (unsigned long start, unsigned long Len) is not recommended );

Then you must set a ing, which is implemented by the ioremap function. This function is used to assign virtual addresses to the I/O memory area. After ioremap, the device driver can access any I/O memory address. Note: The addresses returned by ioremap should not be directly referenced. The accessor function provided by the kernel should be used. Function Definition:

# Include <ASM/Io. h> void * ioremap (unsigned long phys_addr, unsigned long size); void * ioremap_nocache (unsigned long phys_addr, unsigned long size);/* If the control register is also in this area, non-cached version to implement side effect. */Void iounmap (void * ADDR );

Access I/O memory

The correct way to access the I/O memory is through a series of functions dedicated for this purpose (defined in <ASM/IO. h> ):

/* I/O memory READ function */unsigned int ioread8 (void * ADDR); unsigned int ioread16 (void * ADDR); unsigned int ioread32 (void * ADDR ); /* ADDR is the address obtained from ioremap (may contain an integer offset ), the returned value is the I/O memory write function */void iowrite8 (u8 value, void * ADDR) corresponding to the value * // * read from the given I/O memory ); void iowrite16 (2010value, void * ADDR); void iowrite32 (u32 value, void * ADDR);/* read and write a series of values to a given I/O memory address, read or write count values from the given Buf to the given ADDR */void ioread8_rep (void * ADDR, void * Buf, UNS Igned Long Count); void ioread16_rep (void * ADDR, void * Buf, unsigned Long Count); void ioread32_rep (void * ADDR, void * Buf, unsigned Long Count ); void iowrite8_rep (void * ADDR, const void * Buf, unsigned Long Count); void iowrite16_rep (void * ADDR, const void * Buf, unsigned Long Count ); void iowrite32_rep (void * ADDR, const void * Buf, unsigned Long Count);/* use the function */void memset_io (void * ADDR, u 8 value, unsigned int count); void memcpy_fromio (void * DEST, void * Source, unsigned int count); void memcpy_toio (void * DEST, void * Source, unsigned int count ); /* the old function interface can still work, but is not recommended. */Unsigned readb (Address); unsigned readw (Address); unsigned readl (Address); void writeb (unsigned value, address); void writew (unsigned value, address ); void writel (unsigned value, address );

Use ports like I/O memory

Some hardware has an interesting feature: Some versions use I/O ports, while others use I/O memory. To unify programming interfaces and make the driver easy to write, the 2.6 kernel provides an ioport_map function:

Void * ioport_map (unsigned long port, unsigned int count);/* remap count I/O ports to make them look like I/O memory ., Then, the driver can use ioread8 and similar functions on the returned address. It eliminates the differences between I/O Ports and I/O memory during programming. /* This ing should be revoked when it is no longer used: */void ioport_unmap (void * ADDR);/* Note: the I/O port must still be allocated with request_region before re ing. ARM9. these two functions are not supported! */

The above is based on the introduction of Linux Device Driver (version 3rd). The following analyzes the Linux Driver Interface of s3c2440a of ARM9.

Linux Driver Interface of ARM9.

The s3c24x0 processor uses I/O memory, that is, their peripheral interfaces are implemented through reading and writing corresponding registers. These registers and memory use a single address space, use the same commands as read/write memory. Therefore, I/O memory commands are recommended.

However, this does not mean that the instructions on the I/O port are unavailable in the s3c24x0. But as long as you pay attention to the source code, you will find that the I/O port command is only a shell, and the same code as the I/O memory is used internally. Some of the following are listed:

I/O port

#define outb(v,p)        __raw_writeb(v,__io(p))#define outw(v,p)        __raw_writew((__force __u16) \                    cpu_to_le16(v),__io(p))#define outl(v,p)        __raw_writel((__force __u32) \                    cpu_to_le32(v),__io(p))#define inb(p) ({ __u8 __v = __raw_readb(__io(p)); __v; })#define inw(p) ({ __u16 __v = le16_to_cpu((__force __le16) \            __raw_readw(__io(p))); __v; })#define inl(p) ({ __u32 __v = le32_to_cpu((__force __le32) \            __raw_readl(__io(p))); __v; })

I/O memory

#define ioread8(p) ({ unsigned int __v = __raw_readb(p); __v; })#define ioread16(p) ({ unsigned int __v = le16_to_cpu(__raw_readw(p)); __v; })#define ioread32(p) ({ unsigned int __v = le32_to_cpu(__raw_readl(p)); __v; })#define iowrite8(v,p)    __raw_writeb(v, p)#define iowrite16(v,p)    __raw_writew(cpu_to_le16(v), p)#define iowrite32(v,p)    __raw_writel(cpu_to_le32(v), p)

I have written the corresponding driver for the commands on the I/O port and the instructions on the I/O memory, and all of them have passed the test. Note the following four points:

(1) The addresses assigned by all read/write commands must be virtual addresses. You have two options: Use the address defined by the kernel, such as s3c2440_gpjcon, these are virtual addresses defined by the kernel. If you are interested, you can view the source code. Another method is to use the virtual address mapped by ioremap. You cannot use the actual physical address. Otherwise, oops may occur because the kernel cannot process the address.

(2) when using the I/O command, you can directly use commands such as outb and ioread instead of request_region and request_mem_region. Because the request function only tells the kernel Port who is in use. For example, the kernel will stop sending a request again.

(3) when using the I/O command, sometimes the assigned address data must be converted to unsigned long by force type conversion, otherwise there will be a warning (For details, refer to the Linux Device Driver Learning (7)-kernel data type ). Although your program may also be usable, it is better not to have a warning.

(4) In include \ ASM-Arm \ arch-s3c2410 \ hardware. h defines a lot of IO port operation functions, you need to be able to directly use in the driver, very convenient.

Experiment source code:

Io_port.tar.gz

Io_port_test.tar.gz

Io_mem.tar.gz

Io_mem_test.tar.gz

Both modules implement access control for blocking dedicated devices and notify the kernel that llseek is not supported. The specific test is in io_port.

The test results are as follows:

[Tekkaman2440 @ workshop] # cd/lib/modules/[tekkaman2440 @ workshop] # insmod workshop [tekkaman2440 @ workshop] # insmod workshop [tekkaman2440 @ workshop] # Cat/proc/devicescharacter Devices: 1 mem 2 Pty 3 ttyp 4/dev/VC/0 4 tty 4 TTYs 5/dev/tty 5/dev/console 5/dev/ptmx 7 VCs 10 MISC 13 input 14 sound 81 video4linux 89 I2C 90 mtd116 alsa128 ptm136 pts153 spi180 usb189 usb_device204 s3c2410_serial1_io _ Mem252 io_port253 usb_endpoint254 rtcblock devices: 1 ramdisk256 RfD 7 loop 31 mtdblock 93 nftl 96 inftl179 MMC [tekkaman2440 @ sbc2440v4] # mknod-M 666/dev/io_port C 252 0 [tekkaman2440 @ sbc2440v4] # mknod-M 666/ dev/io_mem C 251 0 [tekkaman2440 @ sbc2440v4] # cd/tmp/[tekkaman2440 @ sbc2440v4] #. /io_mem_testio_addr: c485e0d0io_mem: the module can not lseek! Please input the command: 1io_mem: IOCTL 1 OK! Please input the command: 8io_mem: IOCTL status OK! Current_status = 0x1please input the command: 3io_mem: IOCTL 3 OK! Please input the command: Q [tekkaman2440 @ sbc2440v4] #. /io_porttest_sleep & [tekkaman2440 @ sbc2440v4] #. /io_porttest_sleep & [tekkaman2440 @ sbc2440v4] #. /io_porttest_sleep & [tekkaman2440 @ sbc2440v4] #. /io_port_testio_port: the module can not lseek! Please input the command: 1io_port: IOCTL 1 OK! Please input the command: 8io_port: IOCTL status OK! Current_status = 0x1please input the command: 3io_port: IOCTL 3 OK! Please input the command: 8io_port: IOCTL status OK! Current_status = 0x3please input the command: Q [1] Done. /io_porttest_sleep [tekkaman2440 @ sbc2440v4] # ps PID uid vsz STAT command 1 root 1744 s init 2 root SW <[kthreadd] 3 root swn [ksoftirqd/0] 4 root SW <[Watchdog /0] 5 root SW <[events/0] 6 root SW <[khelper] 61 root SW <[kblockd/0] 62 root SW <[ksuspend_usbd] 65 root SW <[ khubd] 67 root SW <[kseriod] 79 root SW [pdflush] 80 root SW [pdflush] 81 Ro Ot SW <[kswapd0] 82 root SW <[AIO/0] 709 root SW <[mtdblockd] 710 root SW <[nftld] 711 root SW <[inftld] 712 root SW <[rfdd] 746 root SW <[kpsmoused] 755 root SW <[kmmcd] 773 root SW <[rpciod/0] 782 root 1752 S-SH 783 root 1744 s init 785 root 1744 s init 787 root 1744 s init 790 root 1744 s init 843 root 1336 S. /io_porttest_sleep 844 root 1336 S. /io_porttest_sleep 846 root 1744 r ps [tekkaman24 40 @ sbc2440v4] # ps PID uid vsz STAT command 1 root 1744 s init 2 root SW <[kthreadd] 3 root swn [ksoftirqd/0] 4 root SW <[watchdog/0] 5 root SW <[events/0] 6 root SW <[khelper] 61 root SW <[kblockd/0] 62 root SW <[ksuspend_usbd] 65 root SW <[khubd] 67 root SW <[kseriod] 79 root SW [pdflush] 80 root SW [pdflush] 81 root SW <[kswapd0] 82 root SW <[AIO/0] 709 root SW <[mtdblockd] 710 root SW <[nftld] 7 11 root SW <[inftld] 712 root SW <[rfdd] 746 root SW <[kpsmoused] 755 root SW <[kmmcd] 773 root SW <[rpciod/0] 782 root 1752 s-SH 783 root 1744 s init 785 root 1744 s init 787 root 1744 s init 790 root 1744 s init 847 root 1744 r ps [3] + done. /io_porttest_sleep [2] + done. the/io_porttest_sleep program is for 2440. If you use 2410, you only need to modify the test IO port!

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.