wiki:TBR/UserManual/DriverManager

DriverManager

Driver Manager

This document describes the Driver Manager patch for RTEMS-4.10. The LEON2 and the LEON3 BSP have been used to test the Driver Manager, the two Hardware platforms are different when it comes to Plug and Play. The LEON2 systems traditionally use a hardcoded register location and IRQ assignment, whereas in LEON3 systems Plug & Play is used to find all cores such a Memory controller, Timer, IRQ Controller, Ethernet MAC, etc. The Plug & Play information in a LEON3 system contain IRQ number, register base addresses, memory space regions etc.

Purpose

The purpose of the Driver Manager is mainly,

  • more easily reuse driver code
  • enable drivers to be written independent of how certain services are implemented.
    • interrupt handling (registration, masking, unmasking, clearing/acknowledging when level sensitive)
    • memory space translation
    • Bus information (Bus/Core? frequency, Snooping/Cache? options, Location in system)
  • Unify the way drivers are configured. The driver registration string is replaced with "driver resources" managed per bus.
  • build images suitable for multiple targets with different hardware present, for example different PCI Host controllers.
  • Hardware bus topology awareness in software, this makes it easier to access which drivers are loaded and what hardware is present.

Overview

The Driver Manager fills some of the purposes the "The Linux Device Model" fills.

It is a small and simple manager that connects a driver to a device and a device to driver resources. The driver is presented an API where it is possible to get the bus frequency, manage interrupt, get base register addresses, get configuration for a certain hardware device, etc.

A device is situated on a bus, a driver for a bus is called a bus driver. The bus driver tells the driver manager about new devices by a procedure called device registration, basically the bus driver fills in a data structure describing a specific hardware device and gives it to the manager.

Once a device has been registered by a bus driver the manager tries to find a suitable driver by comparing the information about the hardware (provided by the bus driver from Plug & Play) to the driver hardware support information (provided by the driver). When a driver is found that can handle the hardware device the device and driver are "united". The device is put into a list later processed by the driver manager. Each device in the list is picked out and initialized in a two step process (init1 and init2) by calling the device driver's functions, init1() and init2(). If the device driver happens to be a bus driver more devices will be registered and put at the end of the device list. When there is no more devices left in the list the manager will complete.

All device drivers and bus drivers are registered before starting scanning for devices.

The driver manager uses the concepts bus, device, driver and driver resources. A bus represents a device with child devices. A device represents a certain hardware situated on a bus. A driver handles one or more devices of the same hardware type. Driver resources are configuration parameters targeted one specific driver and one specific device, the resouces consists of an array with name, type and value. The resources are hardcoded at compile time by the user.

The topmost device is a bus device, the root bus device. The driver for that device is called the root bus driver. All devices are connected to the root bus device.

Initialization steps

The root bus device is the first device created during driver manager initialization. It is assigned a driver, the root bus driver, manually. The root bus driver must be configured by the user or BSP prior to driver manager initialization.

After all drivers have been registered to the driver manager the device initialization begins. The root bus device is initialized and reports the available hardware devices on the bus. Once the root bus device is done initializing, the driver manager proceeds to initialize the found devices in a two step process.

Once a device has been registered by a bus driver the manager tries to find a suitable driver by comparing the information about the hardware (provided by the bus driver) to the driver hardware support information (provided by the driver). When a driver is found that can handle the device the device and driver are "united". The device is put into a list later processed by the driver manager (init1 stage). When the root bus device driver is done initializing the driver manager proceeds to initialize the found devices in a two step process.

All devices are initialized in a two step process, called init1 and init2. Each driver provides two function pointers init1 and init2 which are called in order. All devices' init1 is first called, when completed the driver manager enter stage two where all devices are called once more, this time using the init2 function. The initialization process is separated in two stages to make it possible for drivers to rely on other drivers services being initialized. The order in which the devices are initialized on a bus can not be known, it is the same order as they were registered in which often depends on how the hardware is found in the Plug and Play information.

All drivers that export some functionality to other drivers, for example memory controller drivers, must initialize their API in stage1 in order to guarantee the service for other drivers when they enter stage 2.

All devices and bus devices should be found and registered in stage1. If not, they are called "hotplug" devices by the driver manager. Hotplugging has currently no special support in the driver manager.

Below are some concepts described that are used by the manager.

Driver Interface

The driver manager provides the drivers with a common interface independent of bus type. In order for the driver manager to implement one interface it relies on the bus driver to make bus dependent operations. The bus drivers are the core of the functionality provided by the driver manager.

It may be bus dependent how to find the bus frequency, manage interrupts (level sensitive?), Plug & Play information, translate addresses, find a driver for a device etc.

When the driver manager interface is not enough for a bus type, the bus driver may implement functions of its own extending the driver manager interface but still taking advantage of the device and driver structure introduced by the manager. Perhaps a stupid example but anyway, the example below shows how a PCI bus driver could make PCI Read Configuration Space function simpler by removing the knowledge of Bus,Slot,Func in a PCI device driver, instead of calling pci_cfg_read_word directly:

int pci_cfg_read_word(int bus, int slot, int func, int offset, unsigned int *buf);

int pcimgr_cfg_read_word(rtems_drvmgr_dev_info *dev, int offset, unsigned int *buf)
{
	/* Get Bus specific information previously prepared by bus driver */
	struct pci_dev_info *pciinfo = (struct pci_dev_info *)dev->businfo;

	return pci_cfg_read_word(pciinfo->bus, pciinfo->slot, pciinfo->func, offset, buf);
}

