Linux下的并口编程

The printer is accessible through /dev/lp0; in the same way, the parallel port itself is accessible through /dev/parport0. The difference is in the level of control that you have over the wires in the parallel port cable.

With the printer driver, a user-space program (such as the printer spooler) can send bytes in "printer protocol". Briefly, this means that for each byte, the eight data lines are set up, then a "strobe" line tells the printer to look at the data lines, and the printer sets an "acknowledgement" line to say that it got the byte. The printer driver also allows the user-space program to read bytes in "nibble mode", which is a way of transferring data from the peripheral to the computer half a byte at a time (and so it's quite slow).

In contrast, the ppdev driver (accessed via /dev/parport0) allows you to:

  • examine status lines,

  • set control lines,

  • set/examine data lines (and control the direction of the data lines),

  • wait for an interrupt (triggered by one of the status lines),

  • find out how many new interrupts have occurred,

  • set up a response to an interrupt,

  • use IEEE 1284 negotiation (for telling peripheral which transfer mode, to use)

  • transfer data using a specified IEEE 1284 mode.


Programming interface

The ppdev interface is largely the same as that of other character special devices, in that it supports open, close, read, write, and ioctl. The constants for the ioctl commands are in include/linux/ppdev.h.

Starting and stopping: open and close

The device node /dev/parport0 represents any device that is connected to parport0, the first parallel port in the system. Each time the device node is opened, it represents (to the process doing the opening) a different device. It can be opened more than once, but only one instance can actually be in control of the parallel port at any time. A process that has opened /dev/parport0 shares the parallel port in the same way as any other device driver. A user-land driver may be sharing the parallel port with in-kernel device drivers as well as other user-land drivers.

Control: ioctl

Most of the control is done, naturally enough, via the ioctl call. Using ioctl, the user-land driver can control both the ppdev driver in the kernel and the physical parallel port itself. The ioctl call takes as parameters a file descriptor (the one returned from opening the device node), a command, and optionally (a pointer to) some data.

PPCLAIM

Claims access to the port. As a user-land device driver writer, you will need to do this before you are able to actually change the state of the parallel port in any way. Note that some operations only affect the ppdev driver and not the port, such as PPSETMODE; they can be performed while access to the port is not claimed.

PPEXCL

Instructs the kernel driver to forbid any sharing of the port with other drivers, i.e. it requests exclusivity. The PPEXCL command is only valid when the port is not already claimed for use, and it may mean that the next PPCLAIM ioctl will fail: some other driver may already have registered itself on that port.

Most device drivers don't need exclusive access to the port. It's only provided in case it is really needed, for example for devices where access to the port is required for extensive periods of time (many seconds).

Note that the PPEXCL ioctl doesn't actually claim the port there and then---action is deferred until the PPCLAIM ioctl is performed.

PPRELEASE

Releases the port. Releasing the port undoes the effect of claiming the port. It allows other device drivers to talk to their devices (assuming that there are any).

PPYIELD

Yields the port to another driver. This ioctl is a kind of short-hand for releasing the port and immediately reclaiming it. It gives other drivers a chance to talk to their devices, but afterwards claims the port back. An example of using this would be in a user-land printer driver: once a few characters have been written we could give the port to another device driver for a while, but if we still have characters to send to the printer we would want the port back as soon as possible.

It is important not to claim the parallel port for too long, as other device drivers will have no time to service their devices. If your device does not allow for parallel port sharing at all, it is better to claim the parallel port exclusively (see PPEXCL).

PPNEGOT

Performs IEEE 1284 negotiation into a particular mode. Briefly, negotiation is the method by which the host and the peripheral decide on a protocol to use when transferring data.

An IEEE 1284 compliant device will start out in compatibility mode, and then the host can negotiate to another mode (such as ECP).

The ioctl parameter should be a pointer to an int; values for this are in incluce/linux/parport.h and include:

  • IEEE1284_MODE_COMPAT

  • IEEE1284_MODE_NIBBLE

  • IEEE1284_MODE_BYTE

  • IEEE1284_MODE_EPP

  • IEEE1284_MODE_ECP

The PPNEGOT ioctl actually does two things: it performs the on-the-wire negotiation, and it sets the behaviour of subsequent read/write calls so that they use that mode (but see PPSETMODE).

PPSETMODE

Sets which IEEE 1284 protocol to use for the read and write calls.

The ioctl parameter should be a pointer to an int.

PPGETMODE

Retrieves the current IEEE 1284 mode to use for read and write.

PPGETTIME

Retrieves the time-out value. The read and write calls will time out if the peripheral doesn't respond quickly enough. The PPGETTIME ioctl retrieves the length of time that the peripheral is allowed to have before giving up.

The ioctl parameter should be a pointer to a struct timeval.

PPSETTIME

Sets the time-out. The ioctl parameter should be a pointer to a struct timeval.

PPGETMODES

Retrieves the capabilities of the hardware (i.e. the modes field of the parport structure).

PPSETFLAGS

Sets flags on the ppdev device which can affect future I/O operations. Available flags are:

  • PP_FASTWRITE

  • PP_FASTREAD

  • PP_W91284PIC

PPWCONTROL

Sets the control lines. The ioctl parameter is a pointer to an unsigned char, the bitwise OR of the control line values in include/linux/parport.h.

PPRCONTROL

Returns the last value written to the control register, in the form of an unsigned char: each bit corresponds to a control line (although some are unused). The ioctl parameter should be a pointer to an unsigned char.

This doesn't actually touch the hardware; the last value written is remembered in software. This is because some parallel port hardware does not offer read access to the control register.

The control lines bits are defined in include/linux/parport.h:

  • PARPORT_CONTROL_STROBE

  • PARPORT_CONTROL_AUTOFD

  • PARPORT_CONTROL_SELECT

  • PARPORT_CONTROL_INIT

PPFCONTROL

Frobs the control lines. Since a common operation is to change one of the control signals while leaving the others alone, it would be quite inefficient for the user-land driver to have to use PPRCONTROL, make the change, and then use PPWCONTROL. Of course, each driver could remember what state the control lines are supposed to be in (they are never changed by anything else), but in order to provide PPRCONTROL, ppdev must remember the state of the control lines anyway.

The PPFCONTROL ioctl is for "frobbing" control lines, and is like PPWCONTROL but acts on a restricted set of control lines. The ioctl parameter is a pointer to a struct ppdev_frob_struct:

	
struct ppdev_frob_struct {
unsigned char mask;
unsigned char val;
};

The mask and val fields are bitwise ORs of control line names (such as in PPWCONTROL). The operation performed by PPFCONTROL is:

	
new_ctr = (old_ctr & ~mask) | val;

In other words, the signals named in mask are set to the values in val.

PPRSTATUS

Returns an unsigned char containing bits set for each status line that is set (for instance, PARPORT_STATUS_BUSY). The ioctl parameter should be a pointer to an unsigned char.

PPDATADIR

Controls the data line drivers. Normally the computer's parallel port will drive the data lines, but for byte-wide transfers from the peripheral to the host it is useful to turn off those drivers and let the peripheral drive the signals. (If the drivers on the computer's parallel port are left on when this happens, the port might be damaged.)

