노무현 대통령 배너


2007. 8. 20. 14:00

VxWorks - Device drivers in a nut shell

From: http://www.ayyalasoft.com/VxWorks-device-drivers.htm

VxWorks - Device drivers in a nut shell

Prasad Ayyalasomayajula

Allen Tully

#

# (C) Copyright 2000

# Copyright 2001 by AyyalaSoft, except when noted otherwise. All rights reserved by AyyalaSoft. payyalas@ayyalasoft.com, payyalas@yahoo.com

#

# See file CREDITS for list of people who contributed to this

# project.

#

# This program is free software; you can redistribute it and/or

# modify it under the terms of the GNU General Public License as

# published by the Free Software Foundation; either version 2 of

# the License, or (at your option) any later version.

#

# This program is distributed in the hope that it will be useful,

# but WITHOUT ANY WARRANTY; without even the implied warranty of

# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the

# GNU General Public License for more details.

#

# You should have received a copy of the GNU General Public License

# along with this program; if not, write to the Free Software

# Foundation, Inc., 59 Temple Place, Suite 330, Boston,

# MA 02111-1307 USA

#

Legal Stuff: You are at your own risk on using the contents of this material. AyyalaSoft is not responsible in any way for your assumptions, mistakes based on the contents of this material.

Table Of Contents

Preface

Chapter 1: REAL-TIME SYTEMS AND VXWORKS

Chapter 2: Tour of VxWorks

Chapter 3: Interrupts and Interrupt handling

Chapter 4: Devices and Drivers

Chapter 5: Character Drivers

Chapter 6: Block Drivers

Chapter 7: Real World Scenarios

Chapter 8: User Interaction with a driver

Chapter 9: Advanced Topics

Preface

This book if for device driver developers, who have general back ground in real time operating systems. This book addresses device driver development using VxWorks/Tornado 5.4.

Acknowledgements

We referred to VxWorks 5.4 programmers manual and other websites to make sure that the content is as accurate as possible. We hope you enjoy this work, and drop an email to me at payyalas@yahoo.com for suggested improvements. Very soon we will add few more code samples to my website www.ayyalasoft.com.

Regards

Prasad Ayyalasomayajula

Allen Tully

1) REAL-TIME SYTEMS AND VXWORKS:

Operating systems can be categorized into real-time and non-real-time systems. A real-time system is defined as a system where the response time for an event is predictable and deterministic with minimal latency. The architecture of the operating system's scheduler, also referred to as the dispatcher, has a significant impact on the responsiveness of the OS. Preemptive scheduling ensures the highest priority task/thread always runs and doesn’t relinquish the CPU until its work is done or a higher priority task becomes available. A preemptive scheduler also implies a real-time kernel. Several aspects to consider when selecting a real-time OS are:

§ Foot print of the kernel

§ Interrupt latency

§ Interrupt response time

§ Interrupt recovery

§ Multi-tasking

§ Task context switching

§ Virtual memory support etc.,

VxWorks provides a real-time kernel that interleaves the execution of multiple tasks employing a scheduling algorithm. Thus the user sees multiple tasks executing simultaneously. VxWorks uses a single common address space for all tasks thus avoiding virtual-to-physical memory mapping. Complete virtual memory support is available with the optional vxMem library.

 

2) Tour of VxWorks

Tasks

A task is an independent program with its own thread of execution and execution context. Every task contains a structure called the task control block that is responsible for managing the task's context. A task’s context includes

· program counter or thread of execution

· CPU registers

· Stack of dynamic variables and function calls

· Signal handlers

· IO assignments

· Kernel control structures etc.,

Every task has a name and an ID associated with it. Each task is assigned a default priority as well. A task has four states as shown below.

A task can be created with taskInit() and then activated with taskActivate() routine or both these actions can be performed in a single step using taskSpawn(). Once a task is created it is set to the suspend state and suspended until it is activated, after which it is added to the ready queue to be picked up by the scheduler and run. A task may be suspended by either the debugging your task, or the occurrence an exception. The difference between the pend and suspend states is that a task pends when it is waiting for a resource. A task that is put to sleep is added to delay queue.

Scheduler