Bus Driver

The Driver Manager needs to be informed about hardware devices present in the system, that is what the bus driver does. The bus driver reads a hardcoded set up or Plug & Play information and registers devices to the manager. The bus driver must also provide function pointers to a couple of services in order for the driver manager to present a common driver interface to device drivers.

One of the most important services is the ability to unite a hardware device with a driver, for this to happen a driver must provide information about what hardware is supported so that the bus driver can compare that with the found hardware.

/*! Bus information. Describes a bus. */
struct rtems_drvmgr_bus_info {
	int				bus_type;	/*!< Type of bus */
	struct rtems_drvmgr_bus_info	*next;		/*!< Next Bus */
	struct rtems_drvmgr_dev_info	*dev;		/*!< Bus device, the hardware... */
	void				*priv;		/*!< Private data structure used by BUS driver */
	struct rtems_drvmgr_dev_info	*children;	/*!< Hardware devices on this bus */
	struct rtems_drvmgr_bus_ops	*ops;		/*!< Bus operations supported by this bus driver */
	int				dev_cnt;	/*!< Number of devices this bus has */
	struct rtems_drvmgr_bus_res	*reslist;	/*!< Bus resources, head of a linked list of resources. */
	struct rtems_drvmgr_mmap_entry	*mmaps;		/*!< Memory Map Translation, array of address spaces */
};

Root bus driver

The driver manager needs to know what driver should handle the CPU bus. The root bus driver is registered separately and before the driver manager is initialized.

Device

Represents a hardware normally found by the bus driver. A device is created and registered by the bus driver, once registered the driver manager finds the driver that supports the hardware, then a driver is united with the device. Each device structure has a private pointer that the driver use to access the information needed by the driver.

/* States of a device */
#define DEV_STATE_INIT1_DONE	0x00000001	/* Init 1 Stage has been done, not neccessarily successful */
#define DEV_STATE_INIT2_DONE	0x00000002	/* Init 2 Stage has been done, not neccessarily successful */
#define DEV_STATE_INIT1_FAILED	0x00000010	/* Init 1 Stage Failed */
#define DEV_STATE_INIT2_FAILED	0x00000020	/* Init 2 Stage Failed */
#define DEV_STATE_UNITED	0x00000100	/* Device United with Device Driver */
#define DEV_STATE_DELETED	0x00000200	/* Device has been deleted (unregistered) */
#define DEV_STATE_IGNORED	0x00000400	/* Device was ignored according to user's request, the device
						 * was never reported to it's driver (as expected).
						 */
/*! Device information */
struct rtems_drvmgr_dev_info {
	struct rtems_drvmgr_dev_info	*next;		/*!< Next device */
	struct rtems_drvmgr_dev_info	*next_in_bus;	/*!< Next device on the same bus */
	struct rtems_drvmgr_dev_info	*next_in_drv;	/*!< Next device using the same driver */

	struct rtems_drvmgr_drv_info	*drv;		/*!< The driver owning this device */
	struct rtems_drvmgr_bus_info	*parent;	/*!< Bus that this device resides on */
	short				minor_drv;	/*!< Device number on driver (often minor in filesystem) */
	short				minor_bus;	/*!< Device number on bus (for device separation) */
	unsigned int			state;		/*!< State of this device, see DEV_STATE_* */
	char				*name;		/*!< Name of Device Hardware */
	void				*priv;		/*!< Pointer to driver private device structure */
	void				*businfo;	/*!< Host bus specific information */
	struct rtems_drvmgr_bus_info	*bus;		/*!< Pointer to bus, set only if this is a bus */
	int				error;		/*!< Error state returned by driver */
};

Device State

During the initialization of a device the initialization level (init1 or init2) changes, to hold that information a device state integer is maintained for each device. The device state is updated when,

¤ Assigning a driver ¤ Init1 is completed or erroneous ¤ Init2 is completed or erroneous ¤ A device is deleted by the user ¤ A device must be ignored by user's request

The manager uses the state internaly but it may also be of improtance to the user when a device failed.

Device Error Handling

During the initialization steps of a device the driver manager may not complete the initialization because of,

¤ No suitable driver found ¤ Device was requested to be ignored by user ¤ Driver failed to take device into init1 or init2 dur to an error

The driver manager removes the device from further initialization and instead of freeing the device structure the device is put into a separate list (the inactive list) and the device state is updated accordingly. When initialization fails the error field in the device structure is updated indicating the error more in detail.

A user may process all failed devices on the inactive list to determined if the error was acceptable or not.

Unregistering a device

A device may also be unregistered due to device error (parity error on the PCI bus for example) or hardware was removed (hot-plug system). A device is unregistered by calling rtems_drvmgr_dev_unregister. The device is moved from the current list to the inactive list (delete=0) or it may be removed completely to avoid memory exhaustion (delete=1).

/*! Remove a device, and all its children devices if device is a bus device. The device 
 *  driver will be requested to remove the device and once gone from bus, device and 
 *  driver list the device is put into a inactive list for debugging (this is optional
 *  by using delete argument).
 *
 * Removing the Root Bus Device is not supported.
 *
 * \param delete If non-zero the device will be deallocated, and not put into the 
 *               inacitve list.
 */
