What are "stubs"?

You know that funny little option you turned on when compiling newlib? The one that went "--disable-newlib-supplied-syscalls"? Let me explain what that is and just how powerfull this is to an embedded system.

In a real operating system, such as Linux and Windows (cygwin), newlib expects that the operating system will provide the means to move data (bytes) between itself and the hardware. Newlib merely provides an abstraction of the operations, it doesn't implement the whole operation from start to finish. For example, we want to write a string of data to one of the uarts (serial ports) we could do something like:

void function (void)
{
FILE * fooFile;
   fooFile = fopen("/com1/9600,n,8,1", "w");
   fwrite("this is a line of text\r\n", fooFile);
   fclose(fooFile);
}

While the absolute procedure of conditioning a serial port is not done this way on either operating system, the example does serve to show the idea of what we want to do. What will really happen is that the newlib function calls of: fopen(), fwrite() and fclose() serve to provide a pathway for the data string to reach the UART. Newlib does not touch the hardware, as with all modern software systems, "that is someone else's problem". Newlib merely keeps track of the "channel" that fooFile represents. Newlib doesn't really know what the ultimate hardware is, that is someone else's problem (the operating system).


TopWhat are all those funny "_r.c" files about?

Without an operating system, we don't have some body of code automagically moving bytes between newlib and the hardware. Normally, this would be done by making System Calls (SysCall()). In our case of an embedded system where it is running our operating system that we wrote, it is now up to us to move that data to the hardware.

The movement of data places certain requirements on us:
1. We need to prepare the hardware for use.
2. We need to be handed the data to pass to the hardware.
3. We need to identify the hardware that is to get the data.

Working in conjunction with newlib, through the stubs, we can accomplish this. To open a resource (hardware), newlib calls the open_r() function. This is a function which we will have to write, normally open_r() is a SysCall to the operating system. Newlib hands us two important pieces of information from the fopen() call we made: filename + mode. In the above example: "/com1/9600,n,8,1" is the filename and "w" is in the flags. The filename is the textual name that we used in the fopen call, the mode of "w" has been digested (cooked) and is a integer containing bits to indicate the mode. These flags are the same bits as defined in the newlib include of sys/fcntl.h .


TopA practical open_r()

Get the RDCF2 sources from elsewhere within this site and unpack them. We will look at how a file would be opened on the SD drive. The process is essentially identical to that of opening a hardware serial port, to the point of entering the RDCF2 code itself.

Open_r() first scans the device table looking for a device name that matches that of the desired filename. Note the '/' chars surrounding the name "com1"? That delineates the name of the device. So, if we have a device named "com1" in the device table, the entry point of open() is called with parameters passed to it. This is the line that says return device_table_list [i].item->open( ptr, &file[len+2], flags, mode).

In the case of the MMC device (SD drive), open() is in the mmc_module.c file. Look there now. You will now see process of opening a file via the rdcf2 code. We keep track of file handles here in this code. It is necessary for us, here within mmc_module.c, to assign a unique identifier to this filestream. Newlib does not assign filehandles, we do (the operating system).

A filehandle is a means for the operating system to track a filestream to hardware destination point. Since we can have multiple files open on the SD drive, we use filehandles to keep track of them. The mmc_module.c code also uses File Control Blocks for it's own management of the files. Newlib never sees these FCBs. We never see the internal FILE buffers of newlib. The filehandle is the logical representation of the data stream to hardware connection.


TopA practical fwrite()

The process of an fputs() or fwrite() are the same: write_r() is called by newlib with a filehandle (stream identifier), data to be written (buffer) and a count (how many bytes in the buffer to write). Before we can call the appropriate device write function, we have to see what device would receive the data. Look at write_r.c now.

The write_r() function looks up the device using the filehandle value as the device ID + buffer ID. If you recall, we told newlib to use these values back in the open_r()? If a device of that number exists, the write function is then called for that device. Again we look at mmc_module.c to see how the write function proceeds. In the case of the SD drive, the filehandle points to the buffer, and the buffer names the file which will receive the data.

Hmmm, couldn't the buffer also tell us what serial port the data would go to?! Yes! It can.. The filebuffer (or more correctly: File Control Block) is what we decide it should represent / contain. It is important to note that newlib keeps it's own file buffers, but the contents of those buffers are of no importance to us. Our FCBs are what we concern ourselves with!


TopHow do I add a new device?

To add a new device, say one called "pong", or more correctly "/pong/". Take the mmc_module.c as an example: strip out all but the DEVICE_TABLE_ENTRY structure. Rename, if necessary, the calls within the call table to more descriptive names to match your device name. Then replace the name "mmc" with "pong". Next, edit devices.c and add the reference to the "pong" device table. Finally, edit sysdefs.h and create a new DEVICE_ID similar to the MMC_DEVICE.

From here, you will focus on the writing of the functions within your device module code (renamed mmc_module.c file). Your serial port device will be simpler than the MMC device. The MMC device has to keep track of multiple connections to multiple files, hence the rather complex FCB structure and filehandles. The serial port device merely needs to return a unique filehandle so that write_r() and read_r() will properly call your module code.


TopSummary

While this page does not attempt to intimately detail each and every step of the process of how the MMC device was implemented, it does attempt to outline the general process. The process is that newlib eventually calls our stub functions (open_r(), write_r(), etc.) to touch the hardware. We, in turn, take the process a step further in the stub function of resolving which hardware module will receive the newlib call. And finally, our module code takes the operation that newlib has requested and causes it to occur on the desired hardware.