How do I write a network device driver using SPI? - linux

I have implemented a device driver for the NRF24L01+ transceiver in userspace using rust. The userspace driver makes use of the kernel spi interface driver. Writing the driver as kernel module seems incredibly hard, as the documentation for linux/netdevice.h as found in linux device drivers seems outdated (or I'm just not smart enough to understand the intricate details).
A new project from the TU Munich proposes the use of vfio. From my understanding this type of driver implementation uses the iommu to manage isolation to protected memory areas for the devices. "Project Ixy" uses the network device as block device, hence it can be mapped. SPI is different as insofar it is a streaming protocol.
My question is, if it is possible to integrate the user space spi network device driver into the linux network stack, e.g. having all protocols etc handled by the network stack. Is it possible to use a similar approach as Project Ixy, like having a small component in kernel space, which is isolated for security, that builds a "bridge" to userland?

I think it is possible in two ways:
using TAP interface
writing your own "bridging" interface like TAP between user space and kernel space
If ethernet-like interface is enough for you - then use TAP. I mean TAP provides functionality where physical layer is moved to user space. In your case it could work like this: data received by SPI can be pushed to TAP interface to linux network stack. The data received from TAP interface (from Linux network stack) you can push via SPI. Is that what you wanted?
If ethernet-like interace (as TAP is) is not enough for you - you can write your own interface in kernel space basing on TAP sources.

Related

Does a HDD/SSD use 1 or 2 device controllers to connect to the OS?

I am studying Operating Systems, and came across divice controllers.
I gathered that a device controller is hardware whereas a device driver is software.
I also know that a HDD and a SSD both have a small PCB buit into them and I assume those PCB's are the device controllers.
Now what I want to know is if there is another device controller on the PC/motherboard side of the bus/cable connecting the HDD/SSD to the OS?
Is the configuration: OS >> Device Driver >> Bus >> Device Controller >> HDD/SSD
Or is it: OS >> Device Driver >> Device Controler >> Bus >> Device Controller >> HDD/SSD
Or is it some other configuration?
Sites I visited for answers:
Tutorialspoint
JavaPoint
Idc online
Quora
Most hard-disks on desktop are SATA or NVME. eMMC is popular for smartphones but some might use something else. These are hardware interface standards that describe the way to interact electrically with those disks. It tells you what voltage at what frequency and for what amount of time you need to apply (a signal) to a certain pin (a bus line) to make the device behave or react in a certain way.
Most computers are separated in a few external chips. On desktop, it is mostly SATA, NVME, DRAM, USB, Audio Output, network card and graphics card. Even though there is few chips, the CPU would be very expensive if it had to support all those hardware interface standards on the same silicon chip. Instead, the CPU implements PCI/PCI-e as a general interface to interact with all those chips using memory mapped registers. Each of these devices have an external PCI-e controller between the device and the CPU. In the same order as above, you have AHCI, NVME controller, DRAM (not PCI and in the CPU), xHCI (almost everywhere) and Intel HDA (example). Network cards are PCI-e and there isn't really a controller outside the card. Graphics card are also self standing PCI-e devices.
So, the OS detects the registers of those devices that are mapped in the address space. The OS writes at those locations, and it will write the registers of the devices. PCI-e devices can read/write DRAM directly but this is managed by the CPU in its general implementation of the PCI-e standard most likely by doing some bus arbitration. The CPU really doesn't care what's the device that it is writing. It knows that there is a PCI register there and the OS instructs to write it with something so it does. It just happens that this device is an implementation of a standard and that the OS developers read the standard so they write the proper values in those registers and the proper data structures in DRAM to make sure that the device knows what to do.
Drivers implement the standard of the software interface of those controllers. The drivers are the ones instructing the CPU on values to write and writing the proper data structures in DRAM for giving commands to the controllers. The user thread simply places the syscall number in a conventionnal register determined by the OS developers and they call an instruction to jump into the kernel at a specific address that the kernel decides by writing a register at boot. Once there, the kernel looks at the register for the number and determines what driver to call based on the operation.
On Linux and some place else, it is done with files. You call syscalls on files and the OS has a driver attached to the file. They are called virtual files. A lot of transfer mechanisms are similar to the reading/writing files pattern so Linux uses that to make a general driver model where the kernel doesn't even need to understand the driver. The driver just says create me a file there that's not really on the hard disk and if someone opens it and calls an operation on it then call this function that's there in my driver. From there, the driver can do whatever it wants because it is in kernel mode. It just creates the proper data structures in DRAM and writes the registers of the device it drives to make it do something.

How does the Linux Operating System understand the underlying hardware?

I want to learn how Linux OS understands the underlying hardware.Can anyone suggest me where to start for getting this understanding,As of now i just know the '/dev' sub-directory plays a vital role in that.
It has the device special files which are like a portal to the device driver which then takes it to the physical device.
I read somewhere that Udev daemon listens to the netlink socket to collect this information and Udev device manager detects addition and removal of devices as they occur.
But with these i am just not satisfied with the thought of how Linux reads the hardware.
Please let me know where to start to understand this, i am so thankful to anyone trying to help.
I think at first you need to find out how the memory mapping works. What is the address space and how it relates to physical memory. Then you could read about how the hardware is mapped in address space and how to access it. It is a big amount of docs to read.
Some of those information are in Linux Documentation Project.
Additionally some knowledge about electronic would be helpful.
In general - Linux for communication with devices needs some "channel" of communication. This channel may be for example ISA, PCI, USB, etc bus. For example PCI devices are memory mapped devices and Linux kernel communicates with them via memory accesses. So first Linux needs to see given device in some memory area and then it is able to configure this device and do some communication with it.
In case of USB devices it is a little bit complicated because USB devices are not memory mapped. You need to configure USB host first to be able to communicate with USB devices. Every communication with USB device is achieved via USB host.
There are also devices which are not connected via ISA, PCI or USB. They are connected directly to the processor and visible under some memory address. This solution is usually implemented in embedded devices. For example ARM processors use this approach.
Regarding udev - it is user-space application which listens for events from Linux kernel and helps other applications with recognizing device addition and configuration.

Linux device driver with optional multiple platform driver interfaces

Suppose I have a chip that has multiple options for interfacing with it, for example, the chip may have an I2C interface and a SPI interface and I can choose one of these interfaces and wire the device accordingly. How would I write a linux device driver for such device?
A chip with this behavior is ssd1307, which already has a driver implementation in the mainline linux kernel (https://github.com/torvalds/linux/blob/master/drivers/video/fbdev/ssd1307fb.c), implemented only for its I2C interface (the chip also has a 3/4 Wire SPI Interface). As you can see in the code, the probe function relies heavily on the fact that it is an I2C interface, so in order to use the SPI interface I would need to write an entirely new driver, repeating a lot of common code, or insert a lot of #ifdefs, which don't seem to be good approaches to me.
For chips providing several interface types, standard practice is to provide core driver and interface-specific frontends for it.
Several examples could be found e.g. in sound/soc/codecs/: pcm179x.c / pcm179x-i2c.c / pcm179x-spi.c and others.
This is not (yet?) done for ssd1307.

What can be removed from the Linux i2c-dev driver to serve as a base for a new driver meant for only one device?

I'm trying to write a Linux character device driver for a device that just happens to communicate over I2C. The device is an Atmel microcontroller with code that provides an I2C address. It already works using the typical i2c-dev method on the Linux-side.
So now I want to replicate i2c-dev as a new driver that works specifically with this particular device, so that I can add some of my own device-specific abstraction code on top. But I'd like to trim out all the unnecessary code from i2c-dev that currently makes it generic. What can be removed in this situation?
What can be removed in this situation?
You're actually asking an XY question.
You would be better off looking at and adapting an existing I2C device driver that is already similar in required functionality, rather than hacking a special case driver for userspace access.
So now I want to replicate i2c-dev as a new driver that works specifically with this particular device, so that I can add some of my own device-specific abstraction code on top
So then you actually need to write a "Client driver" as described below (from Linux Documentation/i2c/summary):
When we talk about I2C, we use the following terms:
Bus -> Algorithm
Adapter
Device -> Driver
Client
An Algorithm driver contains general code that can be used for a whole class
of I2C adapters. Each specific adapter driver either depends on one algorithm
driver, or includes its own implementation.
A Driver driver (yes, this sounds ridiculous, sorry) contains the general
code to access some type of device. Each detected device gets its own
data in the Client structure. Usually, Driver and Client are more closely
integrated than Algorithm and Adapter.
Details are in Documentation/i2c/writing-clients.
To find a driver of similar functionality, scan the list of I2C client drivers. Note that these I2C drivers are located in the Linux source tree by their functionality (e.g. drivers/rtc/ or drivers/hwmon/) and not their interface (i.e. I2C).

How do user-space programs interact with a USB 802.11 Wi-Fi driver on Linux?

Allow me to qualify this question by stating that I am new to driver development. I am trying to understand the driver source code for a USB Wi-Fi card that uses a RealTek 8187L chip. Based on a good answer to my previous question, I established that the relevant driver source code that I needed to inspect is in drivers/net/wireless/rtl818x/rtl8187/dev.c (inside the Linux kernel source).
Doing some reading, it seems as though a USB driver instantiates a usb_driver struct that it registers with the kernel, which describes (among other things) the devices the driver supports (.id_table), the function to execute when a supported device is connected (.probe) and optionally, a set of file operations (.fops), for interaction with user-space. The usb_driver struct associated with the 8187L driver does not include a .fops:
static struct usb_driver rtl8187_driver = {
.name = KBUILD_MODNAME,
.id_table = rtl8187_table,
.probe = rtl8187_probe,
.disconnect = __devexit_p(rtl8187_disconnect),
.disable_hub_initiated_lpm = 1,
};
module_usb_driver(rtl8187_driver);
Hence, I am curious as to how user-space programs are interacting with this driver to send and receive data.
On an old Linux Journal post (2001), there is the following excerpt:
The fops and minor variables are optional. Most USB drivers hook into another kernel
subsystem, such as the SCSI, network or TTY subsystem. These types of drivers register
themselves with the other kernel subsystem, and any user-space interactions are provided
through that interface. But for drivers that do not have a matching kernel subsystem,
such as MP3 players or scanners, a method of interacting with user space is needed. The
USB subsystem provides a way to register a minor device number and a set of
file_operations [fops] function pointers that enable this user-space interaction.
So it sounds like the 8187L driver is probably one that "hooks into another kernel subsystem". So I suppose my question is, for such a driver, which doesn't supply a .fops function pointer, how is the interaction taking place with this other kernel subsystem? Ultimately I want to be able to find the point(s) in the driver code that programs are actually interacting with to send and receive data so I can continue to analyse how the code works.
The driver for an individual wireless chipset is at a very low level. There are many layers between it and userspace. The next layer above rtl8187 is mac80211. In drivers/net/wireless/rtl818x/rtl8187/dev.c observe the call to ieee80211_register_hw. That registration call provides the link between the mac80211 layer and the rtl8187 device.
Next look at the implementation of ieee80211_register_hw, found in net/mac80211/main.c. That calls ieee80211_if_add, found in net/mac80211/iface.c, which calls register_netdevice. And that puts the device in the kernel's main list of network interfaces, making it available for things like ifconfig and route.
Most userspace programs don't interact directly with a network interface, they just send packets to an IP address and the kernel uses the routing table to choose an outgoing interface.
The RTL8187 driver registers itself with the IEEE 802.11 wireless networking subsystem, by calling ieee80211_alloc_hw() and ieee80211_register_hw() in its probe function.

Resources