extern int rtems_drvmgr_dev_unregister(struct rtems_drvmgr_dev_info *dev, int delete);

Driver

Driver for a hardware device. It uses the driver manager services provided, which in turn rely on the bus driver. The driver holds information to identify a hardware device, it tells the driver manager what kind of bus is supported and perhaps the Plug & Play Vendor and Device ID used to identify certain hardware.

Drivers are called twice to initialize a hardware device. Init1 and Init2 stages. A delete function may optionally be defined if the driver can handle removing of hardware Also a info function to request information such as device name, type of driver, statistcs, current register content etc for interactive information printing from for example the shell.

/*! Driver operations, function pointers. */
struct rtems_drvmgr_drv_ops {
	rtems_status_code	(*init1)(struct rtems_drvmgr_dev_info *);	/*! Function doing Init Stage 1 of a hardware device */
	rtems_status_code	(*init2)(struct rtems_drvmgr_dev_info *);	/*! Function doing Init Stage 2 of a hardware device */
	rtems_status_code	(*delete)(struct rtems_drvmgr_dev_info *);	/*! Function called when device instance is to be removed */
	rtems_status_code	(*info)(struct rtems_drvmgr_dev_info *, int, int);/*! Function called to request information about a device or driver */
};

/*! Information about a driver used during registration */
struct rtems_drvmgr_drv_info {
	struct rtems_drvmgr_drv_info	*next;		/*!< Next Driver */
	struct rtems_drvmgr_dev_info	*dev;		/*!< Devices using this driver */

	unsigned long long		drv_id;		/*!< Unique Driver ID */
	char				*name;		/*!< Name of Driver */
	int				bus_type;	/*!< Type of Bus this driver supports */
	struct rtems_drvmgr_drv_ops	*ops;		/*!< Driver operations */
	unsigned int			dev_cnt;	/*!< Number of devices in dev */
};

Driver ID

All drivers must have a unique ID, it may be created by using one of the supported devices's ID. Often a device have a VENDOR:DEVICE unique for the bus. A driver ID is created by combining a bus dependent ID and the bus type number:

From drvmgr.h:

/*** Bus indentification ***/
#define DRVMGR_BUS_TYPE_NONE 0		/* Not a valid bus */
#define DRVMGR_BUS_TYPE_ROOT 1		/* Hard coded bus */
#define DRVMGR_BUS_TYPE_PCI 2		/* PCI bus */
#define DRVMGR_BUS_TYPE_AMBAPP 3	/* AMBA Plug & Play bus */

/* 64-bit identification integer definition
 *  ¤ Bus ID 8-bit [7..0]
 *  ¤ Reserved 8-bit field [63..56]
 *  ¤ Device ID specific for bus type 48-bit [55..8]  (Different buses have different unique identifications for hardware/driver.)
 *
 * ID Rules
 *  ¤ A root bus driver must always have device ID set to 0. There can only by one root bus driver
 *    for a certain bus type.
 *  ¤ A Driver ID must identify a unique hardware core
 *
 */

/* Bus ID Mask */
#define DRIVER_ID_BUS_MASK 0x00000000000000FFULL

/* Reserved Mask for future use */
#define DRIVER_ID_RSV_MASK 0xFF00000000000000ULL

/* Reserved Mask for future use */
#define DRIVER_ID_DEV_MASK 0x00FFFFFFFFFFFF00ULL

/* Set Bus ID Mask. */
#define DRIVER_ID(busid, devid) \
	(unsigned long long)( (((unsigned long long)(devid) << 8) & DRIVER_ID_DEV_MASK) | ((unsigned long long)(busid) & DRIVER_ID_BUS_MASK) )

From AMBA Plug & Play bus, ambapp_bus.h:

/* GRLIB AMBA Plug&Play Driver ID generation */
#define DRIVER_AMBAPP_ID(vendor, device) \
	DRIVER_ID(DRVMGR_BUS_TYPE_AMBAPP, ((((vendor) & 0xff) << 16) | ((device) & 0xfff)))

/*** Gaisler Hardware Device Driver IDs ***/
#define DRIVER_AMBAPP_GAISLER_GRETH_ID		DRIVER_AMBAPP_ID(VENDOR_GAISLER, GAISLER_ETHMAC)

In the above example VENDOR_GAISLER and GAISLER_GRETH are IDs assigned by the GRLIB Bus domain.

The Driver ID and a device unit number is used when assigning driver resources to a certain device. The driver resources must be in a format the driver can understand, which the driver ID makes sure of, and the driver must not use another device's resources, which the unit/core number makes sure of.

Driver Resource

A driver resource is a resource used by a driver for a certain device instance. The resource may be an integer with value 65 called "numberTxDescriptors". The resources are grouped together in arrays targeting one device instance. The resources are assigned to a bus making it possible for a driver to find the resources for a specific device, since the Driver Manager knows which bus the device is situated on. A resource is identified by BUS, DRIVER ID and Unit number. Multiple hardware devices of the same type are separated by their bus unit number which is always the same between runs. The number usually comes from the order the device if found in the plug and play information.

Below is a typical bus resource definition in a LEON3-GRLIB system. The grlib_drv_resource configures two GRSPW device drivers. The second GRSPW core will have double the amount of descriptors than the first.

