Linux Device Drivers

Linux Device Drivers


Linux
Modules
Character drivers
IO & Memory
Linux Kernel
Process Management
Process Address space

Linux Scheduler
Memory Management
Interrupts
Signals
System Calls
Kernel Synchronization
Linux Inter Process Communications




Serial Ports
Parallel Ports
Introduction to Hardware
Linux Timers
DMA in Linux
Linux Threads

Linux Thread Synchronization
Linux Multi Threading
Debugging in Linux
GDB GNU Debugger
KDB Kernel Debugger
KGDB Kernel GNU Debugger
Example Ethernet Driver




IO Port and Memory

 

I/O Ports

 

     Peripheral devices are controlled by writing and reading their registers.

     They are accessed at consecutive addresses, either in the memory address space or in the I/O address space.

     Some CPU manufacturers implement a single address space in their chips.

     Some processors (x86 etc) have separate read and write electrical lines for I/O ports and special CPU instructions to access ports

     Linux implements the concept of I / O ports on all computer platforms it runs on.

     Even if the processor or peripheral bus has a separate address space for I/O orts, not all devices map their registers to I / O ports.

     Use of I / O ports is common for ISA peripheral boards.

     Most PCI devices map registers into a memory address region.

     Architecture that support memory mapped I / O registers fake port I /O by mapping port addresses and the kernel hides the details from the driver.

 

Allocation of I/O ports

 

I/O ports must be allocated before being used by the driver.

 

Linux provides following functions for this purpose:

#include <linux/ioport.h>

int check_region ( unsigned long start, unsigned long len);

struct resource *request_region (unsigned long start,unsigned long len, char *name);

void release_region ( unsigned long start , unsigned long len);

 

IMPORTANT FILE: /proc/ioports

 

Reading/Writing I/O Ports

 

Reading :

unsigned inb ( unsigned port ) ;

unsigned inw ( unsigned port ) ;

unsigned inl ( unsigned port ) ;

 

Writing :

void outb ( unsigned char byte, unsigned port ) ;

void outw ( unsigned short word, unsigned port ) ;

void outl ( unsigned long word, unsigned port ) ;

 

     Most h/w differentiate between 8-bit , 16-bit & 32 bit ports.

     Not to mix them like we do with system memory access

     64 bit I / O operations are not defined .

     Even on 64-bit architectures the port address space uses a 32-bit maximum.

     These functions can also be used from user space ( at least on PC-class) provided the ioperm or iopl are used to obtain permissions.

 

I/O registers and conventional Memory

 

     Be careful to avoid being tricked by CPU and / or compiler optimizations

     I / O operations have side effects , while memory operations have none.

     Only effect of memory write is storing a value to a location , and a memory read returns the last value written there.

     Some times these values are cached and read / write instructions are reordered.

     The compiler can cache data values into CPU registers without writing them to memory. Read & Write operations can operate on cache memory without ever reaching physical RAM.

     Reordering can happen both at compiler level and at h/w level.

     Hence a driver must therefore ensure that no caching is performed and no read or write takes place when accessing registers.

 

Solution:

H/W Caching:

The underlying hardware is already configured (either automatically or by inux initialization code) to disable any hardware cache when accessing I/O Regions. (Memory/Port)

 

Compiler optimization and h/w reordering:

Place a "memory barrier" between operations that must be visible to the h/w in a particular order.

 

Solution:

 

Linux provides four macros for this purpose.

 

#include < linux/ kernel.h >

void barrier ( void ) compiler barrier, no effect on h/w

 

h/w memory barriers:

#include < asm / system.h >

void rmb ( void ) ;

void wmb ( void ) ;

void mb ( void ) ;

 

barrier Compiled code will store to memory all values that are currently modified and resident in CPU registers, and will reveal them later when they are needed.

rmb Any reads appearing before the barrier are completed prior to the execution of any subsequent read.

wmb Any writes appearing before the barrier are completed prior to the execution of any subsequent write.

mb Does both.

 

CAUTION: Memory barriers affect performance, they should only beused where really needed.

 

String operations for Port I/O

 

Transfer a sequence of bytes, words, or longs to and from a single I / O Port of the same size.

 

Linux provides following macros :

 

void insb ( unsigned port, void *addr, unsigned long count);

void insw ( unsigned port, void *addr, unsigned long count);

void insl ( unsigned port, void *addr, unsigned long count);

void outsb ( unsigned port, void *addr, unsigned long count);

void outsw ( unsigned port, void *addr, unsigned long count);

void outsl ( unsigned port, void *addr, unsigned long count);

 

Accessing Ports in User Space

 

For some operations on the hardware the standard driver calls as read() or write() are too slow. In this case it is better to permit the I/O port access directly to the user process.

 

For this Linux offers the ioperm() call that permits port access

#include <unistd.h>

int ioperm (unsigned long from, unsigned long num, int turn_on);

 

The use of ioperm requires root privileges.

On success, zero is returned. On error, -1 is returned and errno is set appropriately.

 

I/O Memory

 

     It is the main mechanism used to communicate with devices through memory-mapped registers and device memory.

     It is simply a region of RAM-like locations that the device makes available to the processor over the bus.

     According to the computer platform and bus being used, I / O memory may or may not be accessed through page tables.

     If no page tables are needed , then I / O memory locations look pretty much like I / O Ports.

     Direct use of pointers to I / O memory is not a good practice.

 

 

Allocation of I/O memory

 

I/O memory must be allocated before being used by the driver.

Linux provides the following functions for this purpose:

int check_mem_region ( unsigned long start, unsigned long);

void request_mem_region (unsigned long start, unsigned long len, char *name);

void release_mem_ region ( unsigned long start, unsigned long len) ;

 

IMPORTANT FILE : /proc/iomem

 

Directly Mapped I/O Memory:

 

     Some computer platforms reserve a part of their memory address space for I / O locations and automatically disable memory management for any ( virtual ) address in that memory area.

     Some platforms also bypass "caching" these regions.

 

Software Mapped I/O Memory

 

     Devices live at well-known physical addresses, but the CPU has no predefined virtual address to access them.

     For software to access I/O memory, there must be a way to assign a virtual address to the device.

 

Linux provides the following functions:

 

#include < asm / io.h >

void *ioremap ( unsigned long phys_addr, unsigned long size ) ;

void *ioremap_nocache ( unsigned long phys_addr,unsigned long size);

void *iounmap ( void *addr );

 

Reading / Writing I / O Memory

 

Reading :

unsigned readb( address ) ;

unsigned readw( address ) ;

unsigned readl( address ) ;

 

Writing :

void writeb (unsigned value, address ) ;

void writew ( unsigned value, address ) ;

void writel ( unsigned value, address ) ;

 

     Neither the reading nor the writing functions check the validity of address.

     reading and writing functions are provided by some platforms for 64-bit .

 

Accessing Kernel Memory from User Space

 

The memory of video card is to be accessed from user space. This memory can be mapped to user space using the mmap() call.

 

A short example from vgalib:

#include <sys/types.h>

mem_fd = open (/dev/mem, O_RDWR)) == NULL);

graph_mem = malloc(GRAPH_SIZE+PAGE_SIZE-1);

 

Now map the memory

graph_mem = (unsigned char *) mmap ((caddr_t)graph_mem,

GRAPH_SIZE, PROT_READ|PROT_WRITE,

MAP_SHARED | MAP_FIXED, mem_fd,

GRAPH_BASE);