Skip to contentSkip to conversation

input: Use finer grain udelays while waitng for the i8042 keyboard buffer to empty

PATCH Superseded Simon Glass 3 participants 10 messages Oct 12, 2012 – Oct 20, 2012 Input drivers

v1 of 2 Superseded Latest is v2 →

lore
Share & tools
lore
#0 lore raw

PATCH 1/5 input: Use finer grain udelays while waitng for the i8042 keyboard buffer to empty

From: Gabe Black <[email protected]>

On x86, the i8042 keyboard controller driver frequently waits for the keyboard input buffer to be empty to make sure the controller has had a chance to process the data it was given. The way the delay loop was structured, if the controller hadn't cleared the corresponding status bit immediately, it would wait 1ms before checking again. If the keyboard responded quickly but not instantly, the driver would still wait a full 1ms when perhaps 1us would have been sufficient. Because udelay is a busy wait anyway, this change decreases the delay between checks to 1us.

Also, this change gets rid of a hardcoded 250ms delay.

On Stumpy, this saves 100-150ms during boot.

---
 drivers/input/i8042.c |    7 ++-----
 1 files changed, 2 insertions(+), 5 deletions(-)
drivers/input/i8042.c +2 -5
index c3bc536..99254e4 100644
@@ -607,11 +607,10 @@ static void kbd_led_set(void)
 
 static int kbd_input_empty(void)
 {
-	int kbdTimeout = KBD_TIMEOUT;
+	int kbdTimeout = KBD_TIMEOUT * 1000;
 
-	/* wait for input buf empty */
 	while ((in8(I8042_STATUS_REG) & 0x02) && kbdTimeout--)
-		udelay(1000);
+		udelay(1);
 
 	return kbdTimeout != -1;
 }
@@ -625,8 +624,6 @@ static int kbd_reset(void)
 
 	out8(I8042_DATA_REG, 0xff);
 
-	udelay(250000);
-
 	if (kbd_input_empty() == 0)
 		return -1;
 
Signature · 1 line
1.7.7.3
#1 lore raw

PATCH 2/5 input: Provide a board specific mechanism to decide whether to skip i8042 init

From: Gabe Black <[email protected]>

This change adds a board overridable function which can be used to decide whether or not to initialize the i8042 keyboard controller. On systems where it isn't actually connected to anything, this can save a significant amount of boot time.

On Stumpy, this saves about 200ms on boot.

---
 drivers/input/i8042.c |   13 ++++++++++++-
 1 files changed, 12 insertions(+), 1 deletions(-)
drivers/input/i8042.c +12 -1
index 99254e4..cae2d0a 100644
@@ -320,6 +320,17 @@ static int kbd_controller_present(void)
 	return in8(I8042_STATUS_REG) != 0xff;
 }
 
+/*
+ * Implement a weak default function for boards that optionally
+ * need to skip the i8042 initialization.
+ */
+int __board_i8042_skip(void)
+{
+	/* As default, don't skip */
+	return 0;
+}
+int board_i8042_skip(void) __attribute__((weak, alias("__board_i8042_skip")));
+
 /*******************************************************************************
  *
  * i8042_kbd_init - reset keyboard and init state flags
@@ -329,7 +340,7 @@ int i8042_kbd_init(void)
 	int keymap, try;
 	char *penv;
 
-	if (!kbd_controller_present())
+	if (!kbd_controller_present() || board_i8042_skip())
 		return -1;
 
 #ifdef CONFIG_USE_CPCIDVI
Signature · 1 line
1.7.7.3
#2 lore raw

PATCH 2/5 input: Provide a board specific mechanism to decide whether to skip i8042 init

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Quoted 19 lines · Simon Glass

From: Gabe Black <[email protected]>

This change adds a board overridable function which can be used to decide whether or not to initialize the i8042 keyboard controller. On systems where it isn't actually connected to anything, this can save a significant amount of boot time.

On Stumpy, this saves about 200ms on boot.

Simon Glass <[email protected]> --- drivers/input/i8042.c | 13
++++++++++++- 1 files changed, 12 insertions(+), 1 deletions(-)

drivers/input/i8042.c index renamed from drivers/input/i8042.c +0 -0

99254e4..cae2d0a 100644 --- a/drivers/input/i8042.c +++ b/drivers/input/i8042.c @@ -320,6 +320,17 @@ static int kbd_controller_present(void) return in8(I8042_STATUS_REG) != 0xff; }

Quoted 5 lines

+/* + * Implement a weak default function for boards that optionally + * need to skip the i8042 initialization. + */ +int
__board_i8042_skip(void) +{ + /* As default, don't skip */ + return
0; +} +int board_i8042_skip(void) __attribute__((weak, alias("__board_i8042_skip")));