/*** Resource definitions ***
 * 
 * Overview of structures:
 *  All bus resources entries (_bus_res) are linked together per bus (bus_info->reslist).
 *  One bus resource entry has a pointer to an array of driver resources (_drv_res). One driver 
 *  resouces is made out of an array of keys (rtems_drvmgr_key). All keys belongs to the 
 *  same driver and harwdare device. Each key has a Name, Type ID and Data interpreted 
 *  differently depending on the Type ID (union rtems_drvmgr_key_value).
 *
 */

/* Key Data Types */
#define KEY_TYPE_NONE		0
#define KEY_TYPE_INT 		1
#define KEY_TYPE_STRING		2
#define KEY_TYPE_POINTER	3

#define KEY_EMPTY	{NULL, KEY_TYPE_NONE, {0}}
#define RES_EMPTY	{0, 0, NULL}
#define MMAP_EMPTY	{0, 0, 0}

/*! Union of different values */
union rtems_drvmgr_key_value {
	unsigned int		i;		/*!< Key data type UNSIGNED INTEGER */
	char			*str;		/*!< Key data type STRING */
	void			*ptr;		/*!< Key data type ADDRESS/POINTER */
};

/* One key. One Value. Holding information relevant to the driver. */
struct rtems_drvmgr_key {
	char				*key_name;	/* Name of key */
	int				key_type;	/* How to interpret key_value */
	union rtems_drvmgr_key_value	key_value;	/* The value or pointer to the value */
};

/*! Driver resource entry, Driver resources for a certain device instance, containing a number of keys 
 * Where each key hold the data of interest.
 */
struct rtems_drvmgr_drv_res {
	unsigned long long	drv_id;		/*!< Identifies the driver this resource is aiming */
	int			minor_bus;	/*!< Indentifies a specific device */
	struct rtems_drvmgr_key	*keys;		/*!< First key in key array, ended with KEY_EMPTY */
};

/*! Bus resource list node */
struct rtems_drvmgr_bus_res {
	struct rtems_drvmgr_bus_res	*next;		/*!< Next resource node in list */
	struct rtems_drvmgr_drv_res	*resource;	/*!< Array of resources, one per device instance */
};

User defined Driver resources for the first three GRSPW cores. GRSPW core 1 and 2 uses the same configuration.

/* GRSPW0 resources */
struct rtems_drvmgr_key grlib_grspw0_res[] =

{
	{"txDesc", KEY_TYPE_INT, {(unsigned int)16}},
	{"rxDesc", KEY_TYPE_INT, {(unsigned int)32}},
	KEY_EMPTY
};
/* GRSPW1 and GRSPW2 resources */
struct rtems_drvmgr_key grlib_grspw1_res[] =

{
	{"txDesc", KEY_TYPE_INT, {(unsigned int)32}},
	{"rxDesc", KEY_TYPE_INT, {(unsigned int)64}},
	KEY_EMPTY
};
/* GRLIB Plug & Play bus driver resources */
struct rtems_drvmgr_drv_res grlib_drv_resources[] =

{
	{DRIVER_AMBAPP_GAISLER_GRSPW_ID, 0, &grlib_grspw0_res[0]},
	{DRIVER_AMBAPP_GAISLER_GRSPW_ID, 1, &grlib_grspw1_res[0]},
	{DRIVER_AMBAPP_GAISLER_GRSPW_ID, 2, &grlib_grspw1_res[0]},
	RES_EMPTY
};

The driver to which the resources belong is identified by a unique driver id. The driver id must be unique for the bus type, in this case AMBAPP (AMBA Plug & Play). The device instances are separated by the bus unit number, second entry in the grlib_drv_resources.

Using

Configuration

The following may be configured,

  • Driver resources per bus
  • Root bus driver
  • Driver table

The driver manager is configured by selecting drivers used by the driver manager and by registering a root bus prior to driver manager initialization described in the next section. Drivers may also be configured by using driver resources, see section "Driver Resource".

The driver resources are registered different depending on bus driver.

The root bus device driver is registered by calling root_drv_register, the root bus driver may provide a function doing this, in that case one must call that function instead. For example drv_mgr_grlib_init register the LEON3 GRLIB-AMBA Plug & Play Bus as the root bus driver and also assigns the driver resources for the root bus.

The drivers are selected by defining the array drv_mgr_drivers, it contains one function pointer per driver that is responsible to register one or more drivers. The drv_mgr_drivers can be set up by defining CONFIGURE_INIT, selecting the appropriate drivers and including drvmgr/drvmgr_confdefs.h. This approach is similar to configuring a standard RTEMS project using rtems/confdefs.h. Below is an example how to select drivers.

#include <rtems.h>
#include <bsp.h>

#define CONFIGURE_INIT

/* Standard RTEMS set up */
#define CONFIGURE_APPLICATION_NEEDS_CONSOLE_DRIVER
#define CONFIGURE_APPLICATION_NEEDS_CLOCK_DRIVER
#define CONFIGURE_RTEMS_INIT_TASKS_TABLE
#define CONFIGURE_MAXIMUM_DRIVERS 32
#include <rtems/confdefs.h>

