In this article, I look at the process of writing a simple PCI device driver under Linux OS. The device of the PCI software model, the writing of the driver itself, the user test program and the launch of the entire system will be briefly studied.
The interface of LIR940 / 941 displacement sensors will be presented as an experimental. This device, domestically produced, provides connection to up to 4 encoders using the serial SSI protocol over the RS-422 physical interface. Today, the PCI bus (and its newer version, PCI Express) is a standard interface for connecting a wide range of additional equipment to modern computers and this bus does not need a special presentation.
It is not uncommon in the form of a PCI adapter that various specialized input / output interfaces are implemented to connect at least specialized external equipment. Also, there are still not rare situations when a hardware manufacturer provides a driver only for Windows OC. ')
LIR941 was acquired by the organization in which I work to obtain data from high-precision absolute displacement sensors. When there was a question about working under Linux, it turned out that the manufacturer does not provide anything for this OS. There was also nothing in the network, which, incidentally, is normal for such a rare and specialized device.
On the board itself, there is an Altera FPGA, which implements the entire interaction logic, as well as several (from 2 to 4) RS-422 interfaces with galvanic isolation.
Typically, in such a situation, developers go the way of reverse engineering, trying to figure out how the Windows driver works.
Morally preparing for this entertainment, I decided to start by trying the easiest and most direct way - I wrote the request directly to the equipment manufacturer.
I asked if they could provide any documentation or specification for their device, so that I could develop an open source driver for Linux. To my surprise, the manufacturer went to the meeting, they answered me very quickly and sent all the necessary documentation!
PCI bus
Before proceeding to the development of the actual driver, I propose to consider how the PCI software model works and how it interacts with the device.
A small note about PCI and PCI Express. Despite the fact that the hardware is two different interfaces - they both use the same software model, so from the point of view of the developer there is no particular difference and the driver will work the same way.
The PCI bus allows you to connect at the same time a large number of devices and often consists of several physical buses connected to each other through special “bridges” - the PCI Bridge. Each bus has its own number, devices on the bus are also assigned a unique number. Also, each device can be multifunctional, as if divided into separate devices that implement some separate functions, each such function is assigned its own number in the same way. Thus, the system "path" to the specific functionality of the device looks like this: <pci bus number> <device number> <function number>.
To see which devices are connected to the PCI bus on Linux, just run the lspci command.
The output may be unexpectedly long, since in addition to devices directly physically connected via pci / pci express slots (such as a video adapter), there are many system devices that are soldered (or included in a chipset chipset device) on the motherboard. The first column of this conclusion, consisting of numbers, is precisely the set of identifiers discussed above.
This output means that the NVIDIA GT 240 video adapter is on PCI bus 01, the device number is 00 and its only function number is also 0.
It should also be added that each PCI device has a set of two unique identifiers - Vendor ID and Product ID, which allows drivers to uniquely identify devices and work with them correctly.
The issuance of unique Vendor ID for hardware manufacturers engaged in a special consortium - PCI-SIG .
To see these identifiers it is enough to run lspci with the -nn keys:
Reading service information and configuration of a PCI device is carried out through a set of configuration registers. Each device must provide a standard set of such registers, which will be discussed later.
The registers are mapped into the computer’s RAM at boot time and the operating system kernel associates with the device a special data structure - pci_dev, and also provides a set of functions for reading and writing.
In addition to the PCI configuration registers, devices can have up to 6 data input / output channels. Each channel is also displayed in RAM at a certain address assigned by the OS kernel.
Read-write operations of this memory area, with certain parameters of block size and offset, lead directly to write-read to the device.
It turns out that to write a PCI driver, you need to know what configuration registers the device uses, as well as what offsets (and what exactly) you need to write / read. In my case, the manufacturer provided all the necessary information.
PCI configuration space
The first 64 bytes are standardized and must be provided by all devices, whether they are required or not.
The picture shows the registers that are mandatory, they must always contain any meaningful values, the rest may contain zeros, if this is not required in this case.
The byte order in all PCI registers is little-endian, this should be considered if driver development is carried out for an architecture with a different order.
Let's see what some of the registers are.
VendorID and ProductID are registers already known to us, in which the manufacturer and equipment identifiers are stored. Each of the registers takes 2 bytes.
Command - this register defines some features of a PCI device, for example, it allows or denies access to memory.
The initialization of these bits is handled by the operating system.
Status - bits of this register store information about various PCI bus events.
These values ​​are set by the equipment; in my case, only bits 9, 10 were configured, which determine the board response time.
Revision ID - number, revision of a specific board. Useful when there are several revisions of the device and differences need to be considered in the driver code.
Class Code is a “magic” number that displays the class of the device, for example: Network Controller, Display Controller, etc. The list of existing codes can be found here .
Base Address Registers - these registers, in the amount of 6 pieces, are used to determine how and how much memory is allocated to the device for input / output procedures. This register is used by the pci subsystem of the kernel and is usually not of interest to driver developers.
Now you can go to programming and try to read these registers and get access to the I / O memory.
Kernel module development
As many probably know, the entry and exit points to the Linux kernel module are special __init and __exit functions.
We define these functions and execute the procedure for registering our driver by calling the special function pci_register_driver (struct pci_driver * drv), as well as the unloading procedure using pci_unregister_driver (struct pci_driver * drv).
The argument of the register and unregister functions is the pci_driver structure that needs to be initialized first, let's do it at the very beginning, declaring the structure static.
The structure fields that we initialize: name is the unique name of the driver that will be used by the kernel in / sys / bus / pci / drivers id_table is a table of the Vendor ID and Product ID pairs that the driver can work with. probe - a function called by the kernel after loading the driver, serves to initialize the equipment remove - the function called by the kernel when the driver is unloaded, serves to free any previously occupied resources
Also in the pci_driver structure, additional functions are provided that we will not use in this example: suspend - this function is called when the device goes to sleep resume - this function is called when the device wakes up
Consider how the table identifies the pairs Vendor ID and Product ID. This is a simple structure with a list of identifiers.
Where 0x0F0F is the Vendor ID, and 0x0F0E and 0x0F0D are the Product ID pair of this vendor. A pair of identifiers can be one or several. Be sure to complete the list with the empty identifier {0,}
After the declaration of the filled structure, you must pass it to the macro.
MODULE_DEVICE_TABLE(pci, my_driver_id_table);
In the function my_driver_probe () we can actually do everything we want.
For example, you can try to read the configuration registers described above in order to verify the correctness of identifiers or to determine the revision of the board.
In case of any problems or inconsistencies, you can return a negative error code value and the kernel will abort the loading of the module. Reading the configuration registers will be discussed below.
It is also usually in this place that the initialization of the device's I / O memory is performed for subsequent work with it.
It will be useful in this place to define some “private” driver structure in which data will be stored, useful in all driver functions. For example, it might be a pointer to the same device I / O memory.
structmy_driver_priv { u8 __iomem *hwmem; }
After initialization of the private structure, it is necessary to register it.
pci_set_drvdata(pdev, drv_priv);
In the my_driver_remove () function, it is convenient to perform freeing of occupied resources, for example, you can free up I / O memory.
Also here it is necessary to free the struct pci_dev structure itself.
There are several functions for performing read / write registers in the Linux kernel. All these functions related to the PCI subsystem are available in the header file <linux / pci.h>
Reading 8, 16 and 32 bits of registers respectively:
intpci_read_config_byte(struct pci_dev *dev, int where, u8 *ptr); intpci_read_config_word(struct pci_dev *dev, int where, u16 *ptr); intpci_read_config_dword(struct pci_dev *dev, int where, u32 *ptr);
Write 8, 16 and 32 bits of registers, respectively:
intpci_write_config_byte(struct pci_dev *dev, int where, u8 val); intpci_write_config_word(struct pci_dev *dev, int where, u16 val); intpci_write_config_dword(struct pci_dev *dev, int where, u32 val);
The first argument of all these functions is the pci_dev structure, which is directly related to a specific PCI device. Initialization of this structure will be discussed below.
For example, we want to read the values ​​of the Vendor ID, Product ID and Revision ID registers:
As you can see, everything is extremely simple, by substituting the necessary value of the whrere argument, we can access any configuration register of a particular pci_dev.
Reading / writing a device’s memory is a bit more complicated.
We have to specify what type of resource we want to receive, decide on the size and offset, select the necessary piece of memory and map this piece of memory to the device. After that, we can write and read this memory as we please, interacting directly with the device.
#include<linux/pci.h> int bar; unsigned long mmio_start, mmio_len; u8 __iomem *hwmem; struct pci_dev *pdev; ... // , / bar = pci_select_bars(pdev, IORESOURCE_MEM); // "" pci_enable_device_mem(pdev); // , pci_request_region(pdev, bar, "My PCI driver"); // mmio_start = pci_resource_start(pdev, 0); mmio_len = pci_resource_len(pdev, 0); // hwmem = ioremap(mmio_start, mmio_len);
Then we can freely work with the memory pointed to by hwmem. It is best to use special kernel functions for this purpose.
Make sure you have the kernel header files installed. For Debian / Ubuntu, installing the required package is as follows:
sudo apt-get install linux-headers-$(uname -r)
The module is compiled with a simple make command, you can try loading the module with
sudo insmod test_pci_module.ko
Most likely, nothing will just happen quietly, unless you really have a device with Vendor and Product ID from our example.
Now I would like to return to the specific device for which the driver was developed. Here is what information about IO sent to me by the developers of the LIR-941 board:
RgStatus: b7 - Flag of pause between SSI transactions (1 - pause) (see SSI protocol) b6 - The flag of the current transaction (1 - data is being transmitted) (see SSI protocol) b5 - Ext4 (Data latch on Ext4 signal occurred) b4 - Ext3 (Data latch on Ext3 signal occurred) b3 - Ext2 (Data latch occurred on Ext2 signal) b2 - Ext1 (Data latch occurred on Ext1 signal) b1 - Continuous polling mode (Upon completion of the transfer of the code, a new request is generated by the hardware) b0 - On request from computer (Single request for current position)
This means that if I want, for example, to read data from an encoder connected to channel 3, I need to check the seventh bit of the RgStatus3 block, wait there for a unit (the pause between transactions means it has already received information from the sensor and recorded it in the board’s memory, prepare for the next request) and read the number stored in the third piece of memory with a length of 32 bits.
It all comes down to calculating the necessary shift from the beginning of a piece of memory and reading the required number of bytes.
It is clear from the table that channel data is stored as 32 bit values, and RgStatus data is stored as 8 bit lengths.
So to read RgStatus3 you need to move 4 times 32 bits and two times 8 bits and then read 8 bits from this position.
And to read the data of the third channel, you need to move 2 times 32 bits each and read the value 32 bits long.
To perform all these operations, you can write convenient macros:
Everything, we received from the board the data of the sensor connected to the third channel and wrote it into the variable enc_data.
Regarding the record in the device, the manufacturer has already sent another label. It can be seen that the record structure is slightly different and you will have to write new macros, with new offsets.
DATA WIDTH - Determines the maximum number of bits in a single SSI transaction. (digit capacity of the receiving register). Valid values ​​are from 1 to 32
CLOCK RATE - A port that determines the division ratio of the system Clk (33 MHz) for the formation of a Block SSI. Kdel = (CLOCK RATE) * 2 + 2 PAUSE RATE Port defining the amount of pause after the transaction, in periods Clk (30 ns) CONTROL 1: b7 - SSI mode (0 - normal mode, 1 - 16-bit abs mode. Sensor, with wait for the start bit (obsolete data output, only needed for compatibility)). b6 - Reserved b5 - Ext4 external signal resolution b4 - Resolution of the external signal Ext3 b3 - Resolution of the external signal Ext2 b2 - Resolution of the external signal Ext1 b1 - Resolution of continuous sensor polling b0 - Generate a one-time survey
It's all the same here - we consider the shift for the required area and write the value with the corresponding function iowriteX
User Interface Interaction with PCI Driver
There are several ways to communicate the higher software with our driver. One of the oldest, easiest, and most popular ways is the character device. Character device is a virtual device that can be added to the / dev directory, it can be opened, something written, read, make ioctl calls to set any parameters.
A good example of such a device is a serial driver with its / dev / ttySX
Registering the character device is convenient to make a separate function.
intcreate_char_devs(struct my_driver_priv* drv);
A pointer to our private structure is necessary for the subsequent initialization of the file object, so that with each user open / read / write / ioctl / close call we will have access to our private structure and will be able to perform read / write operations to the PCI device.
It is convenient to call create_char_devs () in the my_driver_probe () function, after all initializations and checks.
In my case, this function is called create_char_devs (), in the plural. The fact is that the driver creates several of the same name (but with different digital indices at the end of the name) character device, one per channel of the LIR941 board, this allows you to conveniently, independently and simultaneously work with several connected sensors at once.
Creating a character device is pretty simple.
We determine the number of devices, allocate memory and initialize each device with the configured file_operations structure. This structure contains references to our file operations functions that will be called by the kernel when working with a device file in user space.
Inside the kernel, all / dev devices are identified by a pair of ids.
<major>:<minor>
Some major identifiers are reserved and are always assigned to specific devices, other identifiers are dynamic. The major value is shared by all devices of a specific driver; they differ only in the minor identifiers.
When you initialize your device, you can set the value to major with your hands, well, it’s better not to do this, since you can arrange a conflict. The best option is to use the MAJOR () macro.
Its application will be shown in the code below.
In the case of a minor, the value usually coincides with the sequence number of the device, when created, starting from zero. This allows you to find out which particular device / dev / device-X is accessed from the kernel space - just look at the minor available in the handler for file operations.
Identifiers: displayed by the ls utility with the -l option for example, if you run:
The number 89 is the major identifier of the driver for the i2c bus controller, it is common to all channels of i2c, and 0.1,2,3,4 is the minor identifier.
The mydev_open () function will be called if someone tries to open our device in user space.
It is very convenient in this function to initialize the private structure for an open device file. In it, you can save the minor value for the current open device. You can also put a pointer to some more global structures to help interact with the rest of the driver, for example, we can save a pointer to my_driver_priv, which we worked with earlier, in this place. A pointer to this structure can be used in ioctl / read / write operations to execute hardware queries.
Read and write operations are fairly simple, the only “nuance” is the lack of security (or even impossibility) of direct access of the user application to the kernel memory and vice versa. In this regard, to obtain data written using the write () function, you must use the kernel function copy_from_user () .
And when executing read (), you must use copy_to_user () .
Both features come with various checks and ensure safe data copying between the core and user space.
The ioctl () call handler takes as its arguments the actual ioctl number of the operation and some transmitted data as arguments (if necessary). The ioctl () operation numbers are determined by the driver developer. It's just some kind of "magic" numbers behind the readable define.
These numbers should be known to the user program, so it is convenient to take them somewhere as a separate header file.