Please add <linux/compiler.h> and use __weak directly, thanks.

- --
Tom
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.11 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://www.enigmail.net/

iQIcBAEBAgAGBQJQeD/QAAoJENk4IS6UOR1WVMsP/RJTlWaTvF56K6H75WbjK7l5 xE7zeAeCS4gbM1CS0fEv1X4296L7oWUUUkw73gwSEAxd2CxOj96HrhJI0WQIqZ/a m6ysfwswzY92oqXJGxnWnLiZzd0JUR/QHZAM7U0tX1yHFsYrycEtuq+JvfMm5BU7 1ozqrN0SoFPa8ZBq0GpnXhDsGdMApucDBkbDv3cgHTYPkOlfh3gLpbz/dkWKxrCv NBwKvZgVMtEPf9hZf5CLuB2ghfWIRyP50Bf99nKhV+gdLGtrEbboHkMMXEc2Z1ex foEMqpmSiMVd9yMRqVEIM3sWwnapqAZVn3wQLoq5Kz+YGS/9e0c9t95ck+CmndiM kJmrx/LZ/XSGzjLrKC3MWLWC/KUS2hHqj4AfYUf1kL5lPuHM24c+oSRfOjogkaqR bMJZ9ii/kYcQJry/dAYTjFyEa120X48y46e2bNcMhWVZh48A6VpJkFILb1Ga4tEo ss+hMBX/tiMNp8eoOR7y1n+A+94r3ABKUrLapjfVKg+qXeufrMyotBVfPY35hWnP qLNVlPpdEF7nP1Wt22OB9s6bBcI6dCrNuL75Ym6wJ3DYnpqU8Y8f3HihwL7cBy7F o1Zpo0bzazNfy722IYphWu0O3hihIQ11fRpAndVemUzjaXP/kKxaVhiDCj28xHup 9zGuTm7SW/liJTXp4cc8
=5tNx
-----END PGP SIGNATURE-----

#3 lore raw

PATCH 2/5 input: Provide a board specific mechanism to decide whether to skip i8042 init

Hi Tom,

Quoted 23 lines · Tom Rini

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Quoted 19 lines · Simon Glass

From: Gabe Black <[email protected]>

This change adds a board overridable function which can be used to decide whether or not to initialize the i8042 keyboard controller. On systems where it isn't actually connected to anything, this can save a significant amount of boot time.

On Stumpy, this saves about 200ms on boot.

Simon Glass <[email protected]> --- drivers/input/i8042.c | 13
++++++++++++- 1 files changed, 12 insertions(+), 1 deletions(-)

drivers/input/i8042.c index renamed from drivers/input/i8042.c +0 -0

99254e4..cae2d0a 100644 --- a/drivers/input/i8042.c +++ b/drivers/input/i8042.c @@ -320,6 +320,17 @@ static int kbd_controller_present(void) return in8(I8042_STATUS_REG) != 0xff; }

Quoted 7 lines
Quoted 5 lines

+/* + * Implement a weak default function for boards that optionally + * need to skip the i8042 initialization. + */ +int __board_i8042_skip(void) +{ + /* As default, don't skip */ + return 0; +} +int board_i8042_skip(void) __attribute__((weak, alias("__board_i8042_skip")));

Please add <linux/compiler.h> and use __weak directly, thanks.

Yes will do,

Quoted 7 lines

- --
Tom
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.11 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://www.enigmail.net/

Quoted 14 lines