/* Driver manager set up */
#define CONFIGURE_DRIVER_AMBAPP_GAISLER_GRETH
#define CONFIGURE_DRIVER_AMBAPP_GAISLER_GRSPW
#define CONFIGURE_DRIVER_AMBAPP_GAISLER_GRCAN
#define CONFIGURE_DRIVER_AMBAPP_GAISLER_OCCAN
#define CONFIGURE_DRIVER_AMBAPP_GAISLER_B1553BRM
#define CONFIGURE_DRIVER_AMBAPP_GAISLER_APBUART
#define CONFIGURE_DRIVER_AMBAPP_MCTRL
#define CONFIGURE_DRIVER_AMBAPP_GAISLER_PCIF
#define CONFIGURE_DRIVER_AMBAPP_GAISLER_GRPCI
#define CONFIGURE_DRIVER_PCI_GR_RASTA_IO
#define CONFIGURE_DRIVER_PCI_GR_RASTA_TMTC
#define CONFIGURE_DRIVER_PCI_GR_701
#include <drvmgr/drvmgr_confdefs.h>

Initialization

The driver manager is initialized by the user calling rtems_drvmgr_init() after the root bus driver has been registered. After the driver manager has been initialized all devices have been united with their drivers. The driver are ready for usage.

The initialization cannot currently be done before the I/O Manager intialization, this is one of the reasons it is initialized in the Init() task as below.

/* Initializing Driver Manager */ printf("Initializing manager\n"); if ( rtems_drvmgr_init() ) {

printf("Driver manager Failed to initialize\n"); exit(-1);

}

Driver Interface

The Driver Interface.

/*! Get Device pointer from Driver and Driver minor number 
 *
 * \param drv         Driver the device is united with.
 * \param minor       Driver minor number assigned to device.
 * \param pdev        Location where the Device point will be stored.
 * \return            Zero on success. -1 on failure, when device was not found in driver 
 *                    device list.
 */
int rtems_drvmgr_get_dev(
	struct rtems_drvmgr_drv_info *drv,
	int minor,
	struct rtems_drvmgr_dev_info **pdev);

/*! Get Bus frequency in Hertz. Frequency is stored into address of freq_hz.
 *
 * \param dev        The Device to get Bus frequency for.
 * \param freq_hz    Location where Bus Frequency will be stored.
 */
int rtems_drvmgr_get_freq(struct rtems_drvmgr_dev_info *dev, unsigned int *freq_hz);

/*! Get device name prefix, this name can be used to register a unique name in the 
 *  filesystem or to get an idea where the device is located.
 *
 * \param dev         The Device to get the device Prefix for.
 * \param dev_prefix  Location where the prefix will be stored.
 */
int rtems_drvmgr_get_dev_prefix(struct rtems_drvmgr_dev_info *dev, char *dev_prefix);

/*! Register an interrupt handler.
 *  \param index      Index is used to identify the IRQ number if hardware has multiple IRQ sources. 
 *                    Normally Index is set to 0 to indicated the first and only IRQ source.
 *  \param func       Interrupt Service Routine.
 *  \param arg        Optional ISR argument.
 */
int rtems_drvmgr_interrupt_register(
	struct rtems_drvmgr_dev_info *dev,
	int index,
	void (*func)(int irq, void *arg),
	void *arg);

/*! Enable (unmask) an interrupt source 
 * 
 *  \param dev        Device to enable interrupt for.
 *  \param index      Index is used to identify the IRQ number if hardware has multiple IRQ sources. 
 *                    Normally Index is set to 0 to indicated the first and only IRQ source.
 */
int rtems_drvmgr_interrupt_enable(struct rtems_drvmgr_dev_info *dev, int index);

/*! Disable (mask) an interrupt source 
 *
 *  \param dev        Device to disable interrupt for.
 *  \param index      Index is used to identify the IRQ number if hardware has multiple IRQ sources. 
 *                    Normally Index is set to 0 to indicated the first and only IRQ source.
 */
int rtems_drvmgr_interrupt_disable(struct rtems_drvmgr_dev_info *dev, int index);

/*! Clear (ACK) pending interrupt
 *
 *  \param dev        Device to clear interrupt for.
 *  \param index      Index is used to identify the IRQ number if hardware has multiple IRQ sources. 
 *                    Normally Index is set to 0 to indicated the first and only IRQ source.
 */
int rtems_drvmgr_interrupt_clear(struct rtems_drvmgr_dev_info *dev, int index);

/*! Translate address 
 * 1. From CPU local bus to a remote bus for example a PCI target (from_remote_to_cpu = 0)
 * 2. From remote bus to CPU local bus (from_remote_to_cpu = 1)
 *
 * src_address the address to translate, dst_address is where the translated address is stored.
 *
 * \param dev                  Device to translate addresses for.
 * \param from_remote_to_cpu   Selection tranlation direction.
 * \param src_address          Address to translate
 * \param dst_address          Location where translated address is stored.
 *
 * Returns -1 if unable to translate. If no map is present src_address is translated 1:1 (just copied).
 */
int rtems_drvmgr_mmap_translate(
	struct rtems_drvmgr_dev_info *dev,
	int from_remote_to_cpu,
	void *src_address,
	void **dst_address);

/*! Get key value from the bus resources matching [device[drv_id, minor_bus], key name, key type] 
 * if no matching key NULL is returned.
 *
 * This is typically used by device drivers to find a particular device resource.
 *
 * \param dev         The device to search resource for.
 * \param key_name    The key name to search for
 * \param key_type    The key type expected.
 * \return            Returns NULL if no value found matching Key Name and Key Type was found for device.
 */
