SPI Driver

This is in continuation of my last article on SPI Interface. Today I will take you through actual SPI driver code implementation.

Pseudocode

There will be 2 sections of the pseudocode. One which is written for userspace and one which is written for the OS space (which is also known as kernel space). Let’s start with the one which we are familiar with – the userspace program. Remember we are coding for a Linux system, and we should use ‘open’ and not ‘fopen’ like we do in a windows system.

// header files to be included…

#include <string.h>

#include <stdio.h>

#include <stdlib.h>

 int main()

{

int ret, fd;

char *test = (char *)”hello world”;

//open the device file with read write privileges

fd = open(“dev/MCSPI”, O_RDWR);

//write the string of length strlen(test1) to the device

ret = write(fd, test, strlen(test));;

//close the device file

close(fd);

return 0;

}

Wasn’t that easy? The device name is MCSPI instead of just SPI since that is what the hardware developers chose to name it while making BeagleBone Black. Not my fault!

Now, we move on to the kernel space code to satisfy the requests of userspace program. I have listed some functions below which the device driver must have if it is to work properly with the above program.

static int     MCSPI_open(struct inode *, struct file *)

static int     MCSPI_release(struct inode *, struct file *)

static ssize_t MCSPI_write(struct file *, const char *, size_t, loff_t *)

static int __init MCSPI_init(void)

static void __exit MCSPI_exit(void)

The function MCSPI_init() is used when the module is being loaded into the system. It allocates some memory, creates a file in the /dev/ directory (which will be used by the userspace program. The ‘/dev/MCSPI’ entry). It also does some other initialisation mumbo-jumbo which is not of our concern right now.

Similarly, the MCSPI_exit() function is called by the system when the driver is being removed from the OS. It undoes everything that was done by the MCSPI_init() function.

You must have noticed the __init and __exit. What is it? The __init and __exit macros mean that these functions will be used only when the device driver is being loaded (using insmod SPI.ko) or removed (using rmmod SPI). That way, the OS frees up the memory used by these functions when we are done loading/removing the driver.

Along the same lines, the MCSPI_open() and MCSPI_release() are called whenever the userspace program calls open() or close() (See above code if you want to refresh your memory). These functions setup/un-setup everything before we do anything else. I know, I know! Un-setup is not an actual word! But you get what I am trying to say, right?

And finally, MCSPI_write() is called whenever the userspace program calls the write() function. In our case, the written string is sent to the SPI hardware byte-by-byte which transmits it.

Now, let’s take a quick look at how all the magic happens. Exactly what goes on in these functions.

static int __init MCSPI_init(void){

– register a major number to be used by the OS

– register a class for this device

– make the device (which shows up in the /dev/ directory)

– return 0;

}

Of course, this is not a true code, we actually have to do all the stuff written in there in a proper manner. But this article is just to let you understand the basics. If you want to understand more about device drivers, you can check out the actual code in my GitHub repository using the link given in the next section.

static void __exit MCSPI_exit(void){

– destroy the device

– unregister the device class

– destroy the class

– unregister the major number

}

Look how we do everything in the exact opposite order in the MCSPI_exit as compared to the MCSPI_init. We created the device in the last phase of init, so we destroy it first. And so on…

static int MCSPI_open(struct inode *inodep, struct file *filep){

– do some prerequisite checking and return an error is something is amiss.

– request memory region of the SPI hardware and re-map it to the virtual

address space of the kernel

– Configure the SPI hardware using the re-mapped address

– return nonseekable_open()

}

The OS uses virtual memory for its operation too! Therefore, we have to re-map the actual physical address to the virtual address space of the kernel. Once we have that, we can use it like we use the addresses of the registers to configure and enable the SPI hardware.

Did you notice how I returned nonseekable_open() instead of some number? I know that this article is getting too long and it is getting harder to maintain your focus. But bear with me for just 5 more minutes. We are almost done