iQIcBAEBAgAGBQJQeD/QAAoJENk4IS6UOR1WVMsP/RJTlWaTvF56K6H75WbjK7l5 xE7zeAeCS4gbM1CS0fEv1X4296L7oWUUUkw73gwSEAxd2CxOj96HrhJI0WQIqZ/a m6ysfwswzY92oqXJGxnWnLiZzd0JUR/QHZAM7U0tX1yHFsYrycEtuq+JvfMm5BU7 1ozqrN0SoFPa8ZBq0GpnXhDsGdMApucDBkbDv3cgHTYPkOlfh3gLpbz/dkWKxrCv NBwKvZgVMtEPf9hZf5CLuB2ghfWIRyP50Bf99nKhV+gdLGtrEbboHkMMXEc2Z1ex foEMqpmSiMVd9yMRqVEIM3sWwnapqAZVn3wQLoq5Kz+YGS/9e0c9t95ck+CmndiM kJmrx/LZ/XSGzjLrKC3MWLWC/KUS2hHqj4AfYUf1kL5lPuHM24c+oSRfOjogkaqR bMJZ9ii/kYcQJry/dAYTjFyEa120X48y46e2bNcMhWVZh48A6VpJkFILb1Ga4tEo ss+hMBX/tiMNp8eoOR7y1n+A+94r3ABKUrLapjfVKg+qXeufrMyotBVfPY35hWnP qLNVlPpdEF7nP1Wt22OB9s6bBcI6dCrNuL75Ym6wJ3DYnpqU8Y8f3HihwL7cBy7F o1Zpo0bzazNfy722IYphWu0O3hihIQ11fRpAndVemUzjaXP/kKxaVhiDCj28xHup 9zGuTm7SW/liJTXp4cc8
=5tNx
-----END PGP SIGNATURE-----

Regards,
Simon

#4 lore raw

PATCH 3/5 input: i8042: Provide feature to disable keyboard before booting kernel

In reply to Simon Glass

From: Louis Yung-Chieh Lo <[email protected]>

The BIOS leaves the keyboard enabled during boot time so that any keystroke would interfere kernel driver initialization.

Add a way to disable the keyboard to make sure no scancode will be generated during the boot time. Note that the keyboard will be re-enabled again after the kernel driver is up.

This code can be called from the board functions.

---
 drivers/input/i8042.c |   38 ++++++++++++++++++++++++++++++++++++++
 include/i8042.h       |   13 +++++++++++++
 2 files changed, 51 insertions(+), 0 deletions(-)
drivers/input/i8042.c +38 -0
index cae2d0a..6b51eb5 100644
@@ -331,6 +331,44 @@ int __board_i8042_skip(void)
 }
 int board_i8042_skip(void) __attribute__((weak, alias("__board_i8042_skip")));
 
+void i8042_flush(void)
+{
+	int timeout;
+
+	/*
+	 * The delay is to give the keyboard controller some time to fill the
+	 * next byte.
+	 */
+	while (1) {
+		timeout = 100;  /* wait for no longer than 100us */
+		while (timeout > 0 && !(in8(I8042_STATUS_REG) & 0x01)) {
+			udelay(1);
+			timeout--;
+		}
+
+		/* Try to pull next byte if not timeout. */
+		if (in8(I8042_STATUS_REG) & 0x01)
+			in8(I8042_DATA_REG);
+		else
+			break;
+	}
+}
+
+int i8042_disable(void)
+{
+	if (kbd_input_empty() == 0)
+		return -1;
+
+	/* Disable keyboard */
+	out8(I8042_COMMAND_REG, 0xad);
+
+	if (kbd_input_empty() == 0)
+		return -1;
+
+	return 0;
+}
+
+
 /*******************************************************************************
  *
  * i8042_kbd_init - reset keyboard and init state flags
include/i8042.h +13 -0
index 1395289..c3e4e02 100644
@@ -69,6 +69,19 @@
 
 /* exports */
 
+/**
+ * Flush all buffer from keyboard controller to host.
+ */
+void i8042_flush(void);
+
+/**
+ * Disables the keyboard so that key strokes no longer generate scancodes to
+ * the host.
+ *
+ * @return 0 if ok, -1 if keyboard input was found while disabling
+ */
+int i8042_disable(void);
+
 int i8042_kbd_init(void);
 int i8042_tstc(void);
 int i8042_getc(void);
Signature · 1 line
1.7.7.3
#5 lore raw

PATCH 4/5 input: Fix i8042 keyboard reset