VxWorks scheduler determines which task to own the CPU time. By default, the scheduler runs a preemptive algorithm. Preemptive scheduler guarantees that the highest priority task preempts a lower priority task. There are some special cases called priority inversion which is discussed in advanced concepts.

The scheduler can be set to run round robin algorithm which is a time slicing algorithm.

Mutual Exclusion

Mutual exclusion can be implemented in VxWorks in the following three ways.

  • Semaphores
  • Disabling Interrupts
  • Disabling the scheduler using taskLock()

Semaphores

VxWorks supports three types of semaphores, binary, mutual exclusion, and counting, each of which is optimized for a specific application. Semaphores are generally used for task synchronization and communication, and protection of shared resources also referred to as concurrency control or mutual exclusion.

§ Binary semaphores are the fastest and are best suited for basic task synchronization and communication.

§ Mutual exclusion semaphores are sophisticated binary semaphores that are designed to address the issues relating to task priority inversion and semaphore deletion in a multitasking environment.

§ Counting semaphores maintain a count of the number of times a resource is given. This is useful when an action is required for each event occurrence. For example if you have ten buffers, and multiple tasks can grab and release the buffers, then you want to limit the access to this buffer pool using a counting semaphore.

Message Queues

VxWorks supports messages queues for inter task communication. A variable number of messages, each of variable length, can be sent to any task. ISRs and tasks can send messages but only tasks can receive messages.

Multiple tasks can wait on a single message queue and can be ordered by their priority. Messages can be marked urgent for faster delivery.

Network Intertask Communication

VxWorks supports general facilities like pipes, sockets, RPC and signals for network inter task communications.

Additional Facilities

VxWorks provides facilities like Asynchronous IO and buffered IO for application and driver development. It is also POSIX library.

3) Interrupts and Interrupt handling

Interrupt is the mechanism by which a device seeks the attention of CPU. The piece of user code that the CPU executes on interrupt is called interrupt service routine (ISR). The Kernel doesn’t transfer execution to the ISR immediately. It does some house keeping before the ISR is executed. The delay between the occurrence of interrupt and time spent by the kernel before it executes the first ISR instruction is called Interrupt response time. This equals the sum of interrupt latency and time to save CPU’s context and execution time of kernel ISR entry function.

 VxWorks provides a special context for interrupt service code to avoid task context switching, and thus renders fast response. VxWorks supplies interrupt routines which connect to C functions and pass arguments to the functions to be executed at interrupt level. To return from an interrupt, the connected function simply returns. A routine connected to an interrupt in this way is referred to as an interrupt service routine (ISR) or interrupt handler. When an interrupt occurs, the registers are saved, a stack for the arguments to be passed is set up, then the C function is called. On return from the ISR, stack and registers are restored.
 IntConnect(INUM_TO_IVEC(intNum), intHandler, argToHandler) allows C functions to be connected to any interrupt. The first argument to this routine is the byte offset of the interrupt vector to connect to. The second argument is the interrupt handler and the last is any argument to this handler.
 One can disable interrupts using intLock() for synchronization. Care should be taken to re-enable the interrupts using intUnlock(). If you are planning for nested interrupts, you should not disable interrupts using intLock(). Also make sure that your code is reentrant and you allocate enough stack resources for nesting.
t;/SPAN>Points to remember
a) Within an ISR, limited capabilities exist for the user code. Not all library functions are available. 
b) Since memory facilities malloc() and free() take semaphores, they cannot be called within ISR. 
c) Any blocking call is to be avoided.
d) Semaphores can be given, but not taken from an ISR.
t;/SPAN>Points to remember
ISR can communicate with user tasks via
a) shared memory and ring buffers 
b) release of semaphores 
c) signal tasks
d) writing to pipes
e) sending messages using message queue
Understanding ISR and what goes on within interrupt handler is the key to designing your driver. Many real world drivers just have an interrupt handler and interact with user and device without the rest of the interfaces. Please refer to 7) for examples.

4) Devices and Drivers

A driver supplies a uniform device independent logical interface to the user to interact with a device. A device can be a piece of hardware such as your hard drive or can be a piece of software such as a pipe or a socket, but a driver is always a software module. A driver can control multiple devices. If the architecture allows virtual memory, driver works in a logical/virtual address space, but a device works in a physical address space.