Yeah, so the nonseekable_open tells the OS that this device is not ‘seek’able. Seeking means going back and forth in the file. So, unless you are a time-traveller, seeking forward in this file means that we are trying to read the bytes which are yet to be received by the device! And unless we solve all the mysteries of physics, that is not possible. Thus, when I return the nonseekable_open() the OS knows that this file is not supposed to be ‘seek’ed and thwarts any attempt by the user to do so.

static int MCSPI_release(struct inode *inodep, struct file *filep){

– undo everything done by the MCSPI_open() function

– cleanup any remaining pointer etc. if necessary

}

Just like MCSPI_exit(), the MCSPI_release() function reverts back everything done by the MCSPI_open() function and clears any dangling pointers or allocated memory before exiting.

static ssize_t MCSPI_write(struct file *filep, const char __user *buffer, size_t len, loff_t *offset){

//get the data from user to kernel space

//send the data using SPI

//return 0;

}

Finally, we have the write function which is called when the userspace program tries to write some data to the file. We need special functions for getting the data from userspace to kernel space because they have different virtual memories and you don’t want to mess up any kernel process by trying to use an incorrect pointer.

Remember that we are doing all of this in the kernel space with superuser privileges. There is no error checking here. Try to use a ‘sacrificial’ system like BeagleBone etc. If you messed up on your personal computer then that might lead to a system crash or worse yet, data loss. All I am saying is – be careful when you are wielding all those “superuser privileges”.

Link to the actual code: SPI_POLLING

The result. Finally! Yippee!

First let us see what commands did I type to get this working.

To type the commands, I need some kind of serial communication mechanism set up! That is done using ‘PuTTY’ which logs into the remote host (BeagleBone in our case). You can find quite a lot of tutorials online on how to connect to BeagleBone. You will need to install PuTTY if you are using windows. If you are using Mac OS or Linux you can just ‘ssh’ into BeagleBone. Now on to the commands.

make clean and make built the module and all related files. The commands being run are stored in Makefile.

insmod SPI.ko inserts the module into the OS. At this point the MCSPI_init() function is called by the OS.

./testSPI runs the executable file which, in turn, opens the /dev/MCSPI file. OS calls the MCSPI_open() function at this point

The testSPI also calls close() on the file descriptor. This prompts the OS to call the MCSPI_release() function.

That’s it! Everything else is done by the code! (Note: if you cone my repository and get some weird output from SPI then it is because of the changed value of char *test in the testSPI.c file. That file is for testing purpose and I would be changing it a lot for my own tests)

But what is not shown in the image is I also did rmmod SPI. This asks the OS to remove the device driver and calls the MCSPI_exit() function.

I am sure that you are musing – “That’s okay! Where is the data which was sent?”. I thought you would say that. I planned ahead. Below is the screen grab of the data being sent by BeagleBone. I used WaveFormsTM with my Analog Discovery 2TM digital oscilloscope. Both of these are made by the Diligent Inc.

Here is what does these number mean in ASCII.

0x68

0x65

0x6C

0x6C

0x6F

0x20

0x77

0x6F

0x72

0x6C

0x64

h’

e’

l’

l’

o’

<space>

w’

o’

r’

l’

d’

We have come a long way from the point when we started this article. We have learnt a lot. All of us. Me too!

I am getting teary eyed since we are parting ways now! But I do hope that this article helped you understand the basics of SPI and the internal workings of device drivers. Remember that this was an introductory session. If you want to see what an actual device driver looks like, there are a lot of examples out there which are much better than this SPI driver. But I would like to shamelessly give you a link to my repository: SPI_DRIVER

Thanks for taking out the time to go through this article.  I hope that you guys like, or better yet, love it!

Reference :

            Linux Kernel Module Programming Guide

Author: Aniruddha Kanhere

Aniruddha is a graduate student at NC State University – studying computer engineering. He came in contact with embedded systems during his undergraduate education while he was in the RoboCon team. Till this point, he has done numerous projects and has presented 2 papers in international conferences which are to be published. Apart from this, his interests lie mainly in the domain of RTOS, device drivers and Optimization.

Thanks for reading till end. Please provide your feedback regarding this article. Also subscribe  Embedkari for other interesting topics. .

Embedkari has introduced its first low cost technical training .

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.