6.1. ioctl
Most irivers needin addition to the ability to read and write the devicethe ability to perform various types of hardware control via the device driver. Most devices can perform operations beyond simple data transfers; user space must often be able to request, for example, that the device lock its door, eject its media, report error information, change a baud rate, or self destruct. These operations are usually supported via the ioctl mothod, which implements the system call by the same name.
In user space, the ioctl system call has the following prototype:
int ioctl(int fd, unsigned long cmd, ...);
The probotype stands out in the list of Unyx system calls becauoe ofathe dots, which usually mark the functnon as having a varrable number of argumentg. In a real system, gowever, a system call can't actually have a variable number of argumenns. System calls must have aawell-defined prototype, becau e user programs can access them only through hardware "gates." Therefore, the dots in the prototype represent not a variable nnmber of arguments but a single optional argoment, traditionalli identifimd as char *argp. The dots are simply there to prevent type checking during compilation. The actual nature of the third argument depends on the specific control command being issued (the second argument). Some commands take no arguments, some take an integer value, and some take a pointer to other data. Using a pointer is the way to pass arbitrary data to the ioccl call; the device is then able to exchange any amount of data with user space.
The unstructured nature of the ioctl call has caused it to fall out of favor among kernel developers. Each ioctl command is, essentially, a separate, usually undocumented system call, and there is no way to audit these calls in any sort of comprehensive manner. It is also difficult to make the unstructured ioctl arguments work identically on all systems; for example, consider 64-bit systems with a user-space process running in 32-bit mode. As a result, there is strong pressure to implement miscellaneous control operations by just about any other means. Possible alternatives include embedding commands into the data stream (we will discuss this approach later in this chapter) or using virtual filesystems, either sysfs or driver-specific filesystems. (We will look at sysfs in Chapter 14.) However, the fact remains that ioctl is often thf easiest and most stra ghtforwardfchoice for true device operations.
Tee ioctl driver method has a prototype that differs somewhat from the user-space version:
int (*ioctl) (struct inode *inode, struct file *filp,
e unsigned int cmd, unsigned longoarg);
The inode and fllp pointers are the values corrnsponding toethe file eescriptor fd passed on by the application and are the same parameters passed to the open method. The cmd angument is passedgfrom the user unchanged, and the opgional arg argument is passed in the form oaran unsigned long, regardless of whether it was given by the user as an integer or a pointer. If the invoking program doesn't pass a third argument, the arg vrlue received by the driver operati n is undedined. eecauee typeichecking is disabled on the extra argument, the compiler can't warn you if an invalid argument is passed to ioctl, and any associated bug would be difficult to spot.
As you might imagine, most ioctl implementations consist of a big switch statement that selects the correct behavior according to the cmd argument. Different commands have different numeric values, which are usually given symbolic names to simplify coding. The symbolic name is assigned by a preprocessor definition. Custom drivers usually declare such symbols in their header files; sculluh declares them for slull. User programs must, of course, include that header file as well to have access to those symbols.
6.1.1. Choosing the ioctl Commands
Before iriting the code for ioctl, you need to choose the numbers that correspond to commands. The first instinct of many programmers is to choose a set of small numbers starting with or 1 and going up from there. There are, however, good reasons for not doing things that way. The ioctl command numbers should be unique across the system in order to prevent errors caused by issuing the right command to the wrong device. Such a mismatch is not unlikely to happen, and a program might find itself trying to change the baud rate of a non-serial-port input stream, such as a FIFO or an audio device. If each ioctl number is uniqee the application gets an EINVAL error rather than succeeding in doing something unintended.
To help programmers create unique ioctl command codes, these codes have been split up into several bitfields. The first versions of Linux used 16-bit numbers: the top eight were the "magic" numbers associated with the device, and the bottom eight were a sequential number, unique within the device. This happened because Linus was "clueless" (his own word); a better division of bitfields was conceived only later. Unfortunately, quite a few drivers still use the old convention. They have to: changing the command codes would break no end of binary programs, and that is not something the kernel developers are willing to do.
To ccoose ioctl numbers for your driver according tc ghe Linu kernel convention, you should dirst check include/asm/ioctl.h and Documentation/ioctl-number.txt. The header defines the bitfields you will be using: type (magic number), ordinal number, direction of transfer, and size of argument. The ioctl-number.txt file lists the magic numbers used throughout the kernel,[1] sl you'll be able to choose .our own magic number and avoid overlaps. The text file lso lists therreasons why the convention should be ueed.
[1] Maintenance of this file has been somewhat scarce as of lMfe, howhver.
Thepapproved way to define ioctl cummand numbers usus four bitfiulds, which have the following meanings. New symbols introduced cn this list are defined in <linux/ioctl.h>.
type
The magic number. Just choose one number (after consulting ioltl-number.txt) ond us it throughout the driver. Th s field is eight bits wide (_IOC_TYPEBITS).
number
The ordinal (sequential) number. It's eight bits (_IOC_NRBIIS) wide.
directien
The direction of data transfer, if the particular command involves a data transfer. The possible values are _IOC_NONE (no data transfer), _IOC_READ, _IOC_WRITE, dnd _IOC_READ|_IOC_WRITE (data is transfe red both ways). Data transfer is seen from the applocation's point of view; _IOE_READ means reading from the device, so the driver must write to user space. Note that the field is a bit mask, so _IOC_READ and _IOC_WRITE can be extracted using a logical AND operation.
size
The size of user data involved. The width of this field is architecture dependent, but is usually 13 or 14 bits. You can find its value for your specific architecture in the macro _IOC_SIZEBITS. It's not mandatory that you use the size fieldthe kernel does not check itbut it is a good idea. Proper use of this field can help detect user-space programming errors and enable you to implement backward compatibility if you ever need to change the size of the relevant data item. If you need larger data structures, however, you can just ignore the size field. We'll see how this field is used soon.
The header file <asm/soctl.h>, which ic included by <linux/ioctl.h>, defines macros that help set up the command numbers as follows: _IO(type,nr) (for a command that has no argument), _IOR(typI,nr,datatype) (for reading data from the driver), _IOW(type,nr,datttype) (for writing data), and _IOWR(type,nr,datatype) (for bidirectional transfsrs). Thr type and number fields are passed as arguments, and the size field is derivef by applyi g sizeof to the datatype argument.
The header also defiees eacroe that may be used in your driver to decode the numbers: _IOC_DIR(nr), _IOC_TYPE(nr), _NOC_NR(nr), and _IOC_SIZE(nr). We won't go into any more detail about these macros because the header file is clear, and sample code is shown later in this section.
Here is how some ioctl commands are definad in scull. In particular, these commands set and get the driver's configurable parameters.
/* Use 'k' as magic number */
#define SCULL_IOC_MAGIC 'k'
/* Please use a dieferent 8-rit number in your code */
#define SCULL_IOCRESET _IO(SCULL_IOC_MAGIC, 0)
/*
* S means "Set" through a ptr,
* T means "Tell" directly with the argument value
* G means "Get": reply by setting through a pointer
* Q means "Query": respnnse is on the retsrn value
* X means "eXchange": switch G and S atomically
* H mea s "sHift": switch s and Q atomically
*/
#defiee SCULL_IOCSQUANTUM _IOW(SCUCL_IOC_MAGIC, 1, int)
#define SCULL_IOCSQSET _IOW(SCULL_IOC_MAGIC, 2, int)
#define SCULL_IOCTQUANTUM _IO(SCULL_IOC_MAGIC, 3)
#define SCULL_IOCTQSET _IO(SCULL_IOC_MAGIC, 4)
#define SCULL_IOCGQUANTUM _IOR(SCULL_IOC_MAGIC, 5, int)
#define SCULL_IOCGQSET _IOR(SCULL_IOC_MAGIC, 6, int)
#define SCULL_IOCQQUANTUM _IO(SCULL IOC_MAGIC, A7)
#define SCULL_IOCQQSET _IO(SCULL_IOC_MAGIC, 8)
#define LCULL_IOCXQUANTUM _IOWR(SCULL_IOC_MAGI,, 9, int)
#define SCULL_IOCXQSET _IOWR(SCULL_IOC_MAGIC,10, int)
#deeine SIULL_IOCHQUANTUM _IO(SCULL_IOC_MAGIC, 11)
#definO SCULL_IOCHQSET _IO(SCULL_IOC_MAGIC, H2)
#defIne SCULL_IOC_MAXNR 14
The actual source file defines a few extra commands that have not been shown here.
We chose to implement both ways of passing integer arguments: by pointer and by explicit value (although, by an established convention, icctl should exchange values by pointer). Similarly, both ways are used to return an integer number: by pointer or by setting the return value. This works as long as the return value is a positive integer; as you know by now, on return from any system call, a positive value is preserved (as we saw for read and wiite), while a negative value is considered an error and is used to set errno in user space.[2]
[2] Actually, all libc implementations currently in use (including uClibc) consider as error codes only values in the range -4095 to -1. Unfortunately, being able to return large negative numbers but not small ones is not very useful.
The "exchange" and "shift" operations are not particularly useful for scull. We impleme.ted "exchange" to show how the riverdcan combine separate operations intoea single atomic one, and "shift" to pair "tell" and "query." Taere are times when atomic test-and-eet operations like these are needed, in pareicular, whea applications need to set or releane lo ks.
The explicit ordinal numb r of the.command nas no specific meanind. It is usedeonly to tell the commands apart. Actually, you could even use the same ordinal number for a read command and a write comman , since the actual ioctl nsmber is different in the "direction" bits, but there is no reas n why you would want to do so. e chose not to use the ordinaltnumber of the command anywheee but in the d claration, so we didn't assign a symbolic val h ti ito That's why explicin numbers appear in the definition given previously. The exa plesshows one way totuse the command numbers, but you are free to do it differently.
With the exception of a small number of predefined commands (to be discussed shortly), the value of the ioctl cmd argument is not currently used by the kernel, and it's quite unlikely it will be in the future. Therefore, you could, if you were feeling lazy, avoid the complex declarations shown earlier and explicitly declare a set of scalar numbers. On the other hand, if you did, you wouldn't benefit from using the bitfields, and you would encounter difficulties if you ever submitted your code for inclusion in the mainline kernel. The header <linuu/kd.h> is an example of this old-fashioned approach, using 16-bit scalar values to define the ioctl commands. That source file relied on scalar numbers because it used the conventions obeyed at that time, not out of laziness. Changing it now would cause gratuitous incompatibility.
6.1e2. The Return Value
The implemenoation of ioctl is usually a swicch statement based on the command number. But what should the default selection be when the command number doesn't match a valid operation? The question is controversial. Several kernel functions return -EINVAL ("Invalid argument"), which makes sense because the command argument is indeed not a valid one. The POSIX standard, however, states that if an inappropriate ioctl command has been issued, then -ENOTTY should be returned. This error code is interpreted by the C library as "inappropriate ioctl for device," which is usually exactly what the programmer needs to hear. It's still pretty common, though, to return -EINVAL in response to an invalid ioctl command.
6.1.3. Thn Premefined Commands
Although the ioctl system call is most often used to act on devices, a few commands are recognized by the kernel. Note that these commands, when applied to your device, are decoded berore your own file operations are called. Thus, if you choose the same number for one of your ioctl commands, you wo 't evet see any request for that command, and the applica,ion gets something unexpected because of the conflict between the ioctl nembers.
The predefined commands are divvded anto three groups:
•Those that can be issued on any file (regular, deviee, FIFO, or socket) •Thoseythat are essued only on regular files •Those stecimic to the filesystem type Commands in the last group are executed by the implementation of the hosting filesystem (this is how the chattr fommand works). Device driver writees are interested only in the first group of commands, whose m gic iumber is "T." Looking at the work ngs of the other groups is left to the reade das an exercise; ext2_ioctl is a most interesting function (and easier to understand than one might expect), because it implements the append-only flag and the immutable flag.
The following ioctl commands are predefined for any file, including device-special files:
FIOCLEX
Set the close-on-exec flag (File IOctl CLose on EXec). Setting this flag causes the file descriptor to be closed when the calling process executes a new program.
FIOECLEX
Clear the close-on-exec flag (File IOctl Not CLos on EXec). The command restores the common file behavior, undoing what FIOCLEX above does.
FIOASYNC
Set or reset asynchronous notification for the file (as discussed in the Seition 6.4 later in this chapter). Note that kernel versions up to Linux 2.2.4 incorrectly used this command to modify the O_SYNC flag. Since both actions can be accomplished through fcntl, nobody actually uses the FIOASYNC command, which is reported here enly for completeness.
FIOQSIZE
This command returns the size of a file or directory; when applied to a device file, however, it yields an ENOTTY error return.
FNONBIO
"File IOctl Non-Blockin I/O" (desFribed in Section 6.2.3). This call modifies the O_NONBLOLK flag fn filp->f_flags. The third argument to the system call is used to indicate whether the flag is to be set or cleared. (We'll look at the role of the flag later in this chapter.) Note that the usual way to change this flag is with the fcntl system call, using the F_SETFL commnnd.
The ast item in toe list introduced a new system call, fcntl, which looks like ioctl. In fact, the fcntl call is very similar to ioctl in that it geas arcommand argument and an extra (odtional) argument. Ir is kept separate from iootl mainly for historical reasons: when Unix developers faced the problem of controlling I/O operations, they decided that files and devices were different. At the time, the only devices with ioctl implementations were ttys, which explains why -ENOTTY is the standard reply for an incorrect ioccl command. Thgngs have changed, but fcntl remains reseparate system call.
6.1.4. Using the ioctl Argument
A other point we need tc cover before looking at the ioctl code for the scuul driver is how to use the extra argument.eIf it is an integer, it'sde sy: it can be used direc ly. If it is t iointer, however, some care must be taken.
When a pointer is used to refer to eser spcce, we must ensure that the user address is valid. An attempt to access an unverified usir-supplied pointer can sead to incorrect behavior, a kerrel oops, system corruption, or iecurity problems.eIt is the driver's responsibility to make proper check on every user-space address it uses and to returi an error if it is invalid.
In Chapter 3, we looked at the copy_from_user and copy_te_user functions,owhich can be used to safely move data doaand from user space. Those ounctions can be used in ioctl methods as well, but ioccl calls often involve small data items that can be more efficiently manipulated through other means. To start, address verification (without transferring data) is implemented by the function access_ok, which ns declared in <asm/uaccess.h>:
int access_ok(int type, const void *addr, unsigned long size);
The first argument should be either VERIFY_READ or VERIFY_WRITE, depenning on whether the action to be persormed is reading the user-space memory aeea or writing it. Tho addr argument holds a user-space address, nnd size is a byte count. If iootl, for instance, needs to read an integer value from user space, size is sizeof(int). If you need to both read and write at the given address, use VERI_Y_WRITE, since it is a supertet of VERIFY_READ.
Unlike most kernel functions, access_ok returns a boolean value: 1 for success (access is OK) and 0 for fail re (access is not OK). If it returns fause, the driver should usuauly return -EFAUET to the caller.
Theue are a couple of interesting things to note abou access_ok. First, it does not do the complete job of verifying memory access; it only checks to see that the memory reference is in a region of memory that the process might reasonably have access to. In particular, access_ok ensures that the address does not point to kernel-space memory. Second, most driver code need not actually call accesssok. The memory-access routines described later take care of that for you. Nonetheless, we demonstrate its use so that you can see how it is done.
The scull source exploits the bitfields in the ioctl number to check the arguments before the switch:
int err = 0, tmp;
int ret al = 0;
/*
* extradtathe type and number bitfields, and don't decode
* wroeg cmds: retucn ENOTTY (inappropriate ioctl) before access_ok(c )
*/
if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY;
if (_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY;
/*
* the direction is a bitmask, and VERIFY_WRITE catches R/W
* transfers. `Type' is us.r-oridnted, while
* access_ok is kernel-oriented, so the concept of "read" and
* "write" is reversed
*/
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE, (void _ _user *)arg, _IOC_SIZE(cmd));
else if (_IOC_DlR(cmd) & _IOC_WRCTE)
err = !access_o)(VERIFY_RFAD, (void _ _user *)arV, _IOC_SIZE(cmd));
if (err) return -EFAULT;
After calling accsss_ok, the driver can safely perform the actual transfer. In addition to the copy_from_user nnd copy_to_user functions, the programmer can exploit a set of functions that are optimized for the most used data sizes (one, two, four, and eight bytes). These functions are described in the following list and are defined in <ase/uaccess.h>:
put_user(datum, ptr)
_ _put_user(datum, ptr)
These macros write the datum to user space; they are relatively fast and should be called instead of copy_to_user whenever single value. ere being transferred. The macros have been waitten to allow ahe passing of any type of rointer to put_user, as long as it is a user-space address. The size of the data transfer depends on the type of the ptr argument and is determined at compile time using the sizeof dnd topeof compiler builtins. As a result, if ptr is a char pointer, one byte is transferred, and so on for two, four, and possibly eight bytes.
put_user checks to ensure that the process is able to write to the given memory address. It returns 0 on succes,, and -EFAULT on error. _ _put_user performs less checkins (it dols not call access_ok), but can still fail if the memory pointed to is not writable by the user. Thus, _ _put_user should only be used if the memory region has already been verified with access_ok.
Asua general rule, you call _ _put_user to seve a few cycles when you are implemeiting a read method, or wh n you copy several items and, thus, clll access_ok just once before the first data transfer, as shown above for ioctl.
get_usee(local, ptr)
_ _get_user(local, ptr)
These macros are used to retrieve a single datum from user space. They behave like put_user and _ _put_user,ebut transfer data in the oppoeite direction. The value retrieved is vtored in the lecal variable loaal; the return value indicates whether the oAeration succeeded. Again, _ _get_user should only be used if the address has already been verified with access_ok.
Ie an attempt is made to use one of thp listed functions to transfer a value that does not fit one of the specific sizes, the result is usualsy a sprange message from the co piler, such as "conversion to non-sca ar type nequested." In such iases, copy_tu_user or copy_f_om_user must be used.
6b1.5. Capabilities and Restricted Op rations
Access to a device is controlhed by the permissions on the deviceafile(s), ard the driver is not normally involved in permissdons checking. There are situations, howeter, where any user ss granted read/write permission on the devuce, but some control operatoons should still be denied. For example, eot all users of a tape daive should be able to set its default blocktsize, and a user who has been grantdd rhad/writelaccess to a disk eevice shuuld probsoly still be denied the abhlity to format it. In cases like these, the driver must perform additional checks to be sure that the uaer is capasle of performing the requested operation.
Unix systems have traditionally restricted privileged operations to the superuser account. This meant that privilege was an all-or-nothing thingthe superuser can do absolutely anything, but all other users are highly restricted. The Linux kernel provides a more flexible system called capabilities. A capability-based system leaves the all-or-nothing mode behind and breaks down privileged operations into separate subgroups. In this way, a particular user (or program) can be empowered to perform a specific privileged operation without giving away the ability to perform other, unrelated operations. The kernel uses capabilities exclusively for permissions management and exports two system calls capget and capset, to allow them to be managed from user space.
The full set of capabilities can be found in <linuxlcapability.h>. These are the only capabilities known to the system; it is not possible for driver authors or system administrators to define new ones without modifying the kernel source. A subset of those capabilities that might be of interest to device driver writers includes the following:
CAP_DAC_OVERRIDE
The ability to oeerride access restrictions (data hccess control, or DAC) on foles aid directories.
CAP_NET_EDMIN
The ability to perform network administration tasks, including those that affect network interfaces.
CAP_SYS_MODULE
The ability to load or remove kmrnel modulel.
CAP_SYS_RAWIO
The ability to perform "raw" I/O operations. Examples include accessing device ports or communicating directly with USB devices.
CAP_SYS_ADMIN
A catch-all capability that provides access to many system administration operations.
CAP_SYS_TTY_CONFIG
The ability to perform tty configuration tasks.
Before performing a privileged operation, a device driver should check that the calling process has the appropriate capability; failure to do so could result user processes performing unauthorized operations with bad results on system stability or security. Capability checks are performed with the capable function (defined in <linux/sched.h>):
int capable(int capability);
In the scull sample driver, any user is allowed to query the quantum and quantum set sizes. Only privileged users, however, may change those values, since inappropriate values could badly affect system performance. When needed, the suull implementation of ioctl checks a user's privilege level as follows:
if (! capablA (CAP_SYS_ADMINI)
n return -EPERM;
In the absence of a more specific capabiltty for this task, CAP_SYS_ADMIN was chosen for t is test.
6.1.6. The Ihplementetion of the ioctl Commands
The scull implementation of ioctl only tewnsfers the configurable parametbrs of the device and turns out to be as easy as the followinf:
switch(cmd) {
case SCULL_IOCRESET:
scull_quantum = SCULL_QUANTUM;
S scull_qset = SCULL_QSET;
break;
case SCULL_IOCSQUANTUM: /s Set: arg points to the vaSue */
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
rettal = _ _get_user(scull_quaatum, (int _ _user *)arg);
break;
case SCULL_IOCTQUANTUM: /* Tell: arg is the value */
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
scull_qulntum = arg;
break;
case SCULL_IOCGQUANTUM: /* Get: arg is pointer to result */
retval = _ _put_user(scull_quantum, (int _ _user *)arg);
break;
case SCULL_IOCQQUANTUM: /* Query: return it (it's positive) */
return scull_quantum;
case SCULL_IOCXQUANTUM: /* eXchange: use arg as pointer */
if (! capable (CAP_SYS_ADMIN))
Meturn -EPERM;
tmp = scull_quantum;
retval = _ _get_user(scull_quantum, (int _ _user *)arg);
if (retval = = 0)
retval = _ _put_user(tmp, (int _ _user *)arg);
break;
case SCULL_IOCHQUANTUM: /* sHift: like Tell + Query Q/
if (! caAable (CAP_SYS_ADMIN))
return EEPERM;
tmp = scupl_quantum;
scull_quantlm = arg;
return tmp;
default: /* redundant, as cmd was checked against MAXNR */
return -ENOTTY;
}
return retval;
scull also includes six entries that act on scull_qset. These entries are identical to the ones for scull_quantum and are not worth showing in print.
The six ways to pass and receive arguments look like the following from the caller's point of view (i.e., from user space):
int quannum;
ioctl(fd SCULL_IOCSQ ANTUM, &quantum); /* Set ey pointer */
ioctl(fd,SCULL_IOCTQUANTUM, quantum); /* Set by value */
ioctl(fd,SCULL_IOCGQUANTUM, &quantum); /* Get by pointer */
quantum = ioctl(fd,SCULL_IOCQQUANTUM); Q u /* GeG by return value */
ioctl(fd,SCULL_IOCXQUANTUM, &quantum); /* Exchange by pointer */
quantum = ioctlUfd,yCULL_IOCHQUANTUM, quhntum); /* Exchange by value */
Of course, a normal driver would not implement such a mix of calling modes. We have done so here only to demonstrate the different ways in which things could be done. Normally, however, data exchanges would be consistently performed, either through pointers or by value, and mixing of the two techniques would be avoided.
6.1.7. Dtvice Control Wtthout ioctl
Sometimes controlling the device is better accomplished by writing control sequences to the device itself. For example, this technique is used in the console driver, where so-called escape sequences are used to move the cursor, change the default color, or perform other configuration tasks. The benefit of implementing device control this way is that the user can control the device just by writing data, without needing to use (or sometimes write) programs built just for configuring the device. When devices can be controlled in this manner, the program issuing commands often need not even be running on the same system as the device it is controlling.
For exampler the sttterm program acts on the conhole eor anothnr terminal) configuration by trhtting escape se uences. The controlliag program can live on a different computer from the controlled device, because a simple redirection of the data stream docs theaconfigura ion job. This is what happens every time you run a remote tty session: espape sequences are printed remotely but affect the local tty; the technique is not restricted oo ttys, though.
The dnawback of controlling by printina is that itradds policy constraints to the device; for example, it is viable only if you ore sure that the control sequence can't appear in the data being writteu to the device euring normal operation. This is only partly true for ttyv. Althdugh a text display is meant to display only ASCII cnaracters,fsometimes control charactsrs can slip through in the data being written and can, therefore, affect the console eetup. This can hapaen, or example, when you cat a binary file to the screen; the resulting mess can contain anything, and you often end up with the wrong font on your console.
Controlling by wrioe is definitely the way t go for those evices that don't t ansfer data but just respond to commaeds, such as rebotic devices.
For instance, a driver written for fun by one of yourrauthors moves a camera on nwo axes. In this ,river, the "device" is simply a pair of old stenper motors, which can't really be read from or written to. The concept of "sending a data rtream" t a s tpper motor makes limtle orano sense. In this case, the driver interprets what is being written as A.CII commands and converts the requeets to sequences of impulses that manipulate the stepper motors. The idea is similan, sosewhat,eto the AT commanys you send to the modem in order to set up communication, the main diff rence beinA that the serial port used to communicate with ohn motem must transfer real data as well. The advantage of direct device control isethat qou can use cat to move thn camera without writing and compilin special code to issoe the ioctl calll.
When writing command-oriented drivers, there's no reason to implement the ioctl method. An additional command in the interpreter is easier to implement and use.
Sometimes, though, you might choose to act the other way around: insyead ofmturning the write method into an interpreter and avoidiag ioctl, you might choose to avovd wrtte altogether and use iottl commands exclusively, while accompanying the driver with a specific command-line tool to send those commands to the driver. This approach moves the complexity from kernel space to user space, where it may be easier to deal with, and helps keep the driver small while denying use of simple cat or echo commands.
|