All interactions with devices in VxWorks are performed through the IOsub-system. VxWorks treats all devices as files. Devices are opened just like normal files are for IO operations. An example device is /tyCo/0 that represents a serial channel. When a filename is specified in an IO call by the user task, the IO system searches for a device with a name that matches 
The specified filename. Two most important devices are character devices or
non-block and block devices. Character devices perform IO operations character
by character. Block devices are used for storing file systems. Block devices perform IO in blocks of characters and can support complicated operations such as random access. Block devices are accessed via file system routines as shown in the above figure. The driver interface to character devices are not filesystem routines.

t;/SPAN>Points to remember

a) A character device is named usually at the system initialization

b) Block devices are always associated with a file system like raw file system, dos file system. They are named when initialized for a specific file system.

c) Drivers can be loaded and unloaded dynamically.

d) Drivers work in thee context of the task invoked an interface routine. Hence drivers are preemptable and should be designed as such.

5) Character Drivers

creat(), remove(), open(), close(), read(), write(), ioctl() are the seven standard driver interfaces that can be exposed to the user. Not all of the interfaces are mandatory.

Four steps are involved in the driver design and install process

Step 1: Decide the interfaces you want to expose and install the driver

The following piece of code is the driver initialization routine.

STATUS myDrv ()

{

myDrvNum = iosDrvInstall( myDevCreate /*create*/,

0 /*remove() is null*/,

myDevOpen /*open()*/,

0 /*close()*/,

myDevRead /*read()*/,

myDevWrite /*write()*/,

myDevIoctl /*ioctl()*/

);

/* connect the ISR*/

intConnect(intvec, myIntHandler,0);

}

As shown in the above piece of code, we can skip the driver interface routines like remove and close. But it is always a good practice to include them and return an error. VxWorks returns an error on your behalf, if it doesn’t find a particular interface. Also you can initialize any relevant data structures in the myDrv routine.

t;/SPAN>Points to remember

1) myDrvNum is used by the IO subsystem in locating your device.

2) The device driver gets installed into a driver table. The index is based on driver number. Since a driver can service more then one device, a list of devices are tied together in a linked list, with the same driver number, but different device names and device descriptors.

Step 2: Create your device descriptor structure

Capture the essence of your device in a structure. This structure will hold all the information related to your device. This structure will be passed back by the IO subsystem, as a parameter to the rest of the interfaces like read(), write(), ioctl() etc,. You can even get this structure within your ISR.

typedef struct

{

DEV_HDR myDevHdr;

BOOL isDevAvailable;

Semaphore getAccess;

}MY_DEV;

If you are using semaphores to control the access to your device, make sure you create and initialize them before you make use of them.

Once you are ready with your structure, pass it as an address to to iosDevAdd as shown in the below piece of code.

STATUS myDevCreate( char* name, …)

{

MY_DEV* pMyDevice;

status = iosDevAdd( pMyDevice, /* pointer to MY_DEV device */

name, /* input param */

myDrvNum /* return value from iosDrvInstall */

);

/* do other work as necessary */

}

iosDevAdd takes three arguments. The first argument is the address of device descriptor structure. A device descriptor structure always starts with DEV_HDR structure as it’s first member. It can contain any other private data structures for your own use. The second argument is the name of the device. The third argument is the driver number, the return value of iosDrvInstall.

t;/SPAN>Points to remember

IO subsystem searches the correct device based on device name and driver number. They are held in a header structure DEV_HDR.

Step 3: Finish the definitions of all other interfaces

STATUS myDevOpen(MY_DEV* pMyDev, char* additionalInfo, int mode)

{

}

STATUS myDevRead(MY_DEV* pMyDev, char* buffer, int nBytes)

{

/* read nBytes from the device and put them into the buffer*/

}

STATUS myDevWrite(MY_DEV* pMyDev, char* buffer, int nBytes)

{

/* write to the device from buffer if the device has room*/

}

IOCTL needs some explanation. It is through IOCTL that a user can control the device.

This the preferred way of controlling the device. The code within the IOCTL depends upon the

Way your device perform and the way you want to control the device.

STATUS myDevIoctl(MY_DEV* pMyDev, int request, int arg)

{

switch(request)

{

CASE SET_DEVICE:

/* set the device*/

CASE MODIFY_PARAM:

}

}

