[freenos] r269 committed - Implemented a Device and DeviceServer class for easy driver developmen...

  • From: codesite-noreply@xxxxxxxxxx
  • To: freenos@xxxxxxxxxxxxx
  • Date: Sun, 02 Aug 2009 12:35:31 +0000

Revision: 269
Author: nieklinnenbank
Date: Sun Aug  2 04:48:54 2009
Log: Implemented a Device and DeviceServer class for easy driver development.
Developers now only have to implement Device::read(), Device::write()
and optionally Device::initialize() and Device::interrupt() when programming
a driver. The complete FileSystem and IPC layer has been abstracted.

http://code.google.com/p/freenos/source/detail?r=269

Added:
 /trunk/srv/Device.h
 /trunk/srv/DeviceServer.h
Modified:
 /trunk/srv/SConscript

=======================================
--- /dev/null
+++ /trunk/srv/Device.h Sun Aug  2 04:48:54 2009
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2009 Niek Linnenbank
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __DEVICE_H
+#define __DEVICE_H
+
+#include <Types.h>
+#include <Error.h>
+
+/**
+ * Represents a device attached to the system.
+ */
+class Device
+{
+    public:
+
+        /**
+         * Constructor function.
+         */
+       Device()
+       {
+       }
+
+       /**
+        * Class destructor.
+        */
+       virtual ~Device()
+       {
+       }
+
+       /**
+        * @brief Perform device specific initialization.
+        * @return Error result code.
+        */
+       virtual Error initialize()
+       {
+           return ESUCCESS;
+       }
+
+       /**
+        * Read bytes from the underlying device.
+        * @param buffer Buffer to store bytes to read.
+        * @param size Number of bytes to read.
+        * @param offset Offset in the device.
+        * @return Number of bytes on success and an error code on failure.
+        */
+       virtual Error read(s8 *buffer, Size size, Size offset)
+       {
+           return ENOTSUP;
+       }
+
+       /**
+        * Write bytes to the underlying device.
+        * @param buffer Buffer containing bytes to write.
+        * @param size Number of bytes to write.
+        * @return Number of bytes on success and an error code on failure.
+        */
+       virtual Error write(s8 *buffer, Size size, Size offset)
+       {
+           return ENOTSUP;
+       }
+
+       /**
+        * Called when an interrupt has been triggered for this device.
+        * @param vector Vector number of the interrupt.
+        * @return Error result code.
+        */
+       virtual Error interrupt(Size vector)
+       {
+           return ESUCCESS;
+       }
+};
+
+#endif /* __DEVICE_H */
=======================================
--- /dev/null
+++ /trunk/srv/DeviceServer.h   Sun Aug  2 04:48:54 2009
@@ -0,0 +1,479 @@
+/*
+ * Copyright (C) 2009 Niek Linnenbank
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __DEVICE_SERVER_H
+#define __DEVICE_SERVER_H
+
+#include <API/VMCopy.h>
+#include <API/ProcessCtl.h>
+#include <FileSystemMessage.h>
+#include <FileSystemPath.h>
+#include <FileDescriptor.h>
+#include <FileType.h>
+#include <FileMode.h>
+#include <Array.h>
+#include <Shared.h>
+#include "IPCServer.h"
+#include "Device.h"
+#include <unistd.h>
+#include <sys/stat.h>
+
+/** Maximum number of devices handled simultaneously by a DeviceServer. */
+#define DEVICE_MAX 32
+
+/**
+ * @brief Device driver server.
+ *
+ * Implements IPC handlers to communicate with the FileSystem using
+ * FileSystemMessages and invokes Device functions appropriately.
+ *
+ * @see Device
+ * @see FileSystem
+ * @see FileSystemMessage
+ */
+class DeviceServer : public IPCServer<DeviceServer, FileSystemMessage>
+{
+    public:
+
+       /**
+        * @brief Constructor function.
+        *
+        * Responsible for registering IPC handlers.
+        *
+        * @param prefix Used to to form the filename of the device files.
+        * @param type FileType of the device files to create.
+        * @param mode Access permissions on the device files.
+        */
+       DeviceServer(const char *prefix, FileType type, FileMode mode = OwnerRW)
+ : IPCServer<DeviceServer, FileSystemMessage>(this), devices(DEVICE_MAX)
+       {
+           /* Initialize local member variables. */
+           this->prefix = prefix;
+           this->type   = type;
+           this->mode   = mode;
+           this->files  = new Array<Shared<FileDescriptor> >(MAX_PROCS);
+
+           /* Register IPC Handlers. */
+           addIPCHandler(ReadFile,  &DeviceServer::ioHandler, false);
+           addIPCHandler(WriteFile, &DeviceServer::ioHandler, false);
+           addIPCHandler(SeekFile,  &DeviceServer::ioHandler, false);
+           addIPCHandler(CloseFile, &DeviceServer::ioHandler, false);
+       }
+
+       /**
+        * @brief Add a Device.
+        *
+        * Adds an Device to the internal Array of Devices, to
+        * be able to process requests for the Device later on.
+        *
+        * @param dev New device to serve requests for.
+        *
+        * @see Device
+        * @see devices
+        */
+       void add(Device *dev)
+       {
+           /* Add to the list of Devices. */
+           devices.insert(dev);
+       }
+
+       /**
+        * @brief Run the DeviceServer in the background.
+        *
+        * First fork() is used to spawn a child process. The child is
+        * responsible for invoking init() on each Device to run device
+        * specific initialization procedures. Additionally, the child
+        * will ask the kernel to receive InterruptMessages for the registered
+        * interrupt vectors using ProcessCtl. The parent will create a
+        * special device file for each Device in /dev using the prefix
+        * value as a base.
+        *
+        * @return Error code describing how we terminated.
+        *
+        * @see fork
+        * @see mknod
+        * @see mknod
+        * @see prefix
+        * @see ProcessCtl
+        */
+       Error run()
+       {
+           char path[PATHLEN];
+           pid_t pid;
+           dev_t id;
+
+           /* If we don't have any Devices, bail out. */
+           for (Size i = 0; i <= devices.size(); i++)
+           {
+               if (devices[i])
+               {
+                   break;
+               }
+               if (i == devices.size())
+               {
+                   return EXIT_FAILURE;
+               }
+           }
+
+           /*
+            * Copy ourselves using a fork().
+            */
+           if (!(pid = fork()))
+           {
+               /* Register interrupt handlers. */
+               for (Size i = 0; i < interrupts.size(); i++)
+               {
+                   if (interrupts[i])
+                   {
+                       /* Register to kernel. */
+                       ProcessCtl(SELF, WatchIRQ, i);
+
+                       /* Register interrupt handler. */
+                       addIRQHandler(i, &DeviceServer::interruptHandler);
+                   }
+               }
+               /* Initialize all our Devices. */
+               for (Size i = 0; i < devices.size(); i++)
+               {
+                   if (devices[i])
+                   {
+                       devices[i]->initialize();
+                   }
+               }
+               /* Start processing requests. */
+               return IPCServer<DeviceServer, FileSystemMessage>::run();
+           }
+           /*
+            * Loop all registered Devices.
+            */
+           for (Size i = 0; i < devices.size(); i++)
+           {
+               /* Skip empty. */
+               if (!devices[i])
+               {
+                   continue;
+               }
+               /* Attempt to create the device file. */
+               for (Size i = 0; i < 1000; i++)
+               {
+                   /* For a path using the supplied prefix. */
+                   snprintf(path, sizeof(path), "/dev/%s%u",
+                            prefix, i);
+
+                   /*
+                    * Use our ProcessID to redirect FileSystemMessages to us.
+                    */
+                   id.major = pid;
+                   id.minor = i;
+
+                   /* Create the special device file. */
+                   if (mknod(path, (type << FILEMODE_BITS) | mode, id) == 0)
+                   {
+                       break;
+                   }
+               }
+           }
+           /* All done! */
+           return EXIT_SUCCESS;
+       }
+
+       /**
+        * @brief Register an interrupt vector for the given device.
+        *
+        * Appends the given Device on the internal list used
+        * for interrupt processing in interruptHandler().
+        *
+        * @param dev Pointer to the Device to wait interrupts for.
+        * @param vector Vector number of the interrupt.
+        *
+        * @see Device
+        * @see interrupts
+        * @see interruptHandler
+        */
+       void interrupt(Device *dev, Size vector)
+       {
+           if (!interrupts[vector])
+           {
+               interrupts.insert(vector, new List<Device>);
+           }
+           interrupts[vector]->insertTail(dev);
+       }
+
+    private:
+
+       /**
+        * @brief Input/Output request handler.
+        */
+       void ioHandler(FileSystemMessage *msg)
+       {
+           FileDescriptor *fd = getFileDescriptor(files, msg->from, msg->fd);
+           Device *dev;
+           bool result = false;
+
+            /* Do they have this FileDescriptor? */
+            if (!fd)
+           {
+               msg->result = EBADF;
+               msg->ipc(msg->from, Send, sizeof(*msg));
+               return;
+           }
+           /* Read out values from the FileDescriptor. */
+           else
+           {
+               dev = devices[fd->identifier];
+               msg->deviceID.minor = fd->identifier;
+
+               if (msg->action != SeekFile)
+                   msg->offset = fd->position;
+           }
+
+           /* Do we serve this Device? */
+           if (dev)
+           {
+               switch (msg->action)
+               {
+                   case ReadFile:
+                       result = performRead(msg);
+                       break;
+
+                   case WriteFile:
+                       result = performWrite(msg);
+                       break;
+
+                   case SeekFile:
+                       fd->position = msg->offset;
+                       msg->result  = ESUCCESS;
+                       msg->ipc(msg->from, Send, sizeof(*msg));
+                       return;
+
+                   case CloseFile:
+                       memset(fd, 0, sizeof(*fd));
+                       msg->result = ESUCCESS;
+                       msg->ipc(msg->from, Send, sizeof(*msg));
+                       return;
+
+                   default:
+                       ;
+               }
+               /*
+                * Did we complete the request? If not, enqueue it.
+                */
+               if (!result)
+               {
+                   requests.insertTail(new FileSystemMessage(*msg));
+               }
+           }
+           else
+           {
+               msg->result = ENODEV;
+               msg->ipc(msg->from, Send, sizeof(*msg));
+           }
+       }
+
+       /**
+        * @brief Interrupt request handler.
+        *
+        * Invokes the interrupt callback function of
+        * each Device registered for the interrupt vector.
+        *
+        * @param msg Incoming message from the kernel.
+        * @see Device
+        * @see Device::interrupt
+        */
+       void interruptHandler(InterruptMessage *msg)
+       {
+           List<Device> *lst = interrupts[msg->vector];
+
+           /* Do we have any Devices with this interrupt vector? */
+           if (lst)
+           {
+               /*
+                * Loop all Devices of interest. Invoke callback.
+                */
+               for (ListIterator<Device> i(lst); i.hasNext(); i++)
+               {
+                   i.current()->interrupt(msg->vector);
+               }
+           }
+           /*
+            * Retry any requests in the queue.
+            * Remove them once processed.
+            */
+           for (ListIterator<FileSystemMessage> i(&requests); i.hasNext(); i++)
+           {
+               /* Only handle read/write operations. */
+               switch (i.current()->action)
+               {
+                   case ReadFile:
+
+                       if (performRead(i.current()))
+                       {
+                           requests.remove(i.current());
+                       }
+                       break;
+
+                   case WriteFile:
+
+                       if (performWrite(i.current()))
+                       {
+                           requests.remove(i.current());
+                       }
+
+                   default:
+                       ;
+               }
+           }
+       }
+
+       /**
+        * @brief Attempt to perform a read operation.
+        *
+        * @param msg Request message.
+        * @return True if the request has completed. False otherwise.
+        */
+       bool performRead(FileSystemMessage *msg)
+       {
+           Error result;
+           Device *dev = devices[msg->deviceID.minor];
+
+           /* Allocate a temporary buffer. */
+           s8 *buffer = new s8[msg->size];
+
+           /*
+            * Perform the read operation using the underlying
+            * read() implementation of the Device.
+            */
+           result = dev->read(buffer, msg->size, msg->offset);
+
+           /* Did it complete successfully? */
+           if (result > 0)
+           {
+               msg->result = ESUCCESS;
+               msg->size   = result;
+
+               /* Write the result into the process' buffer. */
+               if (VMCopy(msg->from, Write, (Address) buffer,
+                                            (Address) msg->buffer, msg->size) 
< 0)
+               {
+                   msg->result = EFAULT;
+               }
+           }
+           else
+           {
+               /* Take care that we may need to try again. */
+               msg->result = result;
+           }
+           /* Send a reply if processed. */
+           if (msg->result != EAGAIN)
+           {
+               msg->ipc(msg->from, Send, sizeof(*msg));
+           }
+           /* Release memory. And return. */
+           delete buffer;
+           return msg->result != EAGAIN;
+       }
+
+       /**
+        * @brief Attempt to perform a write operation.
+        *
+        * @param msg Request message.
+        * @return True if the request has completed. False otherwise.
+        */
+       bool performWrite(FileSystemMessage *msg)
+       {
+           Error result;
+           Device *dev = devices[msg->deviceID.minor];
+
+           /* Allocate a temporary buffer. */
+           s8 *buffer = new s8[msg->size];
+
+           /* Obtain input bytes from the process' buffer. */
+           if (VMCopy(msg->from, Read, (Address) buffer,
+                                       (Address) msg->buffer, msg->size) < 0)
+           {
+               msg->result = EFAULT;
+           }
+           else
+           {
+               /*
+                * Perform the write operation using the underlying
+                * write() implementation of the Device.
+                */
+               result = dev->write(buffer, msg->size, msg->offset);
+
+               /* Did it complete successfully? */
+               if (result > 0)
+               {
+                   msg->result = ESUCCESS;
+                   msg->size   = result;
+               }
+               /* Take care that we may need to try again. */
+               else
+               {
+                   msg->result = result;
+               }
+           }
+           /* Send a reply if processed. */
+           if (msg->result != EAGAIN)
+           {
+               msg->ipc(msg->from, Send, sizeof(*msg));
+           }
+           /* Release memory. And return. */
+           delete buffer;
+           return msg->result != EAGAIN;
+       }
+
+       /** Contains all Devices served by this DeviceServer. */
+       Array<Device> devices;
+
+       /**
+        * @brief Registers Devices using interrupts.
+        *
+        * An Array with Lists of Devices using the
+        * interrupt vector as index.
+        *
+        * @see Array
+        * @see List
+        * @see Device
+        */
+       Array<List<Device> > interrupts;
+
+       /**
+        * @brief A List of pending I/O operations.
+        */
+       List<FileSystemMessage> requests;
+
+       /** Per-process File descriptors. */
+        Array<Shared<FileDescriptor> > *files;
+
+       /**
+        * @brief Prefix string used to create device files in /tmp.
+        *
+        * If prefix contains e.g. "foobar", then add() will
+        * attempt to create "/dev/foobar0". If that fails it tries
+        * "/dev/foobar1", "/dev/foobar2", and so on.
+        */
+       const char *prefix;
+
+       /** The type of device file to create. */
+       FileType type;
+
+       /** Access permissions on the device files. */
+       FileMode mode;
+};
+
+#endif /* __DEVICE_SERVER_H */
=======================================
--- /trunk/srv/SConscript       Mon Jun  1 15:41:52 2009
+++ /trunk/srv/SConscript       Sun Aug  2 04:48:54 2009
@@ -15,5 +15,5 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #

-SConscript(dirs = [ 'log', 'idle', 'memory', 'pci',
+SConscript(dirs = [ 'log', 'idle', 'memory', 'pci', 'video', 'input',
                    'process', 'serial', 'terminal', 'filesystem' ])

Other related posts:

  • » [freenos] r269 committed - Implemented a Device and DeviceServer class for easy driver developmen... - codesite-noreply