Prex Home > Document Index > Driver-Kernel Interface

Prex Driver-Kernel Interface

Version 1.0.2, 2007/12/23

Table of Contents



Introduction

The Prex kernel provides the minimum service for the device drivers. Since the driver module is separated from the kernel module, the drivers can not access other kernel functions beyond this interface. This mechanism helps to isolate the kernel from the driver codes.

This document describes the Driver-Kernel Interface (DKI) that can be used by device drivers.

General Information

Data Types

The following data types are defined by kernel.

Data type Description
device_t Used to identify the device object.
task_t Used to identify the task.

Calls from ISR

The driver-kernel service is limited at interrupt level because the kernel does not synchronize all data accesses for interrupt level access. So, the device driver can use only the following functions from the interrupt service routine.

  • irq_lock()
  • irq_unlock()
  • sched_wakeup()
  • sched_stat()
  • timer_callout()
  • timer_stop()
  • timer_count()
  • sched_lock()
  • sched_unlock()
  • sched_tsleep()
  • sched_wakeup()
  • sched_dpc()
  • exception_post()
  • printk()
  • panic()
  • machine_reset()
  • machine_dump()

Device Object

The device object is created by the driver to communicate to the application. Usually, the driver creates a device object for an existing physical device. And, it can also be used to handle logical or virtual devices.

device_t device_create(const struct devio *io, const char *name, int flags);
int device_delete(device_t dev);
int device_broadcast(int event, int force);
device_create()
Creates device object with the specified name in name. The io argument points to the device I/O table for the device object. This function returns the ID of the created device object on success, or 0 on failure.
/*
 * Device object
 */
typedef int *device_t;
All device object has its associated device I/O table. The table defines the function pointer for the corresponding request routines.
/*
 * Device I/O table
 */
struct devio {
    int     (*open)(device_t dev, int mode);
    int     (*close)(device_t dev);
    int     (*read)(device_t dev, char *buf, u_long *size, u_long offset);
    int     (*write)(device_t dev, char *buf, u_long *size, u_long offset);
    int     (*ioctl)(device_t dev, int cmd, u_long arg);
    int     (*event)(device_t dev, int event);
};
The driver can specify the one same I/O table for the different device objects. All device I/O routine can get the device object ID as its first argument. So, each routine can determine the request is sent to which device object.
device_delete()
Deletes the device object specified in dev. This function returns ENODEV if the specified device object does not exist.
device_broadcast()
Broadcasts the message specified by event to all device objects. If force is true, a kernel will ignore the value returned by each driver, and continue event notification. If force is false and any driver returns any error for the event, a kernel stops the event notification. In this case, this function returns an error code which is returned by that driver.

Kernel Memory

The kernel provides the following memory allocation services for drivers. Please note that it can not allocate lager buffer than one page. If the driver needs larger buffer, it should use page_alloc() instead of kmem_alloc().
void *kmem_alloc(size_t size);
void  kmem_free(void *ptr);
void *kmem_map(void *addr, size_t size);
kmem_alloc()
Allocates the kernel buffer for the specified size bytes. It returns the pointer to the allocated buffer on success, or NULL on failure.
kmem_free()
Frees the allocated kernel buffer pointed by ptr.
kmem_map()
Maps the specified virtual address addr to the kernel address. It returns the pointer mapped in the kernel memory on success, or NULL if there is no mapped memory.

User Memory

Since an access to user memory may cause a page fault, the user buffer manipulation is handled by the kernel core code. The driver should not access the user buffer directly. Instead, it should use the following kernel services.

int umem_copyin(void *uaddr, void *kaddr, size_t len);
int umem_copyout(void *kaddr, void *uaddr, size_t len);
int umem_strnlen(const char *uaddr, size_t maxlen, size_t *len);
umem_copyin
Copies the data from the user buffer to the kernel area. Returns 0 on success, or EFAULT on failure.
umem_copyout
Copies the data from the kernel buffer to the user area. Returns 0 on success, or EFAULT on failure.
umem_strnlen
Gets the length of specified string in uaddr. Returns 0 on success, or EFAULT on failure. The returned length does not include a NULL terminator.

Physical Page

void *page_alloc(u_long size);
void  page_free(void *addr, u_long size);
int   page_reserve(void *addr, u_long size);
page_alloc()
Allocates continuous pages for the specified size bytes. This function returns the physical address of the allocated pages, or returns NULL on failure. The kernel does not zero-fill this new page. The requested size is automatically round up to the page boundary.
page_free()
Frees allocated page block. The caller must provide the size information in size argument that was specified for page_alloc().
page_reserve()
Reserves pages in the specified address. This function returns 0 on success, or -1 on failure.

Interrupt

The Prex kernel encapsulates the save/restore of the previous interrupt state. irq_lock() will automatically save the previous interrupt state if needed. So, the driver must call irq_unlock() for the same count of the irq_lock() call.

