Designing a device driver for Linux char to read multiple processes

I notice that for serial devices, for example, /dev/ttyUSB0 , several processes can open the device, but only one process receives bytes (depending on what it reads first).

However, for the Linux input API, for example. /dev/input/event0 , several processes can open the device, and all processes can read input events.

My current goal:

I would like to write a driver for several multi-position switches (for example, as a slider with 3 or 4 possible positions), where applications can be notified of any changes in the position of the switch. Ideally, I would like to use the Linux input API, however, it seems that the Linux input API does not support the concept of multi-position switches. Therefore, I am considering creating a custom driver with similar capabilities for the Linux input API.

Two questions:

  • In terms of driver design, why is there a difference in behavior between Linux I / O and serial Linux devices? I believe that this can be useful for several processes so that everyone can open one serial port and listen on incoming bytes all.
  • What is a good way to write a Linux symbol device driver so that it is like the Linux input input API, so multiple processes can open the device and read all the data?
+6
source share
4 answers

The difference is partly historical and partly related to different mathematical models.

  • The event subsystem is designed for unidirectional notification of simple events from several authors to a system with very small (or missing) configuration parameters.

  • The tty subsystem is designed for bidirectional end-to-end transmission of potentially large amounts of data and provides a fairly flexible (albeit rather baroque) configuration mechanism.

Historically, the tty subsystem has been the main mechanism for communicating with the system: you connect your teletype to the serial port, and the bit went in and out. Different teletypes from different manufacturers used different protocols, and therefore the termios interface appeared. In order for the system to work well in a multi-user context, buffering was added to the kernel (and was configured). The tty subsystem wait model is a point-to-point connection between moderately intelligent endpoints that agrees with what the data flowing between them will look like.

Despite the fact that in the tty subsystem it makes sense to use “one writer, several readers” (the GPS receiver is connected to the serial port, for example, constantly reporting its position), this is not the main goal of the system, but you can easily do this “several readers "in user space.

The event system, on the other hand, is basically an interrupt mechanism for things like mice and keyboards. Unlike teletypes, input devices are unidirectional and have little control over the data they produce. There is also no data buffering. No one will be interested in where the mouse moved ten minutes ago.

Hope the answer to your first question.

For your second question: "it depends." What do you want to achieve? And what is the “longevity” of the data? You should also ask yourself if it makes sense to put complexity in the kernel or whether it would be impractical to put it in user space.

Retrieving data for multiple readers is not particularly difficult. You can create a receive buffer for each reader and fill each of them as data arrives. Everything becomes a little more interesting if the data arrives faster than readers can consume it, but even this is basically a problem. Look at the network stack for inspiration!

If your device is simple and just creates events, maybe you just want to be an input driver?

Your second question is much more difficult to answer without knowing more about what you want to achieve.

Update after adding your specific goal:

When I switch positions, I usually just create a personal device and implement poll and read . If you want to be a fantasy and have a lot of switches, you can do mmap , but I would not bother.

User space just opens your /dev/foo and reads the current state and starts polling. When your switches change state, you simply wake readers and they will read again. All your readers will wake up, they will all read the new state, and everyone will be happy.

Be careful only to wake readers when your switches are "set." Many position switches are very noisy and they will quickly bounce.

In other words: I would completely ignore the input system for this. As you may have guessed, position switches are not really “inputs”.

+9
source

How a personal device handles these types of semantics is completely dependent on the driver for definition and implementation.

Of course, it would be possible, for example, to implement a driver for a serial device that will deliver all the read data to each process in which the symbol driver is open. It would also be possible to implement an input device driver that transmits events to only one process, depending on which one is queued to receive the last event. All this is a matter of coding the corresponding implementation.

The difference is that it all comes down to a simple question: "what makes sense." For a serial device, it was decided that it makes sense to process any read data in one process. For the input device, it was decided that it makes sense to deliver all input events for each process in which the input device is open. It would be reasonable to expect that, for example, one process can only care about a specific input event, for example, press the cursor button # 3, while the other process wants to process all the cursor movement events. Thus, in this situation, it may make sense to distribute all input events to all interested parties.

I ignore some side problems, for simplicity, for example, in a situation where serial data is delivered to all reading processes, which should happen when one of them stops reading from the device. This is also what will be taken into account when deciding on the implementation of the semantics of a particular device.

+4
source

Serial Input / Event

You can try to view the source code of the mouse source code.

This is similar to what you are looking for: from a TTYSx device build a input/event .

Simplification: creating a server instead of a driver .

Historically, the 1st character storage device that I remember is /dev/lp0 .

To be able to write on it from many sources, without duplication or other conflict, the LPR Server was wrotten.

To split the device you must

  • open this device in exclusive (rw) mode.
  • Create a socket (un * x or TCP) to listen on
  • redirect the socket request to the device and possibly
  • save device status (from reading reader device)
  • If necessary, send device status to socket servers.
+1
source

What is a good way to write a Linux character device driver so that it is like the Linux input input API, so multiple processes can open the device and read all the data?

See .open struct file_operations for char device. When the user space opens the device, the .open function is .open . It can add the open file to the list of open files for the device (and then .release deletes it).

The char device data structure should most likely use the struct struct list_head to store a list of open files:

 struct my_dev_data { ... struct cdev cdev; struct list_head file_open_list; ... } 

Data for each file:

 struct file_data { struct my_dev_data *dev_data; struct list_head file_open_list; ... } 

In the .open function .open add the open file to dev_data->file_open_list (use the mutex to protect these operations as necessary):

 static int my_dev_input_open(struct inode * inode, struct file * filp) { struct my_dev_data *dev_data; dev_data = container_of(inode->i_cdev, struct my_dev_data, cdev); ... /* Allocate memory for file data and channel data */ file_data = devm_kzalloc(&dev_data->pdev->dev, sizeof(struct file_data), GFP_KERNEL); ... /* Add open file data to list */ INIT_LIST_HEAD(&file_data->file_open_list); list_add(&file_data->file_open_list, &dev_data->file_open_list); ... file_data->dev_data = dev_data; filp->private_data = file_data; } 

The .release function should remove the open file from dev_data->file_open_list and free the file_data memory.

Now that the .open and .release maintain a list of open files, all open files can read data. Two possible strategies:

  • Separate read buffer for each open file. When data is received, it is copied to the buffers of all open files.
  • One read buffer for the device, but a separate read pointer for each open file. Data can be freed from the buffer after it has been read through all open files.
+1
source

Source: https://habr.com/ru/post/981423/


All Articles