This is only needed in conjunction with PPWDATA or PPRDATA.

The ioctl parameter is a pointer to an int. If the int is zero, the drivers are turned on (forward direction); if non-zero, the drivers are turned off (reverse direction).

PPWDATA

Sets the data lines (if in forward mode). The ioctl parameter is a pointer to an unsigned char.

PPRDATA

Reads the data lines (if in reverse mode). The ioctl parameter is a pointer to an unsigned char.

PPCLRIRQ

Clears the interrupt count. The ppdev driver keeps a count of interrupts as they are triggered. PPCLRIRQ stores this count in an int, a pointer to which is passed in as the ioctl parameter.

In addition, the interrupt count is reset to zero.

PPWCTLONIRQ

Set a trigger response. Afterwards when an interrupt is triggered, the interrupt handler will set the control lines as requested. The ioctl parameter is a pointer to an unsigned char, which is interpreted in the same way as for PPWCONTROL.

The reason for this ioctl is simply speed. Without this ioctl, responding to an interrupt would start in the interrupt handler, switch context to the user-land driver via poll or select, and then switch context back to the kernel in order to handle PPWCONTROL. Doing the whole lot in the interrupt handler is a lot faster.

Transferring data: read and write

Transferring data using read and write is straightforward. The data is transferring using the current IEEE 1284 mode (see the PPSETMODE ioctl). For modes which can only transfer data in one direction, only the appropriate function will work, of course.