int  irq_attach(int irqno, int prio, int shared, int (*isr)(int), void (*ist)(int));
void irq_detach(int handle);
void irq_lock(void);
void irq_unlock(void);
irq_attach()
Attaches to the ISR (interrupt service request) and ist (interrupt service thread) to the interrupt vector specified in irqno. The argument prio is the logical interrupt priority level. The smaller priority value is higher priority for interrupt processing. The caller can specify one of the following interrupt priority levels.
/*
 * Interrupt priority levels
 */
#define IPL_NONE	0	/* Nothing */
#define IPL_COMM	1	/* Serial, Parallel */
#define IPL_BLOCK	2	/* FDD, IDE */
#define IPL_NET		3	/* Network */
#define IPL_DISPLAY	4	/* Screen */
#define IPL_INPUT	5	/* Keyboard, Mouse */
#define IPL_AUDIO	6	/* Audio */
#define IPL_BUS		7	/* USB, PCCARD */
#define IPL_RTC		8	/* RTC Alarm */
#define IPL_PROFILE	9	/* Profiling timer */
#define IPL_CLOCK	10	/* System Clock Timer */
#define IPL_HIGH	11	/* Everything */
If shared argument is true, the kernel allows the other irq owner to attach to the same irq vector.
irq_detach()
Detaches the interrupt from the IRQ specified by handle.
irq_lock()
Masks all H/W interrupts, and increments the IRQ lock count.
irq_unlock()
Decrements the IRQ lock count. If the IRQ lock count becomes 0, the H/W interrupts are unmasked.

Scheduler

The thread can sleep/wakeup for the specific event. The event works as the queue of the sleeping threads.

void sched_lock(void);
void sched_unlock(void);
int  sched_tsleep(struct event *evt, u_long timeout);
void sched_wakeup(struct event *evt);
void sched_dpc(struct dpc *dpc, void (*func)(void *), void *arg);
sched_lock()
Disables the thread switch, and increments the scheduling lock count. This is used to synchronize the thread execution to protect global resources. Since the scheduling lock count can be nested, the caller must call the sched_unlock() routine the same number of lock count.
sched_unlock()
Decrements the scheduling lock count. If the scheduling lock count becomes 0, the thread switch is enabled again.
sched_tsleep()
Sleep the current thread until specified event occurs. The caller can specify timeout value in msec. If the timeout value is 0, the timeout timer does not work.
/*
 * Event for sleep/wakeup
 */
struct event {
    struct queue    sleepq;         /* Queue for waiting thread */
    char            *name;          /* Event name */
};
sched_wakeup()
Wakes up all threads that are waiting for the specified event.
sched_dpc()
Programs DPC (Deferred Procedure Call).
/*
 * DPC object
 */
struct dpc {
	struct queue	link;		/* Linkage on DPC queue */
	int		state;
	void		(*func)(void *); /* Call back routine */
	void		*arg;		/* Argument to pass */
};

Timer

void   timer_callout(struct timer *tmr, void (*func)(u_long), u_long arg, u_long msec);
void   timer_stop(struct timer *tmr);
u_long timer_delay(u_long msec);
u_long timer_count(void);
int    timer_hook(void (*func)(int));
timer_callout()
Requests a call out timer. The specified func routine will be called with arg argument after msec. The caller must allocate the memory for the timer structure for tmr.
/*
 * Timer structure
 */
struct timer {
    struct list link;           /* Linkage on timer chain */
    int         active;         /* True if active */
    u_long      expire;         /* Expire time (ticks) */
    u_long      interval;       /* Time interval */
    void        (*func)(void *); /* Function to call */
    void        *arg;           /* Function argument */
    struct event event;         /* Event for this timer */
};
timer_stop()
Stops a running timer.
timer_delay()
Delays thread execution.
timer_count()
Returns current timer count (ticks since bootup).
timer_hook()
Installs a hook routine for the timer tick.

Miscellaneous

int   task_capable(task_t task);
int   exception_post(task_t task, int exc);
void *phys_to_virt(void *p_addr);
void *virt_to_phys(void *v_addr);
void  machine_reset(void);
void  machine_dump(int index);
void  debug_attach(void (*func)(char *));
void  printk(const char *fmt, ...);
void  panic(const char *fmt, ...);
task_capable()
Checks the task capability for the speciifed item.
exception_post()
Posts an exception for the specific task.
machine_reset()
Resets the system.
machine_dump()
Dumps the system information for debug.
phys_to_virt()
Return the kernel virtual address from specifid physical address.
virt_to_phys()
Return the mapped physical address from specifid virtual address.
debug_attach()
Attaches to the external output routine for the printk().
printk()
Prints the driver message to the output device. The message is enabled only with debugging kernel.
panic()
Stops the system for the fatal error.