| The Möbius Operating System: Driver Book | ||||||
| HOME | DOWNLOAD | DOCUMENTATION | SCREENSHOTS | |||
DevicesBack: Initialization | Next: File System Drivers Devices are created through the add_device function of the driver object: void SampleAddDevice(driver_t* drv, const wchar_t* name, dev_config_t* cfg)
{
}
The driver of the bus to which your device is attached is responsible for calling add_device via DevInstallDevice in the kernel. The bus driver will usually be either the PCI or ISA drivers, although bus drivers include drivers for things like ATA controllers and serial port enumerators.
The dev_config_t structure looks like this: struct dev_config_t
{
device_t *bus; /* pointer to the bus device */
unsigned bus_type; /* type of bus */
unsigned num_resources; /* number of elements in resources array */
dev_resource_t *resources; /* resources array */
wchar_t *profile_key; /* name of the key where device should read its configuration */
uint16_t device_class; /* PCI-style device class */
void *businfo; /* pointer to bus driver's information structure */
union /* device location, encoded as... */
{
void *ptr; /* ...a pointer, or */
uint32_t number; /* ...an integer */
} location;
};
Your add_device routine should work out whether it supports the device asked for, then create a device object for it by calling the kernel's DevAddDevice function:
device_t *DevAddDevice(driver_t *drv,
const device_vtbl_t *vtbl,
uint32_t flags,
const wchar_t *name,
dev_config_t *cfg,
void *cookie);
The cookie you pass to DevAddDevice is stored in the cookie field of the device object. The same device object pointer will be passed to each virtual function called by the kernel, so don't worry about storing it. When you call DevAddDevice, your device is entered into the device manager's namespace (and into the /System/Devices and /System/Devices/Classes directories) and is available for use by applications. The pointer returned by DevAddDevice can now be passed to DevRegisterIrq if your device handles any interrupts: bool DevRegisterIrq(uint8_t irq, device_t *dev); Handling requestsThe device_vtbl_t structure mentioned above looks like this: struct device_vtbl_t
{
void (*delete_device)(device_t *dev);
status_t (*request)(device_t *dev, request_t *req);
bool (*isr)(device_t *dev, uint8_t irq);
void (*finishio)(device_t *dev, request_t *req);
bool (*cancelio)(device_t *dev, asyncio_t *io);
status_t (*claim_resources)(device_t *dev,
device_t *child,
dev_resource_t *resources,
unsigned num_resources,
bool is_claiming);
status_t (*remove_child)(device_t *dev,
device_t *child);
};
It is a structure containing only function pointers. When the kernel needs your device to do something, it calls the appropriate function in the virtual function table. Any of the pointers can be NULL except request. The functions are:
requestYour device will receive I/O requests through its request function. This is the core of the device's code and it is responsible for dispatching requests issued via IoRequest (and, by extension, IoReadSync, FsWrite and the others). Requests are represented by the request_t structure passed to the request function. This is identical to the pointer passed to IoRequest (the request function runs in the same context as the originator) and it is really only a pointer to the header of a larger structure. The various types of requests can be distinguished by the request_t::code field; each request code has different information ("parameters") stored after the request header.The two main codes you'll see are DEV_READ and DEV_WRITE. Not surprisingly, these are invoked when something needs to read from or write to your device; when invoked from user mode, these come via FsRead and FsWrite. Both DEV_READ and DEV_WRITE have similar parameters: the request_dev_t structure defines both the parameters and the header, and request_dev_t::params::buffered can be used for each. You are provided with three pieces of information: a buffer, the length of the buffer, and an offset: union params_dev_t
{
struct
{
uint32_t length; /* length of the request in bytes */
page_array_t *pages; /* page array representing the caller's buffer */
uint64_t offset; /* offset within the device, in bytes */
} buffered;
/* ...more fields here... */
};
You've got all the parameters you need now. If the operation is going to be quick then you can handle it there and then and copy the results into the buffer. If you don't read as many bytes as requested, update the length field before returning. If the operation fails you need to return an appropriate error code from the request function; for success you just need to return zero. If the operation is going to take some time (any interrupt-driven I/O falls into this category) then you need to use asynchronous I/O. Each device has an internal queue of asynchronous requests, which the device manager maintains. In the case of async I/O, all the request function does is validate the parameters and queue the request using DevQueueRequest:
asyncio_t* DevQueueRequest(device_t *dev,
request_t *req,
size_t size,
page_array_t *pages,
size_t user_buffer_length);
If the hardware device is idle the driver needs to start processing the request it just queued, usually in a common startio routine. Here the driver needs to get the hardware to start the I/O operation, with the expectation that it will trigger a hardware interrupt when it has finished. Once the hardware has started, but before it finishes, the request function should return SIOPENDING.
Now that the request has been queued, and your request function has returned, the kernel is free to keep on scheduling other threads; if it wants, the originator (which might be a user process, a file system driver or something else) can keep on working until your driver has finished. isrWhen your hardware triggers an interrupt your isr function will be called (assuming you called DevRegisterIrq in add_device). Remember that it will be called in an arbitrary context: don't modify any user memory and don't take too long. Here you need to check which request this interrupt applies to (if you're handling requests sequentially it will always be the first one, at device_t::io_first) and retrieve the results from the hardware. You'll probably need to access the request's user buffer at this point. You can call MemMapPageArray to access that buffer, which maps the a page array into a temporary region of kernel address space. Call MemUnmapPageArray when you're finished with the memory. If this request has finished (it might be finished after one IRQ, or the device might give you several IRQs before the operation is finished, as floppy drives do) then you need to call DevFinishIo. This signals the originator's finishio routine, if the originator was a device (e.g. the devfs file system that maintains the /System/Devices directory). This allows nested hierarchies of devices to be created: the originator is able to go off and continue its own request, which will notify the next higher originator, and so on (until user mode is reached). Back: Initialization | Next: File System Drivers Warning: mysql_free_result(): supplied argument is not a valid MySQL result resource in /home/groups/m/mo/mobius/htdocs/p.php on line 174 |
||||||