Waiting for events: poll and select

The ppdev driver provides user-land device drivers with the ability to wait for interrupts, and this is done using poll (and select, which is implemented in terms of poll).

When a user-land device driver wants to wait for an interrupt, it sleeps with poll. When the interrupt arrives, ppdev wakes it up (with a "read" event, although strictly speaking there is nothing to actually read).


Examples

Presented here are two demonstrations of how to write a simple printer driver for ppdev. Firstly we will use the write function, and after that we will drive the control and data lines directly.

The first thing to do is to actually open the device.

int drive_printer (const char *name)
{
int fd;
int mode; /* We'll need this later. */

fd = open (name, O_RDWR);
if (fd == -1) {
perror ("open");
return 1;
}

Here name should be something along the lines of "/dev/parport0". (If you don't have any /dev/parport files, you can make them with mknod; they are character special device nodes with major 99.)

In order to do anything with the port we need to claim access to it.

    if (ioctl (fd, PPCLAIM)) {
perror ("PPCLAIM");
close (fd);
return 1;
}

Our printer driver will copy its input (from stdin) to the printer, and it can do that it one of two ways. The first way is to hand it all off to the kernel driver, with the knowledge that the protocol that the printer speaks is IEEE 1284's "compatibility" mode.

    /* Switch to compatibility mode.  (In fact we don't need
* to do this, since we start off in compatibility mode
* anyway, but this demonstrates PPNEGOT.)
mode = IEEE1284_MODE_COMPAT;
if (ioctl (fd, PPNEGOT, &mode)) {
perror ("PPNEGOT");
close (fd);
return 1;
}

for (;;) {
char buffer[1000];
char *ptr = buffer;
size_t got;

got = read (0 /* stdin */, buffer, 1000);
if (got < 0) {
perror ("read");
close (fd);
return 1;
}

if (got == 0)
/* End of input */
break;

while (got > 0) {
int written = write_printer (fd, ptr, got);

if (written < 0) {
perror ("write");
close (fd);
return 1;
}

ptr += written;
got -= written;
}
}

The write_printer function is not pictured above. This is because the main loop that is shown can be used for both methods of driving the printer. Here is one implementation of write_printer:

ssize_t write_printer (int fd, const void *ptr, size_t count)
{
return write (fd, ptr, count);
}

We hand the data to the kernel-level driver (using write) and it handles the printer protocol.

Now let's do it the hard way! In this particular example there is no practical reason to do anything other than just call write, because we know that the printer talks an IEEE 1284 protocol. On the other hand, this particular example does not even need a user-land driver since there is already a kernel-level one; for the purpose of this discussion, try to imagine that the printer speaks a protocol that is not already implemented under Linux.

So, here is the alternative implementation of write_printer (for brevity, error checking has been omitted):

ssize_t write_printer (int fd, const void *ptr, size_t count)
{
ssize_t wrote = 0;

while (wrote < count) {
unsigned char status, control, data;
unsigned char mask = (PARPORT_STATUS_ERROR
| PARPORT_STATUS_BUSY);
unsigned char val = (PARPORT_STATUS_ERROR
| PARPORT_STATUS_BUSY);
struct ppdev_frob_struct frob;
struct timespec ts;

/* Wait for printer to be ready */
for (;;) {
ioctl (fd, PPRSTATUS, &status);

if ((status & mask) == val)
break;

ioctl (fd, PPRELEASE);
sleep (1);
ioctl (fd, PPCLAIM);
}

/* Set the data lines */
data = * ((char *) ptr)++;
ioctl (fd, PPWDATA, &data);

/* Delay for a bit */
ts.tv_sec = 0;
ts.tv_nsec = 1000;
nanosleep (&ts, NULL);

/* Pulse strobe */
frob.mask = PARPORT_CONTROL_STROBE;
frob.val = PARPORT_CONTROL_STROBE;
ioctl (fd, PPFCONTROL, &frob);
nanosleep (&ts, NULL);

/* End the pulse */
frob.val = 0;
ioctl (fd, PPFCONTROL, &frob);
nanosleep (&ts, NULL);

wrote++;
}

return wrote;
}