extern union rtems_drvmgr_key_value *rtems_drvmgr_dev_key_get(
	struct rtems_drvmgr_dev_info *dev,
	char *key_name,
	int key_type);

Driver skeleton

Below is a selection from the GRSPW driver on how to create a driver using the Driver Manager structure. Below is also a sniplet from AMBA PnP the bus driver that the GRSPW driver is intended for.

/*  General part of a AMBA Plug & Play bus driver. */

/* GRLIB AMBA Plug&Play Driver ID generation */
#define DRIVER_AMBAPP_ID(vendor, device) \
	DRIVER_ID(DRVMGR_BUS_TYPE_AMBAPP, ((((vendor) & 0xff) << 16) | ((device) & 0xfff)))

/*** Gaisler Hardware Device Driver IDs ***/
#define DRIVER_AMBAPP_GAISLER_GRETH_ID		DRIVER_AMBAPP_ID(VENDOR_GAISLER, GAISLER_ETHMAC)
#define DRIVER_AMBAPP_GAISLER_GRSPW_ID		DRIVER_AMBAPP_ID(VENDOR_GAISLER, GAISLER_SPW)

/*** ESA Hardware Device Driver IDs ***/
#define DRIVER_AMBAPP_ESA_MCTRL_ID		DRIVER_AMBAPP_ID(VENDOR_ESA, ESA_MCTRL)

struct amba_dev_id {
	unsigned short		vendor;
	unsigned short		device;
	/* Version ? */
};

struct amba_drv_info {
	struct rtems_drvmgr_drv_info	general;	/* General bus info */
	/* AMBA specific bus information */
	struct amba_dev_id		*ids;		/* Supported hardware */
};

struct amba_dev_info {
	struct amba_dev_id	id;
	struct ambapp_dev_info	info;
};
#define GRSPW_DRIVER_TABLE_ENTRY \
  { grspw_initialize, \
    grspw_open, \
    grspw_close, \
    grspw_read, \
    grspw_write, \
    grspw_control }

static rtems_driver_address_table grspw_driver = GRSPW_DRIVER_TABLE_ENTRY;
static int grspw_driver_io_registered = 0;
static rtems_device_major_number grspw_driver_io_major = 0;

/******************* Driver manager interface ***********************/

/* Driver prototypes */
int grspw_register_io(rtems_device_major_number *m);
int grspw_device_init(GRSPW_DEV *pDev);

rtems_status_code grspw_init1(struct rtems_drvmgr_dev_info *dev);
rtems_status_code grspw_init2(struct rtems_drvmgr_dev_info *dev);

/* Driver operations */
struct rtems_drvmgr_drv_ops grspw_ops = 
{
	grspw_init1,
	grspw_init2,
	NULL,		/* Driver does not support device unregister (delete) */
	NULL		/* No info function */
};

/* Supported Hardware, Two different cores, however GRSPW2 is backwards compatible 
 * except for some small details.
 */
struct amba_dev_id grspw_ids[] = 
{
	{VENDOR_GAISLER, GAISLER_SPW},
	{VENDOR_GAISLER, GAISLER_SPW2},
	{0, 0}		/* Mark end of table */
};

struct amba_drv_info grspw_drv_info =

{
	/* General Driver Part that all drivers must define */
	{
		NULL,				/* Next driver */
		NULL,				/* Device list */
		DRIVER_AMBAPP_GAISLER_GRSPW_ID,	/* Driver ID */
		"GRSPW_DRV",			/* Driver Name */
		DRVMGR_BUS_TYPE_AMBAPP,		/* Bus Type */
		&grspw_ops,			/* Driver oprtations supported */
		0,				/* No devices yet */
	},
	/* AMBA PnP Specific Driver Part that all AMBA PnP drivers must define */
	&grspw_ids[0]
};

void grspw_register_drv (void)
{
	SPACEWIRE_DBG("Registering GRSPW driver\n");
	rtems_drvmgr_drv_register(&grspw_drv_info.general);
}

rtems_status_code grspw_init1(struct rtems_drvmgr_dev_info *dev)
{
	GRSPW_DEV *priv;

	SPACEWIRE_DBG("GRSPW[%d] on bus %s\n", dev->minor_drv, dev->parent->dev->name);
	priv = dev->priv = malloc(sizeof(GRSPW_DEV));
	if ( !priv )
		return RTEMS_NO_MEMORY;
	memset(priv, 0, sizeof(*priv));
	priv->dev = dev;

	/* This core will not find other cores, so we wait for init2() */

	return RTEMS_SUCCESSFUL;
}

