GSoC23: Linux Serial Device Bus
Hello everyone. The Linux driver I am working on will be a Serial Device Bus Driver. So in this post, I will review the critical components of writing a functional Linux Serial Device Bus driver.
Introduction
Here is the description of the Serial device Bus from the Mailing List:
The serdev bus is designed for devices such as Bluetooth, WiFi, GPS and NFC connected to UARTs on host processors. Tradionally these have been handled with tty line disciplines, rfkill, and userspace glue such as hciattach. This approach has many drawbacks since it doesn’t fit into the Linux driver model. Handling of sideband signals, power control and firmware loading are the main issues.
This creates a serdev bus with controllers (i.e. host serial ports) and attached devices. Typically, these are point to point connections, but some devices have muxing protocols or a h/w mux is conceivable. Any muxing is not yet supported with the serdev bus.
The documentation about Serdev is sparse, so most of my knowledge comes from looking at other people’s code.
Device Tree
The “Open Firmware Device Tree”, or simply Devicetree (DT), is a data structure and language for describing hardware. More specifically, it is a description of hardware that is readable by an operating system so that the operating system doesn’t need to hard code details of the machine.
I will use a device tree to attach my driver to a specific UART (connecting AM62 and CC1352). I will be using the Device Tree overlay from bcfserial:
/dts-v1/;
/plugin/;
/ {
fragment@0 {
target = <&uart4>;
status = "okay";
__overlay__ {
bcfserial {
compatible = "beagle,bcfserial";
status = "okay";
};
};
};
};
On the Driver source, we will limit the probing devices using the device table:
static const struct of_device_id beagleplay_greybus_of_match = ;
;
Greybus Driver
This struct defines our actual driver and stores the related data structures. It has the following members:
- serdev: The serdev device we will use with this driver.
- tx_work: The workqueue to execute writing to UART asynchronously.
- tx_producer_lock: A spinlock for writing to the UART buffer.
- tx_consumer_lock: A spinlock for reading from the UART buffer.
- tx_circ_buf: A circular buffer that stores the UART data until it is written asynchronously.
;
Device Driver
We first need to define the serdev_device_driver
structure:
static struct serdev_device_driver beagleplay_greybus_driver = ;
It contains two functions:
beagleplay_greybus_probe
beagleplay_greybus_remove
Probe
The probe function is called when a UART device is detected. In our case, it will only be called once since we have limited the UART device. It needs to initialize our beagleplay_greybus
driver. This involves the following:
- Allocate our driver struct.
- Initialize the work queue for UART transmission.
- Set up spin locks for producer and consumer.
- Allocate a circular buffer for storing the data to write to UART.
- Open serdev device.
static int
Remove
This function is called when the driver is unloaded, or the UART device is removed. We need to perform cleanup here:
- Flush pending write work.
- Close serdev device.
static void
Writing to UART
The writing to UART part is performed asynchronously in the following steps:
- The driver writes to
beagleplay_greybus->tx_circ_buf
. - The contents of
beagleplay_greybus->tx_circ_buf
are written to the UART using the work queue.
static void
static void
Workque callback
This work queue callback is called by the Kernel asynchronously. It then writes the contents of beagleplay_greybus->tx_circ_buf
to UART.
static void
Device Operations
The serdev device operations define the functions that handle asynchronous reading and writing to UART.
static struct serdev_device_ops beagleplay_greybus_ops = ;
It has two main functions:
beagleplay_greybus_tty_receive
beagleplay_greybus_tty_wakeup
TTY Recieve
This function is called when we receive data over UART. For now, I am just printing the data Kernel Logs.
static int
TTY Wakeup
We call schedule_work
when tty Wakeup is triggered by the Kernel. This adds the job to Kernel global work queue if it was not already queued and leaves it in the same position on the kernel-global work queue otherwise.
static void
Send HelloWorld over UART
Here is a simple function to write “HelloWorld” over Serial from our new driver:
static void ;
Now we can call this function from beagleplay_greybus_probe
after driver initialization is complete.
NOTE: This function writes to UART synchronously. Generally, this should be avoided.
Conclusion
Here is the current working repository for my Linux Driver. Feel free to check out the code and open a PR if you are interested.
Consider supporting me if you like my work.