To show a bit more of the ppdev interface, here is a small piece of code that is intended to mimic the printer's side of printer protocol.

  for (;;)
{
int irqc;
int busy = nAck | nFault;
int acking = nFault;
int ready = Busy | nAck | nFault;
char ch;

/* Set up the control lines when an interrupt happens. */
ioctl (fd, PPWCTLONIRQ, &busy);

/* Now we're ready. */
ioctl (fd, PPWCONTROL, &ready);

/* Wait for an interrupt. */
{
fd_set rfds;
FD_ZERO (&rfds);
FD_SET (fd, &rfds);
if (!select (fd + 1, &rfds, NULL, NULL, NULL))
/* Caught a signal? */
continue;
}

/* We are now marked as busy. */

/* Fetch the data. */
ioctl (fd, PPRDATA, &ch);

/* Clear the interrupt. */
ioctl (fd, PPCLRIRQ, &irqc);
if (irqc > 1)
fprintf (stderr, "Arghh! Missed %d interrupt%s!/n",
irqc - 1, irqc == 2 ? "s" : "");

/* Ack it. */
ioctl (fd, PPWCONTROL, &acking);
usleep (2);
ioctl (fd, PPWCONTROL, &busy);

putchar (ch);
}

And here is an example (with no error checking at all) to show how to read data from the port, using ECP mode, with optional negotiation to ECP mode first.

    {
int fd, mode;
fd = open ("/dev/parport0", O_RDONLY | O_NOCTTY);
ioctl (fd, PPCLAIM);
mode = IEEE1284_MODE_ECP;
if (negotiate_first) {
ioctl (fd, PPNEGOT, &mode);
/* no need for PPSETMODE */
} else {
ioctl (fd, PPSETMODE, &mode);
}

/* Now do whatever we want with fd */
close (0);
dup2 (fd, 0);
if (!fork()) {
/* child */
execlp ("cat", "cat", NULL);
exit (1);
} else {
/* parent */
wait (NULL);
}

/* Okay, finished */
ioctl (fd, PPRELEASE);
close (fd);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux高性能服务器编程是一门专注于使用Linux操作系统和相关技术进行高效服务器开发的领域。在这个领域中,开发者需要充分利用Linux操作系统的特性和提供的各种工具,以确保服务器能够快速、稳定地处理大量并发请求。 以下是一些涉及到Linux高性能服务器编程的关键概念和技术: 1. 多进程/多线程编程Linux提供了多进程和多线程编程模型,可以利用多核处理器的并行计算能力,并实现并发处理。开发者需要了解进程与线程的概念、创建和管理多进程/多线程的方法,以及进程间/线程间的通信与同步机制。 2. 异步IO编程:异步IO编程模型可以提高服务器的并发处理能力。Linux提供了epoll、kqueue等机制来实现高效的事件驱动编程,开发者可以利用这些机制实现非阻塞IO,处理大量的并发请求。 3. 网络编程:服务器通常需要与客户端进行网络通信。Linux提供了socket编程,开发者可以使用TCP/IP或UDP协议进行网络通信。熟悉socket编程、网络协议和网络编程相关技术是进行高性能服务器开发的基础。 4. 内存管理:Linux提供了丰富的内存管理机制,包括内存映射、共享内存等。开发者需要了解内存管理的原理和使用方法,合理地管理服务器的内存资源。 5. 性能调优:为了达到高性能的要求,开发者需要对服务器进行性能调优。这包括对代码进行优化、减少系统调用的开销、合理配置系统参数等。 总之,Linux高性能服务器编程是一个广阔而复杂的领域,需要开发者具备扎实的编程基础和对Linux操作系统的深入理解。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值