rtems_status_code grspw_init2(struct rtems_drvmgr_dev_info *dev)
{
	GRSPW_DEV *priv;
	char prefix[16];
	rtems_status_code status;

	priv = dev->priv;

	/* Do initialization */

	if ( grspw_driver_io_registered == 0) {
		/* Register the I/O driver only once for all cores */
		if ( grspw_register_io(&grspw_driver_io_major) ) {
			/* Failed to register I/O driver */
			free(dev->priv);
			dev->priv = NULL;
			return RTEMS_UNSATISFIED;
		}

		grspw_driver_io_registered = 1;
	}

	/* I/O system registered and initialized 
	 * Now we take care of device initialization.
	 */

	/* Get Bus frequency in Hz. The SpaceWire core is clocked by the bus */
	if ( rtems_drvmgr_get_freq(dev, &priv->core_freq_khz) ) {
		return RTEMS_UNSATISFIED;
	}
	/* Convert from Hz -> kHz */
	priv->core_freq_khz = priv->core_freq_khz / 1000;

	/* Initialize Device */
	if ( grspw_device_init(priv) ) {
		return RTEMS_UNSATISFIED;
	}

	/* Get Filesystem name prefix, this is to separate GRSPW cores by bus,
	 * This might not always be wanted.
	 */
	prefix[0] = '\0';
	if ( rtems_drvmgr_get_dev_prefix(dev, prefix) ) {
		/* Failed to get prefix, make sure of a unique FS name
		 * by using the driver minor.
		 */
		sprintf(priv->devName, "/dev/grspw%d", dev->minor_drv);
	} else {
		/* Got special prefix, this means we have a bus prefix
		 * And we should use our "bus minor"
		 */
		sprintf(priv->devName, "/dev/%sgrspw%d", prefix, dev->minor_bus);
	}

	/* Register Device */
	status = rtems_io_register_name(priv->devName, grspw_driver_io_major, dev->minor_drv);
	if (status != RTEMS_SUCCESSFUL) {
		return status;
	}

	return RTEMS_SUCCESSFUL;
}

/******************* Driver Implementation ***********************/

int grspw_register_io(rtems_device_major_number *m)
{
	rtems_status_code r;

	if ((r = rtems_io_register_driver(0, &grspw_driver, m)) == RTEMS_SUCCESSFUL) {
		SPACEWIRE_DBG("GRSPW driver successfully registered, major: %d\n", *m);
	} else {
		switch(r) {
		case RTEMS_TOO_MANY:
			printk("GRSPW rtems_io_register_driver failed: RTEMS_TOO_MANY\n");
			return -1;
		case RTEMS_INVALID_NUMBER:  
			printk("GRSPW rtems_io_register_driver failed: RTEMS_INVALID_NUMBER\n");
			return -1;
		case RTEMS_RESOURCE_IN_USE:
			printk("GRSPW rtems_io_register_driver failed: RTEMS_RESOURCE_IN_USE\n");
			return -1;
		default:
			printk("GRSPW rtems_io_register_driver failed\n");
			return -1;
		}
	}
	return 0;
}

int grspw_device_init(GRSPW_DEV *pDev)
{
	struct amba_dev_info *ambadev;
	struct ambapp_dev_info *pnpinfo;
	union rtems_drvmgr_key_value *value;

	/* Get device information from AMBA PnP information */
	ambadev = (struct amba_dev_info *)pDev->dev->businfo;
	if ( ambadev == NULL ) {
		return -1;
	}
	pnpinfo = &ambadev->info;
	pDev->irq = pnpinfo->irq;
	pDev->regs = (LEON3_SPACEWIRE_Regs_Map *)pnpinfo->apb_slv->start;
	pDev->minor = pDev->dev->minor_drv;

	/* Get SpaceWire core version */
	switch( pnpinfo->device ) {
		case GAISLER_SPW:
			pDev->core_ver = 1;
			break;
		case GAISLER_SPW2:
			pDev->core_ver = 2;
			break;
		default:
			return -1;
	}

	/* initialize the code with some reasonable values,
	 * actual initialization is done later using ioctl(fd)
	 * on the opened device */
	pDev->config.rxmaxlen = SPACEWIRE_RXPCK_SIZE;
	pDev->txdbufsize = SPACEWIRE_TXD_SIZE;
	pDev->txhbufsize = SPACEWIRE_TXH_SIZE;
	pDev->rxbufsize = SPACEWIRE_RXPCK_SIZE;
	pDev->txbufcnt = SPACEWIRE_TXBUFS_NR;
	pDev->rxbufcnt = SPACEWIRE_RXBUFS_NR;

	pDev->ptr_rxbuf0 = 0;
	pDev->ptr_txdbuf0 = 0;
	pDev->ptr_txhbuf0 = 0;
	pDev->rx_dma_area = 0;
	pDev->tx_data_dma_area = 0;
	pDev->tx_hdr_dma_area = 0;

	/* Get Configuration from Bus resources (Let user override defaults) */

	value = rtems_drvmgr_dev_key_get(pDev->dev, "txBdCnt", KEY_TYPE_INT);
	if ( value )
		pDev->txbufcnt = value->i;

	value = rtems_drvmgr_dev_key_get(pDev->dev, "rxBdCnt", KEY_TYPE_INT);
	if ( value )
		pDev->rxbufcnt = value->i;

	value = rtems_drvmgr_dev_key_get(pDev->dev, "txDataSize", KEY_TYPE_INT);
	if ( value )
		pDev->txdbufsize = value->i;

	value = rtems_drvmgr_dev_key_get(pDev->dev, "txHdrSize", KEY_TYPE_INT);
	if ( value )
		pDev->txhbufsize = value->i;

	value = rtems_drvmgr_dev_key_get(pDev->dev, "rxPktSize", KEY_TYPE_INT);
	if ( value )
		pDev->rxbufsize = value->i;
	
	value = rtems_drvmgr_dev_key_get(pDev->dev, "rxDmaArea", KEY_TYPE_INT);
	if ( value )
		pDev->rx_dma_area = value->i;

	value = rtems_drvmgr_dev_key_get(pDev->dev, "txDataDmaArea", KEY_TYPE_INT);
	if ( value )
		pDev->tx_data_dma_area = value->i;

	value = rtems_drvmgr_dev_key_get(pDev->dev, "txHdrDmaArea", KEY_TYPE_INT);
	if ( value )
		pDev->tx_hdr_dma_area = value->i;

	if (grspw_buffer_alloc(pDev)) 
		return RTEMS_NO_MEMORY;

	/* Create semaphores */
	rtems_semaphore_create(
		rtems_build_name('T', 'x', 'S', '0' + pDev->minor), 
		0, 
		RTEMS_FIFO | RTEMS_SIMPLE_BINARY_SEMAPHORE | RTEMS_NO_INHERIT_PRIORITY | \
		RTEMS_NO_PRIORITY_CEILING, 
		0, 
		&(pDev->txsp));

	rtems_semaphore_create(
		rtems_build_name('R', 'x', 'S', '0' + pDev->minor), 
		0, 
		RTEMS_FIFO | RTEMS_SIMPLE_BINARY_SEMAPHORE | RTEMS_NO_INHERIT_PRIORITY | \
		RTEMS_NO_PRIORITY_CEILING, 
		0, 
		&(pDev->rxsp));

	grspw_hw_init(pDev);

	/* Register interrupt routine */
	if ( rtems_drvmgr_interrupt_register(pDev->dev, 0, grspw_interrupt, pDev) ) {
		return -1;
	}

	return 0;
}

