NEXT UP previous
Next: Basic Entry Points

Working Storage

Very often a device driver will need some internal working storage space to keep temporary values during its execution. This can range from a few bytes just to store some internal variables, to large numbers of data buffers where a lot of data needs to be stored in transit, perhaps for several device special files which may be open at the same time.

Linux makes several different data buffering techniques available to the device driver writer, each of which has its own advantages and disadvantages.

Private Buffers

The simplest way to allocate memory to a device driver is just to declare a variable in the driver source code which is the required number of bytes in size. So, for instance, if you need an 800-byte buffer for some reason, to be used for working storage, this could just be declared in the code as a global array:

	static char buffer [800];

The use of the keyword static outside any of the functions means that the buffer will only be visible to the code contained in the same file as the declaration (i.e. just to this device driver).

The use of private buffers like this has several disadvantages:

mem_start

The last of these problems can be overcome by using another memory allocation mechanism mem_start. This is a Linux specific memory allocation mechanism used at system boot time, when a device driver's init() function is called, though, for kernels from 1.1.33 onwards, this technique is no longer available.

The value mem_start is a pointer to the start of available memory. This value is passed as a parameter into your device driver's init() function. When the init() function terminates, it is expected to return the same thing - a pointer to the start of available memory. This gives init() the opportunity to grab some system memory for use by the driver, just by adding the required memory size to the mem_start value received as a parameter and then returning this new value back to the kernel instead.

So, the sequence now is that when the init() function has successfully registered the device driver and its major device number, and tested that the hardware is present, it should then add to the mem_start value the size, in bytes, of any memory buffers the driver will need.

The new value of mem_start is then returned by the init() function and the block of memory of the required size starting at the old mem_start address is then reserved for the sole use of the driver.

The obvious advantages of this over straight private buffers are that the memory block need not be allocated if the hardware is not present or if the driver could not be registered, and that these decisions are left until system boot time so that each time the system is booted the outcome may be different.

Using this technique still has the disadvantage that memory allocation is a 'one off' operation, meaning that no extra memory can be allocated after the initial block is taken and also that the block of memory cannot be released even if the driver has no further use for it.

kmalloc Function

To overcome these problems what is required is a kernel version of the dynamic memory allocation scheme provided to user processes by the library functions malloc() and free.

This functionality is provided in the kernel by the kmalloc() and kfree() functions. The prototype for kmalloc() is:

	void *kmalloc(size_t size, int priority);

where size is the number of bytes of memory you want to have allocated, and the return value is a pointer to the start of the allocated memory or a zero pointer on error.

The priority parameter requires a little explanation. Basically, it is possible when you make a request for a particular block of memory that the kmalloc() function will be unable to comply immediately. However, if you are prepared for your driver routines to sleep while the memory is made available, usually by rearranging some memory pages via swap space, then kmalloc() can usually satisfy your request eventually.

If you are prepared to wait when necessary then the priority value GFP_KERNEL should be used. In situations where an indeterminate-length wait would be unacceptable (and there are some) then the priority value GFP_ATOMIC should be used instead. In this case, if kmalloc() is unable to satisfy your request immediately then it will return with the value zero.

An important case where you need to have the GFP_ATOMIC priority available is where you need to call kmalloc() from within an interrupt service routine. This is because, as a general rule, you must not allow your interrupt service routines to sleep.

Another option is that you might want to make sure that it is possible to do DMA transfers into the memory block returned by kmalloc(). On many machines this isn't a problem but on some PCs DMA transfers can only occur in the first 16 Mbytes of memory. To make sure it isn't a problem you just need to OR the value GFP_DMA into the priority value and kmalloc() will return a memory block guaranteed to be usable for DMA transfers.

Using kmalloc(), blocks of up to 128 KB of memory can be allocated (actually, a few bytes less than this because of administrative overhead).

Memory allocated with kmalloc() can be released back to the system with kfree_s(), whose prototype is:

	void kfree_s(void *ptr, int size);

where ptr is a pointer value, previously returned by kmalloc() and size is the size of the memory block being freed, in bytes. The size value should be the same as the size parameter passed to kmalloc() when the memory was allocated. If the size of the memory block is unknown for some reason, then kfree_s() can be called with a size parameter of zero, in which case it will use an internal search to find the size.

Rather than call kfree_s() with a zero size parameter, there is a Linux macro, defined in <linux/malloc.h> called kfree().

	#define kfree(n) kfree_s((n), 0)

vmalloc

Blocks of memory allocated by kmalloc() are physically contiguous, as required in some hardware applications. Where this constraint can be relaxed, a relatively new memory allocation function can be used instead:

	void *vmalloc (unsigned long size);

where size is the required memory block size, in bytes. The function returns a pointer to the allocated memory block or zero on error.

The memory supplied by vmalloc() cannot be used for DMA transfers nor can vmalloc() be called from within an interrupt service routine.

Any memory blocks allocated with vmalloc() can be released back to the system with vfree():

	void vfree(void *ptr);

where ptr is a pointer value returned by a previous vmalloc() call.

As you can see, these last two memory allocation techniques overcome all the disadvantages of the private buffer and mem_start methods and they should be used in preference in any serious device driver you write.


NEXT UP previous
Next: Basic Entry Points