1. Code Analysis of DDX Input Layer
抱歉这篇报告是用英文写的,有兴趣的朋友可以参考。
1.1 A Big Picture of Maemo DDX Input Layer
From the above description , following is the big picture of it:
Figure 3: DDX Porting I n put Layer Big Picture of Maemo
1.2 Start up procedure of Maemo DDX Input Layer
Based on our current understanding, Maemo uses Tiny X instead of standard XFree86 X Server. Tiny X uses an model of Kdrive to finish the DDX work, which is described step by step as following:
Following is a snap code of main function in dix/main.c:
main(int argc, char *argv[], char *envp[])
{
……
while(1)
{
……
P rocessCommandLine(argc, argv);
……
OsInit();
……
InitCoreDevices();
InitInput(argc, argv);
InitAndStartDevices() ;
……
}
From this snap code, we can see that InitInput function is called from Main function, which is implemented in DDX Layer. For Maemo, it should be InitInput (int argc, char **argv) in Omapinit.c, which is as following:
void
InitInput (int argc, char **argv)
{
KdKeyboardInfo *ki = NULL;
ENTER();
KdAddKeyboardDriver(&LinuxKeyboardDriver);
KdAddPointerDriver(&TsDriver);
ki = KdParseKeyboard("keyboard");
KdAddKeyboard(ki);
KdInitInput();
LEAVE();
}
First of all, two drivers are added: LinuxKeyboardDriver is added as keyboard driver, and TsDriver is added as mouse driver. These two drivers are added to Kdrive global variables kdKeyboardDrivers and kdPointerDrivers , which should list all keyboard and pointer drivers in Kdrive model. They are defined as following:
KdKeyboardDriver LinuxKeyboardDriver = {
"keyboard",
LinuxKeyboardInit,
LinuxKeyboardEnable,
LinuxKeyboardLeds,
LinuxKeyboardBell,
LinuxKeyboardDisable,
LinuxKeyboardFini,
NULL,
NULL,
};
KdPointerDriver TsDriver = {
"tslib",
TslibInit,
TslibEnable,
TslibDisable,
TslibFini,
TslibCtrl,
NULL,
}; //TsDriver has two definitions, I think this one should be the used one.
And then, KdAddKeyboard is invoked to add a concrete device into system. KdParseKeyboard is very simple, which just new a KdKeyboardInfo structure and return it. It is defined as following:
typedef struct _KdKeyboardInfo KdKeyboardInfo;
struct _KdKeyboardInfo {
struct _KdKeyboardInfo *next;
DeviceIntPtr dixdev;
void *closure;
char *name;
char *path;
int inputClass;
#ifdef XKB
XkbDescPtr xkb;
#endif
int LockLed;
CARD8 keyState[KD_KEY_COUNT/8];
int minScanCode;
int maxScanCode;
CARD8 modmap[MAP_LENGTH];
KeySymsRec keySyms;
int leds;
int bellPitch;
int bellDuration;
InputOption *options;
KdKeyboardDriver *driver;
void *driverPrivate;
};
Please notice that “ keyboard ” is set to member driverPrivate in this data structure, which is used for finding driver in future.
KdAddKeyboard first invoke AddInputDevice to add a general DIX Input device. This function new a DeviceIntPtr data structure, and please notice that its first member is: DeviceRec . Also please notice that this new added device is put into the list of member off_devices in DIX global variable inputInfo , which list all devices in X Server. They are defined as following:
typedef struct {
int numDevices; /* total number of devices */
DeviceIntPtr devices; /* all devices turned on */
DeviceIntPtr off_devices; /* all devices turned off */
DeviceIntPtr keyboard; /* the main one for the server */
DeviceIntPtr pointer;
} InputInfo;
typedef struct _DeviceIntRec *DeviceIntPtr;
typedef struct _DeviceIntRec {
DeviceRec public;
DeviceIntPtr next;
TimeStamp grabTime;
Bool startup; /* true if needs to be turned on at server intialization time */
DeviceProc deviceProc; /* proc(DevicePtr, DEVICE_xx). It is used to initialize, turn on, or turn off the device */
Bool inited; /* TRUE if INIT returns Success */
Bool coreEvents; /* TRUE if device also sends core */
GrabPtr grab; /* the grabber - used by DIX */
struct {
Bool frozen;
int state;
GrabPtr other; /* if other grab has this frozen */
xEvent *event; /* saved to be replayed */
int evcount;
} sync;
Atom type;
char *name;
CARD8 id;
CARD8 activatingKey;
Bool fromPassiveGrab;
GrabRec activeGrab;
void (*ActivateGrab) (
DeviceIntPtr /*device*/,
GrabPtr /*grab*/,
TimeStamp /*time*/,
Bool /*autoGrab*/);
void (*DeactivateGrab)(
DeviceIntPtr /*device*/);
KeyClassPtr key;
ValuatorClassPtr valuator;
ButtonClassPtr button;
FocusClassPtr focus;
ProximityClassPtr proximity;
TouchscreenClassPtr touchscreen;
KbdFeedbackPtr kbdfeed;
PtrFeedbackPtr ptrfeed;
IntegerFeedbackPtr intfeed;
StringFeedbackPtr stringfeed;
BellFeedbackPtr bell;
LedFeedbackPtr leds;
#ifdef XKB
struct _XkbInterest * xkb_interest;
#endif
DevUnion *devPrivates;
int nPrivates;
DeviceUnwrapProc unwrapProc;
} DeviceIntRec;
typedef struct _DeviceRec {
pointer devicePrivate;
ProcessInputProc processInputProc; /* current */
ProcessInputProc realInputProc; /* deliver */
ProcessInputProc enqueueInputProc; /* enqueue */
Bool on; /* used by DDX to keep state */
} DeviceRec, *DevicePtr;
We should notice that when KdAddKeyboard invoke s AddInputDevice , function KdKeyboardProc is passed as a parameter, which is set to member deviceProc of DeviceIntRec . This is very important since future device status changes are all passed to this function as a call back.
Function RegisterOtherDevice is then invoked by KdAddKeyboard . This function should be invoked since Macro XINPUT should be defined, but I have not gone into this function ’ s code.
At last, function KdAddKeyboard set the new added keyboard device to kdKeyboards , which should be the variable that lists all keyboard devices in Kdrive model.
Function InitAndStartDevices in Main function is then invoked; it first calls ActivateDevice to all devices in inputInfo.off_devices list. This function invokes the callback which is registered when adding new device by passing parameter DEVICE_INIT , which should be KdKeyboardProc in our case.
Let ’ s go into case DEVICE_INIT in function KdKeyboardProc . It first find the proper driver for this off device, since driverPrivate of this just added device is “ keyboard ” , then LinuxKeyboardDriver is found. After driver is found, driverPrivate is set to NULL for future use. Init function of LinuxKeyboardDriver is called, by passing device as the parameter, which is LinuxKeyboardInit in this case. Following is the code:
static Status
LinuxKeyboardInit (KdKeyboardInfo *ki)
{
if (!ki)
return !Success;
if (ki->path)
xfree(ki->path);
ki->path = KdSaveString("console");
if (ki->name)
xfree(ki->name);
ki->name = KdSaveString("Linux console keyboard");
readKernelMapping (ki);
return Success;
}
Therefore, ki->path now is "console" , and ki->name now is "Linux console keyboard" . I have not gone to details of readKernelMapping , which seemed to equip the map function for keys.
Function KdInitModMap / KdInitAutoRepeats / InitKeyboardDeviceStruct / KdResetInputMachine have not been studied yet. F rom a rough view, InitKeyboardDeviceStruct is used to new KeyClassDevice/ FocusDevice/ KbdFeedbackClassDevice . They should be used in DIX core, yet I do not their concrete meaning since I still have not touch any of them.
After ActivateDevice is called, EnableDevice is then called for all devices in inputInfo.off_devices list. Similar to ActivateDevice , this function invokes the callback which is registered when adding new device by passing parameter DEVICE_ON , which should be KdKeyboardProc in our case.
Let ’ s go into case DEVICE_ ON in function KdKeyboardProc . It just calls the function “ Enable ” , which should be LinuxKeyboardEnable in this case. Following is the code:
static Status
LinuxKeyboardEnable (KdKeyboardInfo *ki)
{
struct termios nTty;
unsigned char buf[256];
int n;
int fd;
if (!ki)
return !Success;
fd = LinuxConsoleFd;
ki->driverPrivate = (void *) fd;
ioctl (fd, KDGKBMODE, &LinuxKbdTrans);
tcgetattr (fd, &LinuxTermios);
#ifdef XKB
if (!noXkbExtension)
ioctl(fd, KDSKBMODE, K_RAW);
else
#else
ioctl(fd, KDSKBMODE, K_MEDIUMRAW);
#endif
nTty = LinuxTermios;
nTty.c_iflag = (IGNPAR | IGNBRK) & (~PARMRK) & (~ISTRIP);
nTty.c_oflag = 0;
nTty.c_cflag = CREAD | CS8;
nTty.c_lflag = 0;
nTty.c_cc[VTIME]=0;
nTty.c_cc[VMIN]=1;
cfsetispeed(&nTty, 9600);
cfsetospeed(&nTty, 9600);
tcsetattr(fd, TCSANOW, &nTty);
/* Our kernel cleverly ignores O_NONBLOCK. Sigh. */
#if 0
/*
* Flush any pending keystrokes
*/
while ((n = read (fd, buf, sizeof (buf))) > 0)
;
#endif
KdRegisterFd (fd, LinuxKeyboardRead, ki);
return Success;
}
LinuxConsoleFd is set in function LinuxInit in Kdrive/linux/linux.c as following:
LinuxConsoleFd = open(vtname, O_RDWR|O_NDELAY, 0) . This function is called during Main function in very beginning phase; we will not go to details of it now, since it is another long story. I only describe the invoke stack of it here:
Main Function (dix/main.c) calls OsInit in os/osinit.c;
OsInit Function calls OsVendorInit , which is in kdrive/linux/linux.c in this case;
OsVendorInit Function calls KdOsInit in kdrive/src by passing LinuxFuncs as parameter;
KdOsInit Function set LinuxFuncs to glocal Kdrive variable kdOsFuncs , and calls Init in LinuxFuncs , which is LinuxInit in this case;
LinuxInit Function calls LinuxConsoleFd = open(vtname, O_RDWR|O_NDELAY, 0) , as described above.
The procedure that vtname is set is a little strange . First of all, /dev/tty0 is opened, and then ioctl by command VT_OPENQRY is used to get the vtno , which is then used to generate vtname by concatenate "/dev/tty" and vtno . Please refer to LinuxInit Function for details. ( ioctl VT_OPENQRY is used to find the number of the first available virtual console .)
Let ’ s come back to function LinuxKeyboardEnable , which then uses a series parameter to set console LinuxConsoleFd , and at last, it invokes Function KdRegisterFd . This function record the file descriptor in global Kdirve variable kdInputFds , set function read to kdInputFds[kdNumInputFds].read , which in this case is LinuxKeyboardRead , and then invoke function KdAddFd . Function KdAddFd is described as following:
static void
KdAddFd (int fd)
{
struct sigaction act;
sigset_t set;
kdnFds++;
fcntl (fd, F_SETOWN, getpid());
KdNonBlockFd (fd);
AddEnabledDevice (fd);
memset (&act, '0', sizeof act);
act.sa_handler = KdSigio;
sigemptyset (&act.sa_mask);
sigaddset (&act.sa_mask, SIGIO);
sigaddset (&act.sa_mask, SIGALRM);
sigaddset (&act.sa_mask, SIGVTALRM);
sigaction (SIGIO, &act, 0);
sigemptyset (&set);
sigprocmask (SIG_SETMASK, &set, 0);
}
It first uses AddEnabledDevice (in os/Connection.c) to add a new file descriptor to global OS Model variable EnabledDevices (which is an fd_set used in asynchronous device or socket invoke). And then KdSigio is registered as the callback function when there is a change.
static void
KdSigio (int sig)
{
int i;
for (i = 0; i < kdNumInputFds; i++)
(*kdInputFds[i].read) (kdInputFds[i].fd, kdInputFds[i].closure);
}
The read function is LinuxKeyboardRead that we just mentioned.
Fuction LinuxKeyboardRead is a long function which read key code from file descriptor , mapping the key, and then enqueuee it, which is listed as following by invoking function KdEnqueueKeyboardEvent :
static void
LinuxKeyboardRead (int fd, void *closure)
{
unsigned char buf[256], *b;
int n;
unsigned char prefix = 0, scancode = 0;
while ((n = read (fd, buf, sizeof (buf))) > 0) {
b = buf;
while (n--) {
#ifdef XKB
if (!noXkbExtension) {
/*
* With xkb we use RAW mode for reading the console, which allows us
* process extended scancodes.
*
* See if this is a prefix extending the following keycode
*/
if (!prefix && ((b[0] & 0x 7f ) == KEY_Prefix0))
{
prefix = KEY_Prefix0;
#ifdef DEBUG
ErrorF("Prefix0");
#endif
/* swallow this up */
b++;
continue;
}
else if (!prefix && ((b[0] & 0x 7f ) == KEY_Prefix1))
{
prefix = KEY_Prefix1;
ErrorF("Prefix1");
/* swallow this up */
b++;
continue;
}
scancode = b[0] & 0x 7f ;
switch (prefix) {
/* from xf86Events.c */
case KEY_Prefix0:
{
#ifdef DEBUG
ErrorF("Prefix0 scancode: 0x%02xn", scancode);
#endif
switch (scancode) {
case KEY_KP_7:
scancode = KEY_Home; break; /* curs home */
case KEY_KP_8:
scancode = KEY_Up; break; /* curs up */
case KEY_KP_9:
scancode = KEY_PgUp; break; /* curs pgup */
case KEY_KP_4:
scancode = KEY_Left; break; /* curs left */
case KEY_KP_5:
scancode = KEY_Begin; break; /* curs begin */
case KEY_KP_6:
scancode = KEY_Right; break; /* curs right */
case KEY_KP_1:
scancode = KEY_End; break; /* curs end */
case KEY_KP_2:
scancode = KEY_Down; break; /* curs down */
case KEY_KP_3:
scancode = KEY_PgDown; break; /* curs pgdown */
case KEY_KP_0:
scancode = KEY_Insert; break; /* curs insert */
case KEY_KP_Decimal:
scancode = KEY_Delete; break; /* curs delete */
case KEY_Enter:
scancode = KEY_KP_Enter; break; /* keypad enter */
case KEY_LCtrl:
scancode = KEY_RCtrl; break; /* right ctrl */
case KEY_KP_Multiply:
scancode = KEY_Print; break; /* print */
case KEY_Slash:
scancode = KEY_KP_Divide; break; /* keyp divide */
case KEY_Alt:
scancode = KEY_AltLang; break; /* right alt */
case KEY_ScrollLock:
scancode = KEY_Break; break; /* curs break */
case 0x5b:
scancode = KEY_LMeta; break;
case 0x 5c :
scancode = KEY_RMeta; break;
case 0x5d:
scancode = KEY_Menu; break;
case KEY_F3:
scancode = KEY_F13; break;
case KEY_F4:
scancode = KEY_F14; break;
case KEY_F5:
scancode = KEY_F15; break;
case KEY_F6:
scancode = KEY_F16; break;
case KEY_F7:
scancode = KEY_F17; break;
case KEY_KP_Plus:
scancode = KEY_KP_DEC; break;
/* Ignore virtual shifts (E0 2A , E0 AA, E0 36, E0 B6) */
case 0x 2A :
case 0x36:
b++;
prefix = 0;
continue;
default:
#ifdef DEBUG
ErrorF("Unreported Prefix0 scancode: 0x%02xn",
scancode);
#endif
/*
* "Internet" keyboards are generating lots of new
* codes. Let them pass. There is little consistency
* between them, so don't bother with symbolic names at
* this level.
*/
scancode += 0x78;
}
break;
}
case KEY_Prefix1:
{
/* we do no handle these */
#ifdef DEBUG
ErrorF("Prefix1 scancode: 0x%02xn", scancode);
#endif
b++;
prefix = 0;
continue;
}
default: /* should not happen*/
case 0: /* do nothing */
#ifdef DEBUG
ErrorF("Plain scancode: 0x%02xn", scancode);
#endif
;
}
prefix = 0;
}
/* without xkb we use mediumraw mode -- enqueue the scancode as is */
else
#endif
scancode = b[0] & 0x 7f ;
KdEnqueueKeyboardEvent (closure, scancode, b[0] & 0x80);
b++;
}
}
}
1.2.1 Pointer Device Register
Go back to the main() function in dix.c, P rocessCommandLine will first be called to interpret command line arguments passed to start X server. ddxProcessArgument in omapinit.c will then be called, which calls KdProcessArgument to take corresponding action according to different arguments. We can notice that there is a statement if (!strcmp (argv[i], "-mouse") || !strcmp (argv[i], "-pointer")) which invokes KdAddConfigPointer taking argv[i+1] as it s parameter, which news a struct KdConfigDevice appended to the last of gobal variable kdConfigPointers with member line assigned to argv[i+1] for future use.
After keyboard is registered using KdAddKeyboard in InitInput(omapinit.c), KdInitInput is called to registered other devices kept in kdConfigPointers and kdConfigKeyboards . This is similar to how keyboard is registered and we will not go any further here.
Since the driver name for pointer device is “ tslib ” , we can conclude that X server will be started at least wi t h “ -mouse tslib ” or “ -pointer tslib ” .
As mentioned above, function InitAndStartDevices in main(dix.c) function is invoked; it first calls ActivateDevice to all devices in inputInfo.off_devices list. This function invokes the callback which is registered when adding new device by passing parameter DEVICE_INIT , which should be KdPointerProc in our case.
Similarly, case DEVICE_INIT will first be covered, which finds according driver using “ tslib ” in our case, then call the driver ’ s callback TslibInit , see following code:
static void TslibInit (KdPointerInfo *pi)
{
int fd = 0, i = 0;
char devpath[PATH_MAX], devname[TS_NAME_SIZE];
DIR *inputdir = NULL;
struct dirent *inputent = NULL;
struct TslibPrivate *private = NULL;
if (!pi || !pi->dixdev)
return !Success;
if (!pi->path || strcmp(pi->path, "auto") == 0) {
if (!(inputdir = opendir("/dev/input"))) {
ErrorF("[tslib/TslibInit]: couldn't open /dev/input!n");
return BadMatch;
}
while ((inputent = readdir(inputdir))) {
if (strncmp(inputent->d_name, "event", 5) != 0)
continue;
snprintf(devpath, PATH_MAX, "/dev/input/%s", inputent->d_name);
fd = open(devpath, O_RDWR);
if (!ioctl(fd, EVIOCGNAME(sizeof(devname)), devname)) {
close(fd);
continue;
}
close(fd);
for (i = 0; valid_ts_names[i]; i++) {
if (strcmp(devname, valid_ts_names[i]) == 0) {
pi->path = KdSaveString(devpath);
break;
}
}
}
closedir(inputdir);
}
if (!pi->path || strcmp(pi->path, "auto") == 0) {
ErrorF("[tslib/TslibInit]: couldn't find device!n");
return BadMatch;
}
pi->driverPrivate = (struct TslibPrivate *)
xcalloc(sizeof(struct TslibPrivate), 1);
if (!pi->driverPrivate)
return !Success;
private = pi->driverPrivate;
/* hacktastic */
private->phys_screen = 0;
private->raw_event_hook = NULL;
private->raw_event_closure = NULL;
pi->nAxes = 3;
pi->nButtons = 8;
pi->name = KdSaveString("Touchscreen");
pi->inputClass = KD_TOUCHSCREEN;
DebugF("[tslib/TslibInit] successfully inited for device %sn", pi->path);
return Success;
}
It iterators through /dev/input , using ioctl to get device name of devices with name /dev/input/eventN(N=0, 1, 2, … ), if one matches one of the items in valid_ts_names , this device is the target one and the path is stored into pi->path for next step.
Then case DEVICE_ON is covered, which invokes TslibEnable . Here our pointer device is opened followed by KdRegisterFd , passing TsRead as it s argument.
When there is a SIGIO, TsRead will also be invoked . Since there will be infinite events theoretically, ts_read is used to get an sample. This sampled event will be passed to KdEnqueuePointerEvent , which does some matrix transform before enqueued.
Seeing from above, there is little needed to be done for touch screen porting.
1.3 Strange Points of Maemo DDX Input Layer
First of all, the keyboard driver we found it here likes something of virtual terminal, not a real physical keyboard on devices.