summaryrefslogtreecommitdiff
path: root/payloads/libpayload/gdb/transport.c
diff options
context:
space:
mode:
Diffstat (limited to 'payloads/libpayload/gdb/transport.c')
-rw-r--r--payloads/libpayload/gdb/transport.c235
1 files changed, 235 insertions, 0 deletions
diff --git a/payloads/libpayload/gdb/transport.c b/payloads/libpayload/gdb/transport.c
new file mode 100644
index 0000000000..596ceb5c6d
--- /dev/null
+++ b/payloads/libpayload/gdb/transport.c
@@ -0,0 +1,235 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but without any warranty; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <gdb.h>
+#include <libpayload.h>
+
+static const int timeout_us = 100 * 1000;
+static const char output_overrun[] = "GDB output buffer overrun (try "
+ "increasing reply.size)!\n";
+static const char input_underrun[] = "GDB input message truncated (bug or "
+ "communication problem)?\n";
+
+/* Serial-specific glue code... add more transport layers here when desired. */
+
+static void gdb_raw_putchar(u8 c)
+{
+ serial_putchar(c);
+}
+
+static int gdb_raw_getchar(void)
+{
+ u64 start = timer_us(0);
+
+ while (!serial_havechar())
+ if (timer_us(start) > timeout_us)
+ return -1;
+
+ return serial_getchar();
+}
+
+void gdb_transport_init(void)
+{
+ console_remove_output_driver(serial_putchar);
+}
+
+void gdb_transport_teardown(void)
+{
+ serial_console_init();
+}
+
+/* Hex digit character <-> number conversion (illegal chars undefined!). */
+
+static s8 from_hex(unsigned char c)
+{
+ static const s8 values[] = {
+ -1, 10, 11, 12, 13, 14, 15, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ 0, 1, 2, 3, 4, 5, 6, 7,
+ 8, 9, -1, -1, -1, -1, -1, -1,
+ };
+
+ return values[c & 0x1f];
+}
+
+static char to_hex(u8 v)
+{
+ static const char digits[] = "0123456789abcdef";
+
+ return digits[v & 0xf];
+}
+
+/* Message encode/decode functions */
+
+void gdb_message_encode_bytes(struct gdb_message *message, const void *data,
+ int length)
+{
+ const u8 *bytes = data;
+ die_if(message->used + length * 2 > message->size, output_overrun);
+ while (length--) {
+ message->buf[message->used++] = to_hex(*bytes >> 4);
+ message->buf[message->used++] = to_hex(*bytes & 0xf);
+ bytes++;
+ }
+}
+
+void gdb_message_decode_bytes(const struct gdb_message *message, int offset,
+ void *data, int length)
+{
+ u8 *bytes = data;
+ die_if(offset + 2 * length > message->used, "Decode overrun in GDB "
+ "message: %.*s", message->used, message->buf);
+ while (length--) {
+ *bytes = from_hex(message->buf[offset++]) << 4;
+ *bytes += from_hex(message->buf[offset++]);
+ bytes++;
+ }
+}
+
+void gdb_message_encode_zero_bytes(struct gdb_message *message, int length)
+{
+ die_if(message->used + length * 2 > message->size, output_overrun);
+ memset(message->buf + message->used, '0', length * 2);
+ message->used += length * 2;
+}
+
+void gdb_message_add_string(struct gdb_message *message, const char *string)
+{
+ message->used += strlcpy((char *)message->buf + message->used,
+ string, message->size - message->used);
+
+ /* Check >= instead of > to account for strlcpy's trailing '\0'. */
+ die_if(message->used >= message->size, output_overrun);
+}
+
+void gdb_message_encode_int(struct gdb_message *message, uintptr_t val)
+{
+ int length = sizeof(uintptr_t) * 2 - __builtin_clz(val) / 4;
+ die_if(message->used + length > message->size, output_overrun);
+ while (length--)
+ message->buf[message->used++] =
+ to_hex((val >> length * 4) & 0xf);
+}
+
+uintptr_t gdb_message_decode_int(const struct gdb_message *message, int offset,
+ int length)
+{
+ uintptr_t val = 0;
+
+ die_if(length > sizeof(uintptr_t) * 2, "GDB decoding invalid number: "
+ "%.*s", message->used, message->buf);
+
+ while (length--) {
+ val <<= 4;
+ val |= from_hex(message->buf[offset++]);
+ }
+
+ return val;
+}
+
+/* Like strtok/strsep: writes back offset argument, returns original offset. */
+int gdb_message_tokenize(const struct gdb_message *message, int *offset)
+{
+ int token = *offset;
+ while (!strchr(",;:", message->buf[(*offset)++]))
+ die_if(*offset >= message->used, "Undelimited token in GDB "
+ "message at offset %d: %.*s",
+ token, message->used, message->buf);
+ return token;
+}
+
+/* High-level send/receive functions. */
+
+void gdb_get_command(struct gdb_message *command)
+{
+ enum command_state {
+ STATE_WAITING,
+ STATE_COMMAND,
+ STATE_CHECKSUM0,
+ STATE_CHECKSUM1,
+ };
+
+ u8 checksum = 0;
+ u8 running_checksum = 0;
+ enum command_state state = STATE_WAITING;
+
+ while (1) {
+ int c = gdb_raw_getchar();
+ if (c < 0) {
+ /*
+ * Timeout waiting for a byte. Reset the
+ * state machine.
+ */
+ state = STATE_WAITING;
+ continue;
+ }
+
+ switch (state) {
+ case STATE_WAITING:
+ if (c == '$') {
+ running_checksum = 0;
+ command->used = 0;
+ state = STATE_COMMAND;
+ }
+ break;
+ case STATE_COMMAND:
+ if (c == '#') {
+ state = STATE_CHECKSUM0;
+ break;
+ }
+ die_if(command->used >= command->size, "GDB input buf"
+ "fer overrun (try increasing command.size)!\n");
+ command->buf[command->used++] = c;
+ running_checksum += c;
+ break;
+ case STATE_CHECKSUM0:
+ checksum = from_hex(c) << 4;
+ state = STATE_CHECKSUM1;
+ break;
+ case STATE_CHECKSUM1:
+ checksum += from_hex(c);
+ if (running_checksum == checksum) {
+ gdb_raw_putchar('+');
+ return;
+ } else {
+ state = STATE_WAITING;
+ gdb_raw_putchar('-');
+ }
+ break;
+ }
+ }
+}
+
+void gdb_send_reply(const struct gdb_message *reply)
+{
+ int i;
+ int retries = 1 * 1000 * 1000 / timeout_us;
+ u8 checksum = 0;
+
+ for (i = 0; i < reply->used; i++)
+ checksum += reply->buf[i];
+
+ do {
+ gdb_raw_putchar('$');
+ for (i = 0; i < reply->used; i++)
+ gdb_raw_putchar(reply->buf[i]);
+ gdb_raw_putchar('#');
+ gdb_raw_putchar(to_hex(checksum >> 4));
+ gdb_raw_putchar(to_hex(checksum & 0xf));
+ } while (gdb_raw_getchar() != '+' && retries--);
+}