/* IOCTL */
static rtems_device_driver grspw_control(
	rtems_device_major_number major,
	rtems_device_minor_number minor,
	void		    * arg
	)
{
	int timeout;
	rtems_device_driver ret;
	rtems_libio_ioctl_args_t *ioarg = (rtems_libio_ioctl_args_t *) arg;
	GRSPW_DEV *pDev;
	struct rtems_drvmgr_dev_info *dev;

	SPACEWIRE_DBGC(DBGSPW_IOCALLS, "ctrl [%i,%i]\n", major, minor);

	/* Get device pointer by searching in driver list for matching driver minor, 
	 * it would be better if RTEMS delivered the device pointer when calling
	 * grspw_control...
	 */
	if ( rtems_drvmgr_get_dev(&grspw_drv_info.general, minor, &dev) ) {
		return RTEMS_INVALID_NAME;
	}
	pDev = (GRSPW_DEV *)dev->priv;

	if (!ioarg)
		return RTEMS_INVALID_NAME;

	ioarg->ioctl_return = 0;
	switch(ioarg->command) {
		case SPACEWIRE_IOCTRL_START:
			if ( pDev->running ){
				return RTEMS_INVALID_NAME;
			}

			/* Get timeout from userspace
			 *  timeout:
			 *   ¤  -1	   = Default timeout
			 *   ¤  less than -1 = forever
			 *   ¤  0	    = no wait, proceed if link is up
			 *   ¤  positive     = specifies number of system clock ticks that 
			 *		     startup will wait for link to enter ready mode.
			 */
			timeout = (int)ioarg->buffer;
			
			if ( (ret=grspw_hw_startup(pDev,timeout)) != RTEMS_SUCCESSFUL ) {
				return ret;
			}
			pDev->running = 1;
			/* Enable interrupt */
			rtems_drvmgr_interrupt_enable(dev, 0);
			break;

		case SPACEWIRE_IOCTRL_STOP:
			if ( !pDev->running ){
				return RTEMS_INVALID_NAME;
			}
			/* Disable interrupts */
			rtems_drvmgr_interrupt_disable(dev, 0);

			pDev->running = 0;

			/* Stop Receiver and transmitter */
			grspw_hw_stop(pDev,1,1);
			break;

		default:
			return RTEMS_NOT_IMPLEMENTED;
	}

	SPACEWIRE_DBGC(DBGSPW_IOCALLS, "SPW_IOCTRL Return\n");
	return RTEMS_SUCCESSFUL;
}

Multi-processor system notes

Ina Multi-processor system sharing the RTEMS instances often share the resources, in a non Plug & Play system it might be easy to avoid conflicts however in a Plug & Play system it may be tougher. Say that four UARTs are found, usually the first one is used a console and the other ones are initialized by the driver into some kind of reset state and registered in the file system to /dev/ttyS[1..3]. This may not work in a MP system, to solve this problem the driver manager provides means to stop a device from being given to the it's device driver. This, of course, must be configured by the user. A device may be ignored by the driver manager by defining an entry in the driver resource array targeting a specific device, but setting the pointer to resource array to NULL.

After a new device has been registered by the bus driver the device is united with a driver, then a NULL pointer resource array is searched for, if found the device is put on an inactive list rather than entering stage1 later on. The device's state mask is set to DEV_STATE_IGNORED.

TODO

== BSP ==

Changes to BSPs:

  • Add support to BSP to register root bus
  • Make driver resources on the root bus weak so that the user can override them.

RTEMS

  • Make I/O Manager handle registering I/O Drivers before I/O Manager is initialized.

Driver Manager

  • Initialize the Driver manager before I/O Manager initialization.
  • Write console/timer/IRQ driver
Last modified on Jun 11, 2009 at 6:16:26 PM Last modified on Jun 11, 2009, 6:16:26 PM