Step 4: Complete your interrupt handler

Void myIntHandler(arg)

{

/* disable any further interrupts */

intLock();

// now read the interrupt register and indicate to the other tasks that you received an interrupt.

// You can do this in multiple ways. Refer to 2)1.

//One easy way is to give a semaphore

semGive(getAccess);

/* re-enable interrupts*/

intUnlock();

return;

}

Once your interrupt handler has been installed using intConnect(), the kernel will call your ISR when the CPU receives an interrupt from the device.

6) Block Drivers

A block device is a device that is organized as a sequence of individually accessible blocks of data. A block is the smallest addressable unit on a block device. Block devices have a slightly different interface than that of other IO drivers. Rather than interacting directly with the IO system, block drivers interact via file-system. The file system in turn interacts with the IO system. Every block device is typically associated with a specific file system. DOS, SCCI, and raw file systems are supported.

Block devices are divided into two categories based on their write capabilities. Direct Access BLOCK Devices are slightly different from SEQUENTIAL Devices in that data can be written only to the end of written medium for sequential devices, where as for true block devices, data can be written any where randomly.

There is no difference between BLOCK and Sequential devices as far as reading from the device is concerned.

A device driver for a block device must provide a means for creating logical device structure, a BLK_DEV for direct access block devices and SEQ_DEV for sequential block devices. BLK_DEV/ SEQ_DEV structures describe the device, contain routines to access the device, describe the device in a general fashion so that the underlying file system that serves this device can know about this device.

t;/SPAN>Points to remember

1) When the driver creates the block device, the device has no name or file system associated with it. These are assigned during the device initialization routine for the specific file system (example dosFsDevInit()).

2) The low-level driver is not installed in the IO system driver table. Instead the underlying file system is installed as an entry into the driver table. Only one entry of file system is installed even if multiple devices are using this file system.

The following three steps are involved in writing a Block device driver. I shall explain this example by using ram driver with DOS as the underlying file system.

Ram driver emulates a disk driver, but actually keeps all data in memory. The memory

location and size are specified when the "disk" is created. The RAM disk feature is useful

when data must be preserved between boots of VxWorks or when sharing data between

CPUs. The RAM driver is called in response to ioctl( ) codes in the same manner as a normal

disk driver. When the file system is unable to handle a specific ioctl( ) request, it is passed

to the ramDrv driver. Although there is no physical device to be controlled, ramDrv does

handle a FIODISKFORMAT request, which always returns OK. All other ioctl( ) requests

return an error and set the task’s errno to S_ioLib_UNKNOWN_REQUEST.

Step 1: Initialize and finish the interfaces within BLK_DEV structure

Declare all your data structures, create your semaphores, initialize the interrupt vectors and enable the interrupts just as been discussed for character devices.

This step is required, only when you are creating your own device and not making use of existing block devices (like ram drive, scsi device etc.,) supported by VxWorks. Check VxWorks reference manual and programmers guide before you fill out the interfaces.

t;/SPAN>Points to remember

If these interfaces are filled, the file system will call them for you, if not it will call the default routines of the file system itself.

BLK_DEV is a structure that has the address of certain routines. If you decided to fill the structure, just declare the required interfaces and pass the address of the interfaces to BLK_DEV.

Declare your private device descriptor structure. Or you can directly use BLK_DEV structure.

typedef struct

{

BLK_DEV myDev;

Bool privateData;

Semaphore giveAccess;

}DEVICE;

The various fields within BLK_DEV structure are

bd_blkRd Address of driver routine that reads blocks from the device

If your device is myBlkDevice, then call this routine as myBlkDevRd.

STATUS myBlkDevRd(

DEVICE* pDev /* pointer to driver’s device descriptor. The file system passes the address of BLK_DEV structure. These two are equivalent because BLK_DEV is the first item of DEVICE structure*/,

Int startBlk,

Int numBlks,

Char* pBuf /*the address where data read is copied to */

);

bd_blkWrt Address of driver routine that writes blocks to the device

STATUS myBlkDevWrt(

DEVICE* pDev /* pointer to driver’s device descriptor. */,

Int startBlk,

Int numBlks,

Char* pBuf /*the address where data is copied from and written to the device */

);