File: usb-kbd.c

package info (click to toggle)
kbd-chooser 1.34
  • links: PTS
  • area: main
  • in suites: etch, etch-m68k
  • size: 692 kB
  • ctags: 276
  • sloc: ansic: 4,320; yacc: 757; makefile: 136; lex: 128; sh: 30
file content (212 lines) | stat: -rw-r--r-- 6,483 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
/* @file  usb-kbd.c
 * @brief Report keyboards present on PC
 *
 * Copyright (C) 2002,2003 Alastair McKinstry, <mckinstry@debian.org>
 * Released under the GPL
 *
 * $Id: usb-kbd.c 38612 2006-07-02 20:42:57Z fjp $
 */

#include "config.h"
#include <sys/types.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <string.h>
#include <sys/utsname.h>
#include <debian-installer.h>
#include "xmalloc.h"
#include "kbd-chooser.h"


// Quick 4 digit hex -> integer conversion
#define DEHEX(s) ( ((*(s) - '0') << 12) + ((*(s+1) -'0') << 8) + ((*(s+2)-'0') << 4) + ((*(s+3)-'0')))

// Arch-specific information about USB Keyboards
typedef struct usb_data_s {
	uint16_t vendorid;
	uint16_t productid;
} usb_data;


static inline kbd_t *usb_new_entry (kbd_t *keyboards) {
	kbd_t *k = xmalloc (sizeof(kbd_t));
	k->name = "usb";
	k->data = NULL;
	k->deflt = NULL;
	k->present = UNKNOWN;
	k->next = keyboards;
	return k;
}


/**
 * @brief  Pick the best keymap for given USB keyboards.
 */
static kbd_t *usb_preferred_keymap (kbd_t *keyboards, const char *subarch)
{
	/* FIXME
	 * It was a mistake to tie "keymaps" to "architectures": all the keymaps
	 * in console-keymaps-usb are Mac-specific (at the moment); "PC" USB keyboards
	 * all use standard "AT" keymaps. But its too close to sarge release to change design,
	 * so we go with the following hack:
	 * If the USB keyboard vendor is Apple, set PRESENT = TRUE.
	 * For other keyboard vendors and if architecture is x86 or powerpc (prep and chrp),
	 * force the installer to display the list of AT keymaps. This is needed because, for
	 * 2.6 kernels, we can not assume that a AT connector will be detected in at-kbd.c.
	 *
	 * UPDATE
	 * Because of the changes in the input layer, we can now be sure that an
	 * AT keyboard layout is needed, even if an USB keyboard is detected. So we force
	 * any USB keyboard to AT and no longer include the option to select a USB keymap
	 * for all arches except powerpc which still needs the usb keymaps as otherwise
	 * the mode switch key (for accented characters) is mapped to the wrong key.
	 */

	// Always use AT keymaps for USB keyboards with 2.6 kernel (except for powerpc)
	int skip_26_kernels = 0;
#if defined (AT_KBD) && !defined(__powerpc__)
	struct utsname buf;
	uname(&buf);
	if (strncmp(buf.release, "2.6", 3) == 0)
		skip_26_kernels = 1;
#endif

	kbd_t *p;
	usb_data *data;
	int usb_present = 0;

	for (p = keyboards; p != NULL; p = p->next) {
		if (strcmp(p->name,"usb") == 0) {
//			usb_present = 1;
			data = (usb_data *) p->data;
			if (data->vendorid == 0x05ac) { // APPLE
				di_debug ("Apple USB keyboard detected\n");
				p->present = TRUE;
			} else {
				di_debug ("non-Apple USB keyboard detected\n");
				p->present = UNKNOWN;     // Is this really an USB/Mac keyboard?
#if defined(__powerpc__) && defined (AT_KBD)
				if (strstr (subarch, "mac") == NULL) {
					di_debug ("Forcing keymap list to AT (powerpc)\n");
					p->name = "at";   // Force installer to show AT keymaps
					p->present = TRUE;
				}
#endif
			}
			if ((strcmp(p->name,"usb") == 0) && (skip_26_kernels)) {
				di_debug ("Forcing keymap list to AT (2.6 kernel)\n");
				p->name = "at";           // Force installer to use AT keymap
				p->present = TRUE;
			}
			if (strcmp(p->name,"usb") == 0)
				usb_present = 1;
		}
	}
	// Ensure at least 1 USB entry, unless the arch uses AT for 2.6 kernels
	if (!usb_present) {
		if (!skip_26_kernels) {
			di_debug ("Adding generic entry for USB keymaps\n");
			p = usb_new_entry (keyboards);
			keyboards = p;
		} else {
			/* Hack because on powerpc laptops no keyboard is detected at all
			 * (they use ADB (Apple bus) keyboards) and keyboard configuration
			 * actually depended on the generic USB entry being added, so we
			 * now add a generic AT entry (yeah, ugly as hell)
			 */
			di_debug ("Adding generic entry for AT keymaps\n");
			p = usb_new_entry (keyboards);
			p->name = "at";   // Force installer to show AT keymaps
			keyboards = p;
		}
	}
	return keyboards;
}