In reply to Simon Glass

From: Marc Jones <[email protected]>

The i8042 keyboard reset was not checking the results of the output buffer after the reset command. This can jam up some KBC/keyboards. Also, remove a write to the wrong register and the CONFIG setting around the incorrect write.

---
 drivers/input/i8042.c |   32 +++++++++++++++++++++++++++-----
 1 files changed, 27 insertions(+), 5 deletions(-)
drivers/input/i8042.c +27 -5
index 6b51eb5..65611ec 100644
@@ -666,31 +666,53 @@ static int kbd_input_empty(void)
 
 /******************************************************************************/
 
+static int wait_until_kbd_output_full(void)
+{
+	int kbdTimeout = KBD_TIMEOUT * 1000;
+
+	while (((in8(I8042_STATUS_REG) & 0x01) == 0) && kbdTimeout--)
+		udelay(1);
+
+	return kbdTimeout != -1;
+}
+
+/******************************************************************************/
+
 static int kbd_reset(void)
 {
+	/* KB Reset */
 	if (kbd_input_empty() == 0)
 		return -1;
 
 	out8(I8042_DATA_REG, 0xff);
 
+	if (wait_until_kbd_output_full() == 0)
+		return -1;
+
+	if (in8(I8042_DATA_REG) != 0xfa) /* ACK */
+		return -1;
+
+	if (wait_until_kbd_output_full() == 0)
+		return -1;
+
+	if (in8(I8042_DATA_REG) != 0xaa) /* Test Pass*/
+		return -1;
+
 	if (kbd_input_empty() == 0)
 		return -1;
 
-#ifdef CONFIG_USE_CPCIDVI
+	/* Set KBC mode */
 	out8(I8042_COMMAND_REG, 0x60);
-#else
-	out8(I8042_DATA_REG, 0x60);
-#endif
 
 	if (kbd_input_empty() == 0)
 		return -1;
 
 	out8(I8042_DATA_REG, 0x45);
 
-
 	if (kbd_input_empty() == 0)
 		return -1;
 
+	/* Enable Keyboard */
 	out8(I8042_COMMAND_REG, 0xae);
 
 	if (kbd_input_empty() == 0)
Signature · 1 line
1.7.7.3
#6 lore raw

PATCH 5/5 input: Add ANSI 3.64 escape sequence generation.

In reply to Simon Glass

From: Hung-Te Lin <[email protected]>

To support Non-ASCII keys (ex, Fn, PgUp/Dn, arrow keys, ...), we need to translate key code into escape sequence.

(Updated by sjg at chromium.org to move away from a function to store keycodes, so we can easily record how many were sent. We now need to return this from input_send_keycodes() so we know whether keys were generated.)

---
 drivers/input/input.c |  102 +++++++++++++++++++++++++++++++++++++++++-------
 include/input.h       |    2 +
 2 files changed, 89 insertions(+), 15 deletions(-)
drivers/input/input.c +87 -15
index 5b2b4b0..9800667 100644
@@ -93,6 +93,22 @@ static unsigned char kbd_ctrl_xlate[] = {
 	'\r', 0xff, 0xff
 };
 
+/*
+ * Scan key code to ANSI 3.64 escape sequence table.  This table is
+ * incomplete in that it does not include all possible extra keys.
+ */
+static struct {
+	int kbd_scan_code;
+	char *escape;
+} kbd_to_ansi364[] = {
+	{ KEY_UP, "\033[A"},
+	{ KEY_DOWN, "\033[B"},
+	{ KEY_RIGHT, "\033[C"},
+	{ KEY_LEFT, "\033[D"},
+};
+
+/* Maximum number of output characters that an ANSI sequence expands to */
+#define ANSI_CHAR_MAX	3
 
 int input_queue_ascii(struct input_config *config, int ch)
 {
@@ -289,24 +305,67 @@ static int input_check_keycodes(struct input_config *config,
 }
 
 /**
+ * Checks and converts a special key code into ANSI 3.64 escape sequence.
+ *
+ * @param config	Input state
+ * @param keycode	Key code to examine
+ * @param output_ch	Buffer to place output characters into. It should
+ *			be at least ANSI_CHAR_MAX bytes long, to allow for
+ *			an ANSI sequence.
+ * @param max_chars	Maximum number of characters to add to output_ch
+ * @return number of characters output, if the key was converted, otherwise 0.
+ *	This may be larger than max_chars, in which case the overflow
+ *	characters are not output.
+ */
+static int input_keycode_to_ansi364(struct input_config *config,
+		int keycode, char output_ch[], int max_chars)
+{
+	const char *escape;
+	int ch_count;
+	int i;
+
+	for (i = ch_count = 0; i < ARRAY_SIZE(kbd_to_ansi364); i++) {
+		if (keycode != kbd_to_ansi364[i].kbd_scan_code)
+			continue;
+		for (escape = kbd_to_ansi364[i].escape; *escape; escape++) {
+			if (ch_count < max_chars)
+				output_ch[ch_count] = *escape;
+			ch_count++;
+		}
+		return ch_count;
+	}
+
+	return 0;
+}
+
+/**
+ * Converts and queues a list of key codes in escaped ASCII string form
  * Convert a list of key codes into ASCII
  *
  * You must call input_check_keycodes() before this. It turns the keycode
- * list into a list of ASCII characters which are ready to send to the
- * input layer.
+ * list into a list of ASCII characters and sends them to the input layer.
  *
  * Characters which were seen last time do not generate fresh ASCII output.
+ * The output (calls to queue_ascii) may be longer than num_keycodes, if the
+ * keycode contains special keys that was encoded to longer escaped sequence.
  *
  * @param config	Input state
  * @param keycode	List of key codes to examine
  * @param num_keycodes	Number of key codes
+ * @param output_ch	Buffer to place output characters into. It should
+ *			be@last ANSI_CHAR_MAX * num_keycodes, to allow for
+ *			ANSI sequences.
+ * @param max_chars	Maximum number of characters to add to output_ch
  * @param same		Number of key codes which are the same
+ * @return number of characters written into output_ch, or -1 if we would
+ *	exceed max_chars chars.
  */
 static int input_keycodes_to_ascii(struct input_config *config,
-		int keycode[], int num_keycodes, char output_ch[], int same)
+		int keycode[], int num_keycodes, char output_ch[],
+		int max_chars, int same)
 {
 	struct input_key_xlate *table;
-	int ch_count;
+	int ch_count = 0;
 	int i;
 
 	table = &config->table[0];
@@ -321,19 +380,31 @@ static int input_keycodes_to_ascii(struct input_config *config,
 		}
 	}
 
-	/* now find normal keys */
-	for (i = ch_count = 0; i < num_keycodes; i++) {
+	/* Start conversion by looking for the first new keycode (by same). */
+	for (i = same; i < num_keycodes; i++) {
 		int key = keycode[i];
+		int ch = (key < table->num_entries) ? table->xlate[key] : 0xff;
 
-		if (key < table->num_entries && i >= same) {
-			int ch = table->xlate[key];
-
-			/* If a normal key with an ASCII value, add it! */
-			if (ch != 0xff)
-				output_ch[ch_count++] = (uchar)ch;
+		/*
+		 * For a normal key (with an ASCII value), add it; otherwise
+		 * translate special key to escape sequence if possible.
+		 */
+		if (ch != 0xff) {
+			if (ch_count < max_chars)
+				output_ch[ch_count] = (uchar)ch;
+			ch_count++;
+		} else {
+			ch_count += input_keycode_to_ansi364(config, key,
+						output_ch, max_chars);
 		}
 	}
 
+	if (ch_count > max_chars) {
+		debug("%s: Output char buffer overflow size=%d, need=%d\n",
+		      __func__, max_chars, ch_count);
+		return -1;
+	}
+
 	/* ok, so return keys */
 	return ch_count;
 }
@@ -341,7 +412,7 @@ static int input_keycodes_to_ascii(struct input_config *config,
 int input_send_keycodes(struct input_config *config,
 			int keycode[], int num_keycodes)
 {
-	char ch[num_keycodes];
+	char ch[num_keycodes * ANSI_CHAR_MAX];
 	int count, i, same = 0;
 	int is_repeat = 0;
 	unsigned delay_ms;
@@ -363,7 +434,7 @@ int input_send_keycodes(struct input_config *config,
 	}
 
 	count = input_keycodes_to_ascii(config, keycode, num_keycodes,
-					ch, is_repeat ? 0 : same);
+					ch, sizeof(ch), is_repeat ? 0 : same);
 	for (i = 0; i < count; i++)
 		input_queue_ascii(config, ch[i]);
 	delay_ms = is_repeat ?
@@ -371,7 +442,8 @@ int input_send_keycodes(struct input_config *config,
 			config->repeat_delay_ms;
 
 	config->next_repeat_ms = get_timer(0) + delay_ms;
-	return 0;
+
+	return count;
 }
 
 int input_add_table(struct input_config *config, int left_keycode,
include/input.h +2 -0
index 0f4acb2..e90bb0b 100644
@@ -84,6 +84,8 @@ struct stdio_dev;
  * @param config	Input state
  * @param keycode	List of key codes to examine
  * @param num_keycodes	Number of key codes
+ * @return number of ascii characters sent, or 0 if none, or -1 for an
+ *	internal error
  */
 int input_send_keycodes(struct input_config *config, int keycode[], int count);
 
Signature · 1 line
1.7.7.3
#7 lore raw

PATCH 1/5 input: Use finer grain udelays while waitng for the i8042 keyboard buffer to empty

In reply to Simon Glass

Hi Simon,

drivers/input/i8042.c static void kbd_led_set(void) · line 612

Quoted 13 lines · Simon Glass
drivers/input/i8042.c +1 -2
index c3bc536..99254e4 100644
@@ -607,11 +607,10 @@ static void kbd_led_set(void)
 
 static int kbd_input_empty(void)
 {
-       int kbdTimeout = KBD_TIMEOUT;
+       int kbdTimeout = KBD_TIMEOUT * 1000;
 
-       /* wait for input buf empty */
        while ((in8(I8042_STATUS_REG) & 0x02) && kbdTimeout--)

I know this magic number was already here, but later patches introduce even more. Any chance these can be cleaned up as well?

Regards,

Graeme

#8 lore raw

PATCH 1/5 input: Use finer grain udelays while waitng for the i8042 keyboard buffer to empty

Hi Graeme,

Quoted 15 lines · Graeme Russ

Hi Simon,

Quoted 11 lines · Simon Glass
drivers/input/i8042.c +1 -1
index c3bc536..99254e4 100644
@@ -607,11 +607,10 @@ static void kbd_led_set(void)
 
 static int kbd_input_empty(void)
 {
-       int kbdTimeout = KBD_TIMEOUT;
+       int kbdTimeout = KBD_TIMEOUT * 1000;
 
Quoted 5 lines

drivers/input/i8042.c static void kbd_led_set(void) · line 612

Quoted 2 lines
drivers/input/i8042.c +0 -1
-       /* wait for input buf empty */
        while ((in8(I8042_STATUS_REG) & 0x02) && kbdTimeout--)

I know this magic number was already here, but later patches introduce even more. Any chance these can be cleaned up as well?

Yes, will do.

Regards,
Simon

Quoted 4 lines

Regards,

Graeme

#9 lore raw

PATCH 1/5 input: Use finer grain udelays while waitng for the i8042 keyboard buffer to empty

In reply to Simon Glass
Quoted 11 lines · Simon Glass

From: Gabe Black <[email protected]>

On x86, the i8042 keyboard controller driver frequently waits for the keyboard input buffer to be empty to make sure the controller has had a chance to process the data it was given. The way the delay loop was structured, if the controller hadn't cleared the corresponding status bit immediately, it would wait 1ms before checking again. If the keyboard responded quickly but not instantly, the driver would still wait a full 1ms when perhaps 1us would have been sufficient. Because udelay is a busy wait anyway, this change decreases the delay between checks to 1us.

Quoted 6 lines

Also, this change gets rid of a hardcoded 250ms delay.

On Stumpy, this saves 100-150ms during boot.

This, or more precisely, v2 of 1/5 and 2/5 and v1 of 3, 4 and 5/5 have been applied to u-boot/master, thanks!

Signature · 8 lines
Tom
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 836 bytes
Desc: Digital signature
URL: <http://lists.denx.de/pipermail/u-boot/attachments/20121020/9985872b/attachment.pgp>