This document provides a set of requirements that future versions of Linux should meet. These requirements are minimal, i.e. the least amount of changes that will allow third parties to write adapters. On the other side of the interface, an adapter that meets this spec can be plugged into future versions of Linux, modifying the computer in a manner that supports a particular disability.
1. Kernel Configuration.
1.1. Make config shall prompt the user for "Accessibility adapters", excepting [ny?] as response. If y is entered, CONFIG_ACCESSIBILITY shall be defined in linux/config.h, or some other header file included by that file. If ? is entered, make config shall describe the option. The following explanation might be presented.
An adapter is a peripheral device and/or a specialized device driver that makes Linux accessible to a disabled user. Some adapters redirect output to a speech synthesizer or Braille device for the blind. Other adapters modify keyboard input for the motor impaired. In any case, the adapter must somehow intercept keyboard input and tty output. These streams are generally hidden deep within the kernel. If you say y here, kernel modules will have access to keyboard input and tty output. Adapters can then be loaded as modules, without patching and rebuilding the kernel. The effect on kernel size and performance is negligible, so you should probably say y here.
1.2. The changes within the Linux kernel that support this capability shall be enclosed in #ifdef CONFIG_ACCESSIBILITY #endif. Thus if the user says no to adapters, the compiled kernel runs as it does today. This may be desirable when performance or size is critical, or when an instance of Linux essentially has no users.
1.3. Adapters shipped with the distribution shall be offered to the user iff CONFIG_ACCESSIBILITY is set. Make config might prompt for "The Jupiter Speech System", and set CONFIG_JUPITER to n m or y, according to the user's response. CONFIG_SPEAKUP is set if the user wants the Speakup adapter, and so on. Adapters can be built as modules (if loadable modules are enabled) or as components of the compiled kernel.
1.4. The source directory drivers/accessibility shall house the various adapters for Linux. This is a parallel to drivers/sound and drivers/net, which hold drivers for sound cards and ethernet cards respectively. Each adapter shall be housed in a subdirectory under drivers/accessibility. Examples include drivers/accessibility/jupiter and drivers/accessibility/speakup.
1.5. The standard make directives, e.g. make bzImage and make modules, shall build the adapters as modules or as object files within the kernel, according to the configuration.
1.6. The config options offered by an adapter shall not be constrained, except to say that they are offered only if that adapter is selected. These config options might establish the synthesizer, serial port, io port, etc. They are not necessary, and not recommended when the adapter is built as a module. Module parameters can be used to specify these options. Even when the adapter is built in, you may want to leave these options as boot parameters. For example, an inbuilt instance of jupiter might be activated by the boot parameter jupiter=double-lt,0,9600.
2. The interface.
2.1. The header file include/linux/accessibility.h shall establish the interface between the kernel and the adapters. It shall define a structure of accessibility operations, similar to file operations or inode operations. It shall also define an accessibility device structure, similar to a device driver.
2.2 The accessibility operations structure shall appear as follows. The individual functions will be described later.
struct accessibility_operations
{
void (*tty_start) (struct accessibility_device *dev,
int minor);
void (*tty_stop) (struct accessibility_device *dev,
int minor);
int (*tty_out) (struct accessibility_device *dev,
int minor, unsigned char c, int echo);
int (*keystroke) (struct accessibility_device *dev,
unsigned char *key_p, int *shiftstate_p, char up_flag);
void (*scroll) (struct accessibility_device *dev,
int minor, int t, int b, int nr);
void (*cursorloc) (struct accessibility_device *dev,
int minor, int x, int y);
void (*polling) (struct accessibility_device *dev);
int period;
};
2.3 The accessibility device structure shall appear as follows.
struct accessibility_device
{
list_head link; /* support multiple adapters */
struct accessibility_operations *ops;
struct proc_dir_entry *proc;
struct pci_device *dev;
struct timer_list polltime;
int pollrunning;
void *data;
};
3. Registering the adapter.
3.1. The header file accessibility.h shall declare the prototypes:
int register_accessibility_device(struct accessibility_device *dev);
int unregister_accessibility_device(struct accessibility_device *dev);
3.2. After register_accessibility_device() is called, the kernel shall invoke the functions indicated by dev->ops as appropriate. (Null pointers represent missing functions, and are not called.) These functions are supplied by the adapter, and will be described later. If register_accessibility_device() is not called, or returns an error code, the kernel shall not call any functions in dev->ops. Instead it shall operate as it did before - as though there were no adapter.
3.3. Multiple adapters can run simultaneously. They are arranged in a stack. The last adapter installed is the first one invoked. A keystroke, for example, shall be passed to each adapter in turn, unless one of the adapters indicates it has acted upon that key, performing a specialized function, whence the keystroke is not passed to any other adapters, or to the operating system. This will become clearer as we explore the functions in the adapter. For now, let's just say the kernel shall do the right thing with regard to multiple adapters.
3.4. Unregister_accessibility_device() shall unregister the designated adapter, restoring the kernel to its original behavior. Do not unregister a device that has not been successfully registered.
3.5. The simplest adapter modules will call register_accessibility_device() from init_module(), passing any error codes back to the insmod utility. Similarly, the module will call unregister_accessibility_device() from cleanup_module(). This is not a requirement, but it is a reasonable design.
3.6.
If an adapter is built into the kernel,
it shall declare an init level function such as
int __init jupiter_setup(char *parms) { ... }
that performs the same functionality as init_module() above.
Among other things, adapter_setup() will probably call register_accessibility_device(),
just as init_module() did when the adapter was a loadable module.
If the built-in adapter fails to do this,
or decides to unregister itself for any reason,
the kernel is free to install other adapters as loadable modules.
If several adapters are built in,
each setup routine might query a boot parameter to see which one should be enabled,
i.e. which one should call register_accessibility_device().
This boot parameter may also designate the synthesizer, serial port, etc.
3.7. If an adapter is compiled into the kernel, the kernel shall call its setup function as part of its initialization sequence. Many other source files have similar __setup functions. There is no guarantee on the order in which these __setup functions are called. Your adapter may be first, or last in the list.
4. Files under /proc.
4.1. The kernel shall create the directory /proc/accessibility. The global variable proc_accessibility, declared in proc_fs.h, points to the corresponding proc_dir_entry. Thus each adapter can create files beneath /proc/accessibility. Remember to verify that the pointer is not null, in case the creation of the directory fails. Adapters are encouraged to make subdirectories beneath /proc/accessibility, since multiple adapters may run simultaneously.
4.2. Adapters can create various files under /proc/accessibility/adapter, as they see fit. For example, Jupiter creates readonly files that contain the session logs for the virtual terminals. Writeonly files copy firmware and dictionaries down to speech synthesizers. This is entirely up to the adapter.
5. Tty resources.
5.1. Most adapters allocate resources for each virtual terminal. The kernel allows this allocation to be dynamic. Whenever a console tty driver is created, the kernel shall call ops->tty_start(minor), passing the minor number of the tty device. If the tty driver is destroyed, because there are no processes associated with it, the kernel shall call ops->tty_stop(minor). This allows the adapter to deallocate its resources.
5.2. When register_accessibility_device() is invoked, it shall call ops->tty_start() for each active tty device. Similarly, unregister_accessibility_device() shall call ops->tty_stop() for each open tty. Obviously these tty devices aren't going away, but the adapter is, and calling tty_stop() for each device is a consistent way to clean things up.
5.3. Developers may want to think carefully before deallocating resources inside tty_stop(). Under Redhat, the first tty driver, tty1, is opened to run rc.sysinit, then closed. Then all the virtual terminals are opened as you enter a higher run level. If you deallocate resources when tty1 closes, there will be no log of the output of rc_sysinit. This is probably not what you want. Some adapters allocate and initialize via tty_start(), but basically ignore tty_stop(). They then deallocate everything they know about in cleanup_module().
6. Console output.
6.1. Each character generated by a tty driver shall be sent to ops->tty_out(), before it is sent to the console. The minor device number, output character, and echo flag are passed to ops->tty_out(). The echo flag is true if the character is an echo of an input character, according to the line discipline of the tty. Adapters may use this flag to treat echoed characters differently from computer generated text. Characters echoed by an application, with the tty in raw mode, are recognized as echo characters based on timing. If the echo is delayed by several seconds, it may not be recognized as such. This is rare, even under ssh across the internet. The feature usually works.
6.2. If the return value of ops->tty_out() has the bottom bit set, 01, the kernel shall send the character on to the console. Most of the time the adapter returns 1, thus sending the character on to the screen. Some adapters can be controlled by the running application via escape sequences. These commands should not appear on the screen, hence the adapter returns 0 as the escape sequence is parsed.
6.3. If the return value of ops->tty_out() has the second bit set, 02, the kernel shall send an escape to the console, before sending the current character. This is used to backtrack. The adapter saw an escape, and thought an escape sequence was coming, but the next character wasn't right, so it asks the kernel to print the escape that wasn't printed last time, and then the current character.
6.4. If the return value of ops->tty_out() has the third bit set, 04, the kernel shall take a real time break before generating more tty output. This feature is activated when the adapter performs a cpu intensive function, such as responding to an escape sequence. Other processes are given a chance to run before more characters are generated on this tty.
6.5. When a console screen, or section thereof, scrolls up or down, the kernel shall call ops->scroll(), passing the top and bottom of the region, and the number of rows that the region has scrolled up. A negative number means the region has scrolled down. If the adapter is reading from screen memory, it may want to move its reading cursor accordingly, to keep in step with the moving text.
6.6. Whenever the cursor moves, the kernel shall call ops->cursorloc(), passing the new x and y coordinates. In a typical sequence, a program generates output, the tty driver passes the output character to the adapter, the adapter returns 1, the tty driver sends the character on to the console, the console prints it, and moves the cursor one to the right, or down to the next line if we wrap, then sends the new cursor location to the adapter.
6.7. Many adapters will ignore vt100 cursor control sequences, which are initiated by escape left bracket and terminated by a letter. These should not be stored in the session log, if such a log is maintained by the adapter. However, ops->tty_out() should consistently return 1, so that the escape sequence is sent to the console, which moves its cursor accordingly, then notifies the adapter via ops->cursorloc(). Note that some adapters try to glean information from these cursor control sequences, arranging the text in a different format that may be more compatible with the user's needs. Once again, it's all up to the adapter.
7. Keyboard input.
7.1. When a key is pressed or released, the kernel shall pass the keycode to ops->keystroke(), along with the shift state and an up-down flag. (This is not done if the keyboard is in raw mode.) The keycode and shift state are passed by reference, for reasons that will be described below. The shift state is an 8 bit mask, and is described in loadkeys(1). It represents the shift/control/alt keys that are being held down at the time. A separate function, getledstate(), reports numlock, capslock, and scrolllock. This function already exists in keyboard.c - we only need export it to modules. The adapter may choose to act on the shift state alone, or on the combination of shift state and lock state. For instance, if shift f3 begins speech, you probably want it to do that whether caps lock is enabled or not. However, if a key on the number pad activates speech, you probably want to inhibit this behavior when num lock is on. It's all up to the adapter.
The last parameter is nonzero if the key is being released. Since the adapter receives up and down information, it can implement key chords. This can be useful when designing a keyboard for the one handed typist. It is possible to use the space bar like another shift state, changing the sense of the letters under the left or right hand, while space alone is still a space. Since a two handed typist is unlikely to enter space chords, even by accident, he might use the very same keyboard without realizing it is adapted. Click here to read more on this design.
7.2. The kernel shall discard the keystroke if ops->keystroke() returns 0. A nonzero return shall cause the kernel to act upon the keystroke as directed by its keymap. In other words, the adapter either swallows the keystroke or passes it back.
7.3. The function puts_queue(const char* cp) shall push a null terminated string onto the input queue of the foreground tty. This is useful for macros or word completion algorithms. If the adapter decides to send the word "hello" to the tty, it calls puts_queue("hello"). The kernel cannot push more than 200 characters at a time, as there is a limit to the tty input queue. Note, this function already exists in keyboard.c - we only need export it to modules. The function put_queue(char c) shall also be exported.
7.4. The above function is fine for macros, but inadequate in other ways. Consider a user with a severe motor impairment. He has a peripheral that relays four different actions, such as sucking on a straw etc. These actions are represented by the scan codes for asdf. The key codes for asdf are passed to the adapter, and it uses them in sequence to build the keystrokes of a full keyboard. This includes the letters and numbers, which can be sent to the input queue via put_queue(). But it also includes commands like alt-f3, to switch to console 3. We need to accommodate these meta commands, and put_queue() won't do the job.
If the adapter returns a nonzero value, the kernel shall act upon the keycode and shift state, as modified by the adapter. That is why they were passed by reference. Suppose a sequence of asdf inputs translates to "console 3". The adapter sets keycode = 61 and shift_state = 2, and returns 1. The kernel then responds to alt-f3, which is a meta function to switch consoles.
8. Reading from screen memory.
8.1. The function vc_screenParams(int minor, short *nrows, short *ncols, short *x, short *y) shall return the size of the screen associated with the designated tty device, and the location of the cursor on that screen. If the designated tty is not active, the function shall return -ENODEV.
8.2. The function vc_screenItem(int minor, int x, int y) shall return the short that appears in column x row y of the designated screen.
8.3. The function vc_screenImage(int minor, short *dest, int destsize) shall return the entire screen image. The array of shorts must be large enough to hold the screen, nrows*ncols in length. The image is in row order. If destsize is not large enough, the function shall return -ENOMEM. If the designated tty is not active, the function shall return -ENODEV.
9. The PC speaker.
9.1. The function kd_mknotes(const short *data) shall play a series of tones using the in-built PC speaker. Each note consists of two shorts: frequency and duration. A frequency of -1 is a rest. A frequency of zero ends the list of notes. Duration is measured in hundredths of a second, and frequency is given in hurtz. If the computer has no in-built speaker, this function shall return harmlessly.
9.2. The kernel maintains a fifo of 32 notes to play. This is not meant to play Bach cantadas - it is used for error tones and simple feedback. This capability is very important, since it provides useful audio feedback even when the speech synthesizer is not functioning properly.
10. Polling function.
10.1. Often an adapter will employ an asynchronous thread to manage continuous reading. This thread monitors the speech synthesizer and transmits text while Linux executes other processes. Thus the blind user can sit back and listen to a screen full of information while somebody else computes the first million digits of pi. For simplicity, this thread is usually implemented by a polling function. In other words, a function wakes up ten times a second, checks the status of the synthesizer, and if the unit is not busy, sends it more words to speak. This is not the most efficient design, but we don't really need to mess with interrupts for events that only come two or three times a second.
Upon successful registration, the kernel shall call ops->polling at the designated period ops->period. The period is given in hundredths of seconds. Thus if the period is 20 the designated function will be called 5 times per second. A function pointer of 0 shall indicate no polling function. A lock shall prevent the polling function from being called if it is still running; thus it need not be reentrant. Unregister_accessibility_device() shall stop the asynchronous thread that calls this function.
11. Software.
11.1. The changes to the kernel shall conform to the standards described in Documentation/CodingStyle.
11.2. The patch that implements these requirements shall be tested using one or more adapters before it is submitted to the Linux developers. Adapters shall be run as modules and as inbuilt objects. Modular adapters shall be installed and deinstalled repeatedly.
11.3. The kernel shall be compiled and run with CONFIG_ACCESSIBILITY turned off.
If you are running Fedora 4, you can grab a already patched kernel, and a precompiled jupiter module, which you can put on your system, and boot from, optionally, using lilo or grub. However, some of your other modules may not load, since the versions are not identical. Make sure insmod jupiter.ko (with appropriate parameters) is one of the first lines in your /etc/rc.d/rc.sysinit file, so you can hear what's going on, and what is and isn't working. The hq_space and generic adapters are also available.
You can also download and burn an iso image for a talking boot cd.