Write Linux parallel interface character device driver translated from: the Linux kernel Primer: a top-down approach for x86 and PowerPC ubuntures
"
Translated by clardia Salzberg Rodriguez, Gordon Fischer, Steven Smolski Liu Jianwen
)
Key: Parallel Port of Linux driver
Intro
In this tutorial, a Linux driver is written for a parallel port controller with simple functions. The current PC Parallel Port Controller is generally integrated into the super Io of the nanqiao chip. This experiment driver is a good example of writing character-type device drivers. The experiment code is not practical, but you can improve it by yourself. Because I write code at the device register level, these codes are also applicable to the PowerPC platform.
The parallel port driver module (which is not specifically referred to as the dynamic loading module) implements standard open (), close (), and the most important IOCTL () System Call functions, the device driver structure and working principle are displayed. We have no actual read () or write (), because this experiment is suitable for replacing it with IOCTL.
We will first give a general description of how to communicate with the parallel port, and then analyze the final structure of the character driver module. We use the ioctl () interface to access each register of the device controller separately, and finally create an application to demonstrate calling the driver.
Parallel Port hardware
This project uses x86 as the hardware environment, but the final driver module is easily transplanted to PowerPC. Currently, the parallel port is still common in the Embedded PowerPC platform, but it is increasingly rare in desktop PCs, such as in the fourth and fifth generation PCs.
When communicating with the parallel port, the C Code uses the INB () and outb () functions respectively (). I can also use the readb () writeb () function independent of specific systems (such as x86 and PPC), which are two macro definitions in Io. h.
The parallel port of the x86 system is generally integrated into the superio device on the system board, or can be added to the host in an independent expansion card (PCI) mode. In terms of underlying configuration, what address is used for intercommunication with the host? Is there an interrupt signal? You can enter the BIOS device to view the system I/O address space occupied by the parallel port. Generally, in x86 systems, the parallel port may use three base addresses: 0x278, 0x378, 0x3bc, and irq7. The parallel port has three 8-bit registers (one occupies one I/O space address, so the parallel port only occupies three I/O address spaces). For example, the following uses 0x378 as the base address:
[*]
Active low)
Kemin: It is said that a parallel port is only a port, not a device controller. How can we still use the register concept? What is the role of the device control register connected by the parallel port?
- The data register contains the 8 bits to write out to the pins on the connector.
- The Status Register contains the input signals from the connector.
- The control register sends specific control signals to the connector.
The connector for the parallel port is 25-pin D-shell (DB-25), the following is a ing table of the Connector pins and signals:
Note: The parallel port is sensitive to static electricity and current. Do not experiment with your onboard parallel port unless you are familiar with the hardware or don't worry about burning the motherboard. We recommend that you use an independent port card for the experiment.
Modify the connection head (view the logic diagram
)
For input operations, we will jumper D7 (PIN 9) to acknowledge (PIN 10) and D6 (PIN 8) to busy (pin 11) with 470 ohm resistors. to monitor output, we drive LEDs with data pins D0 through D4 by using a 470 ohm current limiting resistor. we can do this by using an old printer cable or a 25-pin male d-shell connector from a local electronics store.
Datasheet
A good register-level programmer (Kemin: Actually an assembler programmer) is always very familiar with the hardware details on his hand. Including quickly finding The Datasheet of a parallel Device
(Kemin: note that the device [Controller
] Datasheet, not the datasheet of the parallel port, and the parallel port itself is not a device ). Datasheet contains some device operation information, such as the function of each register of the controller and the maximum current of the device. The programmer must carefully read the device before performing the operation [Controller
] Datasheet.
Parallel Port Software
The driver source code of this project is mainly three files: parll. C, parll. H, and make.
1. Setting up the file operations (FoPs) create a file operation data structure (FoPs)
As mentioned above, the driver module implements the standard open (), close (), and IOCTL () functions. In addition, the module also needs initialization code and cleanup code.
Our first step is to create a file operations structure ). This structure is defined in/Linux/fs. H. This structure has many abstract interface functions not implemented by the driver.
Struct file_operations {<br/> loff_t (* llseek) (struct file *, loff_t, INT); <br/> ssize_t (* read) (struct file *, char *, size_t, loff_t *); <br/> ssize_t (* write) (struct file *, const char *, size_t, loff_t *); <br/> int (* readdir) (struct file *, void *, filldir_t); <br/> unsigned int (* poll) (struct file *, struct poll_table_struct *); <br/> int (* IOCTL) (struct inode *, struct file *, unsigned int, unsigned long); <br/> int (* MMAP) (struct file *, struct vm_area_struct *); <br/> int (* open) (struct inode *, struct file *); <br/> int (* flush) (struct file *); <br/> int (* release) (struct inode *, struct file *); <br/> int (* fsync) (struct file *, struct dentry *); <br/> int (* fasync) (INT, struct file *, INT); <br/> int (* check_media_change) (kdev_t Dev ); <br/> int (* revalidate) (kdev_t Dev); <br/> int (* Lock) (struct file *, Int, struct file_lock *); <br/> };
By implementing the abstract interface of this structure, inform the kernel of the [specific implementation function code] (open, release, and IOCTL) location.
Parll. c -->File_operations parlport_fops
Struct file_operations parlport_fops ={< br/>. Open = parlport_open, <br/>. IOCTL = parlport_ioctl, <br/>. Release = parlport_close}; <br/>
Then implement open () and close () respectively. In this experiment, the two functions have no substantive work:
Parll. c -->Parlport_open ()
Static int parlport_open (struct inode * Ino, struct file * filp) <br/>{< br/> printk ("/n parlport OPEN function "); <br/> return 0; <br/>}< br/> static int parlport_close (struct inode * Ino, struct file * filp) <br/>{< br/> printk ("/n parlport close function"); <br/> return 0; <br/>}
Implement the ioctl () function. Note that the following declaration code must be placed before the source code file parll. C:
Parll. c -->Parlport_ioctl ()
# Define module_name "parll" <br/> static int base = 0x378; <br/> static int parlport_ioctl (struct inode * Ino, struct file * filp, unsigned int ioctl_cmd, unsigned long parm) <br/>{< br/> printk ("/n parlport ioctl function"); <br/> If (_ ioc_type (ioctl_cmd )! = Ioctl_type) <br/>{< br/> printk ("/n % s wrong IOCTL type", module_name); <br/> return-1; <br/>}< br/> switch (ioctl_cmd) <br/>{< br/> case data_out: <br/> printk ("/n % s IOCTL data out = % x", module_name, (unsigned INT) parm); <br/> outb (parm & 0xff, base + 0); <br/> return (parm & 0xff); <br/> case get_status: <br/> parm = INB (base + 1 ); <br/> printk ("/n % s IOCTL get status = % x", module_name, (unsigned INT) parm); <br/> return parm; <br/> case ctrl_out: <br/> printk ("/n % s ioctl ctrl out = % x", module_name, (unsigned INT) parm ); <br/> outb (parm & 0xff, base + 2); <br/> return 0; <br/>}// end switch <br/> return 0; <br/>} // end IOCTL
The objective of IOCTL () function design is to implement custom I/O operations (Kemin: that is, operations that cannot be met by standard I/O operations (such as read or write operations ). This project demonstrates how to use the parallel port to implement three I/O commands to access controller register data:
* The data_out Command sends a value to the data register,
* The get_status command reads from the Status Register,
* The ctrl_out command is available to set the control signals to the port.
Although according to the [beautification principle] (better methodology), the specific details of read/write device operations should be hidden in the read () and write () interface functions, however, the main purpose of this experiment is to demonstrate I/O, rather than data encapsulation. Therefore, use IOCTL.
The three I/O macro commands used in the Code are defined in the header file parll. h. The start of parlport_ioctl () contains the logic for checking the input command. Because the application will introduce
Use the same header file, so the definitions of these commands are consistent.
2. Setting up the module initialization routine to create the module initialization Function
The initialization code is used to associate the driver module with the operating system. Complicated driver types (such as USB) may require some necessary data structures to be initialized at this time. Because the parallel driver does not need complex data structures, the following is a simple registration of the driver module into the operating system:
Parll. c -->Parll_init ()
Static int parll_init (void) <br/>{< br/> int retval; <br/> retval = register_chrdev (Major, module_name, & parlport_fops ); <br/> If (retval <0) <br/> {<br/> printk ("/n % s: Can't register", module_name ); <br/> return retval; <br/>}< br/> else <br/>{< br/> major = retval; <br/> printk ("/n % s: registered, Major = % d", module_name, Major); <br/> If (request_region (base, 3, module_name )) <br/> printk ("/n % s: I/O Region busy. ", module_name); <br/>}< br/> return 0; <br/>}
The register_chrdev () function takes in the requested major number (discussed in section 5.2 and later in Chapter 10; if 0, the kernel assigns one to the module ). recall that the major number is kept in the inode structure, which is pointed to by the dentry structure, which is pointed to by a file struct. the second parameter is the name of the device as it will appear in/proc/devices. the third parameter is
The file operations structure that was just shown.
Upon successfully registering, our init routine callrequest_region () with the base address of the parallel port and the length (in bytes) of the range of registers we are interested in.
The init_module () function returns a negative number upon failure.
3. Setting up the module cleanup routine create a module cleanup function
The cleanup_module () function is responsible for unregistering the module and releasing the I/O range that we requested earlier:
Parll. c -->Parll_cleanup ()
Static void parll_cleanup (void) <br/>{< br/> printk ("/n % s: Cleanup", module_name); <br/> release_region (base, 3 ); <br/> unregister_chrdev (Major, module_name); <br/>}
Finally, we include the required init and cleanup entry points.
Parll. c
Module_init (parll_init); <br/> module_exit (parll_cleanup );
4. inserting the module to install the driver module
We can now insert our module into the kernel, as in the previous projects, by using
LKP :~ # Insmod parll. Ko
Looking at/var/log/messages shows us our Init () Routine output as before, but make specific note of the major number returned.
In previous projects, we simply inserted and removed our module from the kernel. We now need to associate our module with the filesystem with the mknod command. from the command line, enter the following:
LKP :~ # Mknod/dev/parll C <XXX> 0
The parameters:
* C. Create a character special file (as opposed to block)
*/Dev/parll. The path to our device (for the open call)
* XXX. The major number returned at init time (from/var/log/messages)
* 0. The minor number of our device (not used in this example)
For example, if you saw a major number of 254 in/var/log/messages, the command wowould look like this:
LKP :~ # Mknod/dev/parll C 254 0
5. application code
Here, we created a simple application that opens our module and starts a binary count on the D0 through D7 output pins.
Compile this code with GCC app. C. The executable output defaults to A. out:
App. c
// Application to use parallel port driver <br/> # include <fcntl. h> <br/> # include <Linux/IOCTL. h> <br/> # include "parll. H "// input the macro definition package of the ioctl Operation Command <br/> main () <br/>{< br/> int fptr; <br/> int I, retval, parm = 0; <br/> printf ("/nopening driver now"); <br/> If (fptr = open ("/dev/parll", o_wronly )) <0) // open the device file and obtain the reference <br/> {<br/> printf ("/nopen failed, returned = % d", fptr ); <br/> exit (1); <br/>}< br/> for (I = 0; I <0xff; I ++) <BR/>{< br/> system ("sleep. 2 "); // slow down loop execution speed <br/> retval = IOCTL (fptr, data_out, parm ); // write the least significant bits into the Union. <br/> retval = IOCTL (fptr, get_status, parm ); // read the status information <br/> // separate the status bits. Note that the busy bits (busy *) are valid at a low level. <Br/> If (! (Retval & 0x80) <br/> printf ("/nbusy signal COUNT = % x", parm); <br/> If (retval & 0x40) <br/> printf ("/Nack signal COUNT = % x", parm); <br/> // uncomment these as you improve on the design. <br/> // If (retval & 0x20) <br/> // printf ("/npaper end signal COUNT = % x", parm ); <br/> // If (retval & 0x10) <br/> // printf ("/nselect signal COUNT = % x", parm ); <br/> // If (retval & 0x08) <br/> // printf ("/nerror signal COUNT = % x", parm ); <br/> parm ++; <br/>}< br/> // close the our module. <br/> close (fptr); <br/>}
If you change the connector to the following format, the busy bit (busy) and response bit (ACK) can also be highly effective. The user code can read the two highest valid data bits to represent them.
Figure 5.5. Built Connector
This experiment only demonstrates the main elements required to write a character device driver. However, with this demo, you can write your own character device driver. To add Interrupt Processing for this driver, you must call request_irq () in init_module () and pass the required IRQ and the name of the interrupt processing function.
The following are some suggestions for Driver Improvement in this experiment:
Here are some suggested additions to the driver:
- * Make parallel port module service-timer interrupts to poll input.
- How can we multiplex 8 bits of I/O into 16, 32, 64? What is sacriiced?
- Send a character out the serial port from the write routine within the module.
- Add an interrupt routine by using the ACK signal.