10

I know C very well, not C++ - and want to create a hardware driver in C++. I need some pointers.

In C, I would create a structure, with function pointers and populate the func-pointers accordingly, ether at run time, or by compile time initialization {like the device drivers re initialized in Linux Kernel drivers}. Linux device drivers often have a 'driver private' element that lets the driver keep an implementation specific pointer (holding other stuff)

What I don't know how to do - is create the same thing using C++ classes.

My example would be for a serial port.

The Hardware portion of the driver - in C would be something like this:

struct serail_driver;
struct serial_hw_level;

struct serial_hw_level {
    // points to implementation specific details
    inptr_t  hw_private;

    // basically pure virtual functions
    int (*wrBytes)( struct serial_driver *pDriver, uint8_t *ptr_bytes, int nBytes );
    int (*rdBytes)( struct serial_driver *pDriver, uint8_t *ptr_bytes, int nBytes );

    /* there would be other member funds, but this is is a short example */
};

/* the generic serial port */
struct serial_generic_driver {
     const char *name;

     struct serial_hw_driver *hw_Driver;
};

/* create two serial ports at the hardware level */
struct serial_hw_level arm9_serial_port = {
    .hw_private = (intptr_t)(&hw_driver_
    .wr_bytes   = arm9_uart_wr_bytes,
    .rd_bytes   = arm9_uart_rd_bytes
};

struct serial_hw_level usb_emulated_port = {
    .hw_private = (inptr_t)(&usb_private_stuff),
    .wr_bytes   = usb_serial_wr_bytes,
    .rd_bytes   = usb_serial_rd_bytes
};

/* create generic serial port stuff */
struct serial_port usb_serial = {
   .name = "USBSERIAL",
   .hw_driver = &usb_emulated_port
};

struct serial_port hw_serial = {
    .name = "HW SERIAL",
    .hw_driver = &arm9_serial_port
};

To be clear; I do need the two different levels of abstraction. I know well how to do this in straight C - in the Linux kernel -that is a cake walk.

What I don't know how to do is handle the extra hardware specific stuff using C++ that would be keep in the private data structures. It seems that should be in a derived class or something?

I'm thinking something like this:

/* the base class */
class serial_hw_level {
    intptr_t hw_private;  
    virtual int wr_bytes( uint8_t *pBytes, int bytes );
    virtual int rd_bytes( uint8_t *pBytes, int bytes );
 };

/* The generic usb serial port... */
class serial_port : public serial_hw_level {
    string name;    
};


/* another derived class */    
class usb_serial : public serial_hw_level {
   int usb_endpoint;
   int usb_controller;
   int emualted_handshake_pins;
};

So I sort of have a VEN-Diagram of classes, my question is how do I do this in C++. I can do this in C, it is simple. I completely understand that technique.

What i have is two types of class pointers that need to point to the same base class.

in plain C, I hide things in an implementation specific structure via the 'intptr_t private' - which is a pointer in hiding.

In C++, I think this is kept in the derived class.

The same idea could be extended - and I could create a 'ByteStream" class - that could handle a stream of bytes - either to a file, or to a serial port, or - perhaps to a buffer in memory.

Please note: I'm using a serial port as an example, but these same would apply to an I2C port. I might have a I2C port implemented in hardware, or perhaps implemented via bit-bang IO or SPI port

Thanks.

user3696153
  • 568
  • 5
  • 15
  • 1
    Which OS? Linux doesn't support this at all, and windows supports the [super-C functionality](http://msdn.microsoft.com/en-us/library/jj620896.aspx) of C++ and no more – Mgetz Jun 01 '14 at 14:23
  • Bare metal (no-operating-system) is one example. But the same thing could exists in user space under linux. Consider the 'pseudo-device' could be a memory buffer, a TCPIP socket, or a serial port. I want something like a stream, that provides its own read/write functions but that stream needs to keep track of private information. i.e.: In the case of a memory buffer, the buffer pointers. In the case of a TCPIP socket, a URL, with username and password, and in the case of a serial port - the device name and settings (baud rate, etc) – user3696153 Jun 01 '14 at 14:30
  • Does your code have typos? In your code, I see `serail_driver`, `serial_driver` and `serial_hw_driver`, none of which are defined in the code. Are they all the same? what is the purpose of that structure and what do they look like? – Spundun Jun 01 '14 at 17:22
  • @user3696153 I think you should stop talking about bare-metal OS, Linux kernel cake walk and a universal hardware driver and instead pick a good C++ tutorial book (e.g. http://www.cplusplus.com/doc/tutorial) and read it. Your question has nothing to do with hardware drivers. To me it looks like you need to grab basic OOP concepts like classes, abstractions, inheritance, templates. There is no such thing as OS-neutral "driver" same as there is no such thing as processor-neutral C++ compiler. If you are learning check [C++11](http://en.wikipedia.org/wiki/C%2B%2B11) language specification – xmojmr Jun 25 '14 at 12:40

1 Answers1

2
struct serial_driver;

class serial_hw_level {
public:
    virtual ~serial_hw_level() { }
    virtual int wrBytes(serial_driver *pDriver, uint8_t *ptr_bytes, int nBytes) = 0;
    virtual int rdBytes(serial_driver *pDriver, uint8_t *ptr_bytes, int nBytes) = 0;
};

class arm9_uart: public serial_hw_level {
public:
    arm9_uart(int sutff);
    virtual ~arm9_uart();
    virtual int wrBytes(serial_driver *pDriver, uint8_t *ptr_bytes, int nBytes);
    virtual int rdBytes(serial_driver *pDriver, uint8_t *ptr_bytes, int nBytes);

private:
    int arm9_stuff;
};

class usb_emulated: public serial_hw_level {
public:
    usb_emulated(int endpoint, int controller, int handshake_pins);
    virtual ~usb_emulated();
    virtual int wrBytes(serial_driver *pDriver, uint8_t *ptr_bytes, int nBytes);
    virtual int rdBytes(serial_driver *pDriver, uint8_t *ptr_bytes, int nBytes);

private:
    int usb_endpoint;
    int usb_controller;
    int emulated_handshake_pins;
};

class serial_port {
public:
    serial_port(const string& serial_name, serial_hw_level* driver);
    ~serial_port();
    string get_name() const;

private:
    string name;
    serial_hw_level* hw_driver;
};


// Implementation *.cpp files

arm9_uart::arm9_uart(int stuff)
    : arm9_stuff(stuff) {
}

arm9_uart::~arm9_uart() {
    // free resources
}

int arm9_uart::wrBytes(serial_driver *pDriver, uint8_t *ptr_bytes, int nBytes) {
    // do things
    return 0;
}

int arm9_uart::rdBytes(serial_driver *pDriver, uint8_t *ptr_bytes, int nBytes) {
    // do things
    return 0;
}


usb_emulated::usb_emulated(int endpoint, int controller, int handshake_pins)
    : usb_endpoint(endpoint),
      usb_controller(controller),
      emulated_handshake_pins(handshake_pins) {
}

usb_emulated::~usb_emulated() {
    // free resources
}

int usb_emulated::wrBytes(serial_driver *pDriver, uint8_t *ptr_bytes, int nBytes) {
    // do things
    return 0;
}

int usb_emulated::rdBytes(serial_driver *pDriver, uint8_t *ptr_bytes, int nBytes) {
    // do things
    return 0;
}

serial_port::serial_port(const string& serial_name, serial_hw_level* driver)
    : name(serial_name),
      hw_driver(driver) {
}

serial_port::~serial_port() {
    delete hw_driver;
    hw_driver = NULL;
}

string serial_port::get_name() const {
    return name;
}


serial_port hw_serail = {
    "HW SERIAL",
    new arm9_uart(arm9_stuff)
};

serial_port usb_serial = {
    "USBSERIAL",
    new usb_emulated(usb_endpoint, usb_controller, emulated_handshake_pins)
};

The serial_hw_level is the interface class. Function pointer assignments in C such as wrBytes are done in C++ via virual function pointers when object is created.

The serial_port class, however, is not necessary to be derived from the serial_hw_level. Composition is more suitable for it, since you want a name for it, and also an abstract serial_hw_level object.

The next is the objects in the global namespace. When you want it to be global, in C, you can simply assign function pointers to it. You even don't have a derived class or derived object. All you have are the base class, the base object, and the virtual function pointer assignment(in C++'s view) at the initial time. In C++, when you have derived classes and virtual function pointers, the assignment to base class is achieved by pointers or by references. Since you need global objects, you can new the derived objects, assign them to the base pointer at the initial time, and delete them just before the progress's exit automatically by the destructor of class serial_port.

  • Ok - I missed another part. In the 'serial_port' class, I want to have a "wrBytes()" function that resolves to the base hw class wr function. It seems the only solution is a shim function in the serial_port class that does this: serial_port::wrBytes( .. params ... ) { return hw_driver->wrBytes( ... params ... } I was hoping *NOT* to have to create the various shim functions. Is there an automatic way to do that in C++? – user3696153 Jun 01 '14 at 17:14
  • Let me ask this another way: I am looking for the function: serial_port::wrBytes() I do not see that in your answer. It should resolve to the serial_hw_port::wrBytes(). How does that happen? Do I have to write "shim" functions in the serial_port class? I was hoping this would happen automatically – user3696153 Jun 01 '14 at 17:25
  • You simply have two ways to do this in C++. One way is to use the methods in serial_hw_level to implement the same thing in serial_port, as you know. The other way is to get hw_driver like name property:serial_hw_level* get_hw_driver() const { return hw_driver; }. In this way, you don't need all the shim functions. – Alaya Bright Jun 02 '14 at 00:11