/**
 * @brief parse /proc/bus/usb/devices, looking for keyboards
 */
static kbd_t *usb_parse_proc (kbd_t *keyboards)
{
	usb_data *data = NULL;
	kbd_t *k = NULL;
	char buf[LINESIZE], *p;
	FILE *fp;
	int mounted_fs = 0;
	int serr, vendorid = 0, productid = 0;

	fp = fopen ("/proc/bus/usb/devices", "r");
	if (fp == NULL) {	// try harder.
		di_debug ("Mounting usbdevfs to look for kbd\n");
		// redirect stderr for the moment
		serr = dup(2);
		close (2);
		open ("/dev/null", O_RDWR);
		if (system ("mount -t  usbfs usbfs /proc/bus/usb") != 0) {
			return keyboards; // ok, now you can give up.
		}
		// restore stderr
		close (2);
		dup (serr);
		mounted_fs = 1;
		fp = fopen("/proc/bus/usb/devices", "r");
	}
	if (fp) {
		di_debug ("Parsing /proc/bus/usb/devices\n");
		while (!feof(fp)) {
			fgets(buf, LINESIZE, fp);
			if ((p = strstr (buf, "Vendor=")) != NULL) {
				vendorid = DEHEX(p + 7);
				p = strstr(buf, "ProdID=");
				productid = DEHEX(p + strlen("ProdID="));
			}
			// The combination of Cls=03, Sub=01, Prot=01 seems a very reliable
			// test to see that an usb keyboard is present. The Driver=...
			// information varies (hid, usbkbd, usbhid) and is ignored
			if (((p = strstr(buf, "Cls=03")) != NULL) &&    // Human Interface Device
			    ((p = strstr(buf, "Sub=01")) != NULL) &&    // Boot Interface Subclass
			    ((p = strstr(buf, "Prot=01")) != NULL))  {  // Keyboard
					di_debug ("Found usb keyboard: 0x%hx:0x%hx\n", vendorid, productid);
					k = usb_new_entry (keyboards);
					data = xmalloc(sizeof(usb_data));
					k->data = (usb_data *) data;
					data->vendorid = vendorid;
					data->productid = productid;
					k->present = TRUE;
					keyboards = k;
			}
		}
		fclose(fp);
	} else {
		di_debug ("Failed to open /proc/bus/usb/devices: %d\n", errno);
	}
	if (mounted_fs)
		system ("umount /proc/bus/usb");

	return keyboards;
}


/**
 * @brief list of keyboards present
 * @return at least 1 "usb" entry.
 */
kbd_t *usb_kbd_get (kbd_t *keyboards, const char *subarch)
{

#if defined(__MIPSEL__)
        // DECstations do not have USB keyboards
         if (strstr(subarch, "r3k-kn02") || strstr(subarch,"r4k-kn04"))
               return keyboards;
#endif

	// Find all USB keyboards via /proc/bus/usb/devices
	keyboards = usb_parse_proc (keyboards);
	// Mark the default keymaps for each USB keyboard
	keyboards = usb_preferred_keymap (keyboards, subarch);

	return keyboards;
}