Subject: [PATCH] kgdb over USB

if you just use kgdb+ttyGS0, it wont work.

http://bootloader.wikidot.com/android:kgdb
http://github.com/dankex/kgdb-android
---
 arch/arm/mach-msm/board-mahimahi.c         |    4 +
 arch/arm/mach-msm/include/mach/msm_hsusb.h |    5 +
 drivers/serial/kgdboc.c                    |   33 +++++-
 drivers/usb/gadget/msm72k_udc.c            |   51 +++++++
 drivers/usb/gadget/u_serial.c              |  201 +++++++++++++++++++++++++++-
 5 files changed, 292 insertions(+), 2 deletions(-)

diff --git a/arch/arm/mach-msm/board-mahimahi.c b/arch/arm/mach-msm/board-mahimahi.c
index f41f362..61021fe 100644
--- a/arch/arm/mach-msm/board-mahimahi.c
+++ b/arch/arm/mach-msm/board-mahimahi.c
@@ -154,8 +154,12 @@ static char *usb_functions_all[] = {
 #ifdef CONFIG_USB_ANDROID_RNDIS
 	"rndis",
 #endif
+#ifdef CONFIG_USB_ANDROID_MASS_STORAGE
 	"usb_mass_storage",
+#endif
+#ifdef CONFIG_USB_ANDROID_ADB
 	"adb",
+#endif
 #ifdef CONFIG_USB_ANDROID_ACM
 	"acm",
 #endif
diff --git a/arch/arm/mach-msm/include/mach/msm_hsusb.h b/arch/arm/mach-msm/include/mach/msm_hsusb.h
index 93fb9a4..b2d949e 100644
--- a/arch/arm/mach-msm/include/mach/msm_hsusb.h
+++ b/arch/arm/mach-msm/include/mach/msm_hsusb.h
@@ -35,4 +35,9 @@ struct msm_hsusb_platform_data {
 	int *phy_init_seq;
 };
 
+#if defined(CONFIG_USB_GADGET_MSM_72K) && defined(CONFIG_CONSOLE_POLL)
+struct usb_ep;
+int usb_loop_poll_hw(struct usb_ep *_ept, int is_rx);
+#endif /* CONFIG_USB_GADGET_MSM_72K && CONFIG_CONSOLE_POLL */
+
 #endif
diff --git a/drivers/serial/kgdboc.c b/drivers/serial/kgdboc.c
index eadc1ab..5b17844 100644
--- a/drivers/serial/kgdboc.c
+++ b/drivers/serial/kgdboc.c
@@ -24,6 +24,7 @@ static struct kgdb_io		kgdboc_io_ops;
 static int configured		= -1;
 
 static char config[MAX_CONFIG_LEN];
+static int config_retry_time = 5;//default retry time, Droid bootloader donot allow modified cmdline
 static struct kparam_string kps = {
 	.string			= config,
 	.maxlen			= MAX_CONFIG_LEN,
@@ -45,6 +46,28 @@ static int kgdboc_option_setup(char *opt)
 
 __setup("kgdboc=", kgdboc_option_setup);
 
+static int kgdbretry_option_setup(char *opt)
+{
+	if (strlen(opt) > MAX_CONFIG_LEN) {
+		printk(KERN_ERR "kgdbretry: config string too long\n");
+		return -ENOSPC;
+	}
+	config_retry_time = simple_strtoul(opt, NULL, 10);
+
+	return 0;
+}
+
+__setup("kgdbretry=", kgdbretry_option_setup);
+
+static int configure_kgdboc(void);
+static void ttycheck_func(struct work_struct *work)
+{
+	config_retry_time = 0;
+	configure_kgdboc();
+}
+
+static DECLARE_DELAYED_WORK(ttycheck_work, ttycheck_func);
+
 static int configure_kgdboc(void)
 {
 	struct tty_driver *p;
@@ -58,8 +81,15 @@ static int configure_kgdboc(void)
 	err = -ENODEV;
 
 	p = tty_find_polling_driver(config, &tty_line);
-	if (!p)
+	if (!p) {
+		printk("kgdb will retry in %d secs\n", config_retry_time);
+		if (config_retry_time > 0) {
+			INIT_DELAYED_WORK(&ttycheck_work, ttycheck_func);
+			schedule_delayed_work(&ttycheck_work, config_retry_time * HZ);
+			return -ENODEV;
+		}
 		goto noconfig;
+	}
 
 	kgdb_tty_driver = p;
 	kgdb_tty_line = tty_line;
@@ -166,5 +196,6 @@ module_init(init_kgdboc);
 module_exit(cleanup_kgdboc);
 module_param_call(kgdboc, param_set_kgdboc_var, param_get_string, &kps, 0644);
 MODULE_PARM_DESC(kgdboc, "<serial_device>[,baud]");
+MODULE_PARM_DESC(kgdbretry, "<delay in seconds> before retrying tty");
 MODULE_DESCRIPTION("KGDB Console TTY Driver");
 MODULE_LICENSE("GPL");
diff --git a/drivers/usb/gadget/msm72k_udc.c b/drivers/usb/gadget/msm72k_udc.c
index 0250c3f..6bdb309 100644
--- a/drivers/usb/gadget/msm72k_udc.c
+++ b/drivers/usb/gadget/msm72k_udc.c
@@ -794,6 +794,57 @@ static void handle_endpoint(struct usb_info *ui, unsigned bit)
 	spin_unlock_irqrestore(&ui->lock, flags);
 }
 
+#ifdef CONFIG_CONSOLE_POLL
+int usb_loop_poll_hw(struct usb_ep *_ept, int is_rx)
+{
+	struct msm_endpoint *act_ept, *ept = to_msm_endpoint(_ept);
+	struct usb_info *ui = ept->ui;
+	int done = 0;
+	u32 n;
+
+	/* Normally there is a read request in the endpoint, wait for new data */
+	for (;;) {
+		n = readl(USB_USBSTS);
+		writel(n, USB_USBSTS);
+		if (n & STS_UI) /* finished transaction */
+			break;
+	}
+
+	/* USB Transaction is complete */
+	if (n & STS_UI) {
+		n = readl(USB_ENDPTSETUPSTAT);
+		if (n & EPT_RX(0))
+			handle_setup(ui);
+
+		n = readl(USB_ENDPTCOMPLETE);
+		writel(n, USB_ENDPTCOMPLETE);
+
+		while (n) {
+			unsigned bit = __ffs(n);
+			act_ept = ui->ept + bit;
+			if (ept == act_ept) {
+				pr_debug("%s: recv'd right tx %d\n", __func__, bit);
+				done = 1;
+			}
+			else {
+				pr_debug("%s: recv'd extra tx from ept %d (exp %d)\n",
+						__func__, bit, ept->bit);
+			}
+			/* always call the handler for KGDB and other usb functions.
+			 * this is to avoid hardware timeout, but can leave a bit
+			 * kernel code running when kgdb is invoked to stopped the
+			 * kernel. this works quite well with adb but might not
+			 * support usb mass storage devices very well.
+			 */
+			handle_endpoint(ui, bit);
+			n = n & (~(1 << bit));
+		}
+	}
+
+	return done ? 0 : -EAGAIN;
+}
+#endif /* CONFIG_CONSOLE_POLL */
+
 #define FLUSH_WAIT_US	5
 #define FLUSH_TIMEOUT	(2 * (USEC_PER_SEC / FLUSH_WAIT_US))
 static void flush_endpoint_hw(struct usb_info *ui, unsigned bits)
diff --git a/drivers/usb/gadget/u_serial.c b/drivers/usb/gadget/u_serial.c
index 3e8dcb5..f10296c 100644
--- a/drivers/usb/gadget/u_serial.c
+++ b/drivers/usb/gadget/u_serial.c
@@ -23,10 +23,13 @@
 #include <linux/delay.h>
 #include <linux/tty.h>
 #include <linux/tty_flip.h>
-#include <linux/slab.h>
 
 #include "u_serial.h"
 
+#ifdef CONFIG_CONSOLE_POLL
+/* for kgdb support, directly talks to msm72k_udc for now */
+# include <mach/msm_hsusb.h>
+#endif
 
 /*
  * This component encapsulates the TTY layer glue needed to provide basic
@@ -78,6 +81,7 @@
  */
 #define QUEUE_SIZE		16
 #define WRITE_BUF_SIZE		8192		/* TX only */
+#define CONSOLE_BUF_SIZE	1024
 
 /* circular buffer */
 struct gs_buf {
@@ -989,6 +993,12 @@ static int gs_break_ctl(struct tty_struct *tty, int duration)
 	return status;
 }
 
+#ifdef CONFIG_CONSOLE_POLL
+static int gs_poll_init(struct tty_driver *driver, int line, char *options);
+static int gs_poll_get_char(struct tty_driver *driver, int line);
+static void gs_poll_put_char(struct tty_driver *driver, int line, char ch);
+#endif
+
 static const struct tty_operations gs_tty_ops = {
 	.open =			gs_open,
 	.close =		gs_close,
@@ -999,6 +1009,11 @@ static const struct tty_operations gs_tty_ops = {
 	.chars_in_buffer =	gs_chars_in_buffer,
 	.unthrottle =		gs_unthrottle,
 	.break_ctl =		gs_break_ctl,
+#ifdef CONFIG_CONSOLE_POLL
+	.poll_init = gs_poll_init,
+	.poll_get_char = gs_poll_get_char,
+	.poll_put_char = gs_poll_put_char,
+#endif
 };
 
 /*-------------------------------------------------------------------------*/
@@ -1319,3 +1334,187 @@ void gserial_disconnect(struct gserial *gser)
 	gs_free_requests(gser->in, &port->write_pool);
 	spin_unlock_irqrestore(&port->port_lock, flags);
 }
+
+#ifdef CONFIG_CONSOLE_POLL
+
+static char console_buf[CONSOLE_BUF_SIZE]; /* >= max packet size 512 */
+static int console_buf_len = 0, console_buf_read = 0;
+
+/*
+ * This set of functions are used to read/write simple characters
+ * from the USB serial ports, so they can be used for the console.
+ */
+static int gs_poll_init(struct tty_driver *driver, int port_num, char *options) {
+	struct gs_port *port;
+	struct tty_struct *tty;
+
+	if (!(port_num >= 0 && port_num < N_PORTS))
+		return -EINVAL;
+
+	port = ports[port_num].port;
+	if (!port)
+		return -ENODEV;
+
+	tty = port->port_tty;
+	if (!tty) {
+		/* the kgdb put/get char functions don't need a tty */
+		pr_vdebug("%s: no tty, but it's ok\n", __func__);
+		return 0;
+	} else {
+		pr_vdebug("%s: tty opened for port_num %d\n", __func__, port_num);
+	}
+	return 0;
+}
+
+static int gs_poll_pop_buffer(void) {
+	if (console_buf_read >= console_buf_len) {
+		return -EAGAIN;
+	}
+	return console_buf[console_buf_read++];
+}
+
+/**
+ * gs_poll_read_complete
+ */
+static void gs_poll_read_complete(struct usb_ep *ep,
+		struct usb_request *req) {
+	switch (req->status) {
+	case 0:
+		/* get data */
+		console_buf_len = req->actual;
+		console_buf_read = 0;
+		memcpy(console_buf, req->buf, req->actual);
+		console_buf[req->actual] = '\0';
+		pr_vdebug("[%s] len = %d\n", console_buf, console_buf_len);
+		break;
+
+	default:
+		pr_err("%s: unexpected status error, status=%d\n",
+				__func__, req->status);
+		break;
+	}
+}
+
+/**
+ * Gets a character from the usb endpoint under the
+ * interrupt context
+ *
+ * Returns 0 or a negative error number
+ */
+static int __gs_poll_get_char(struct gs_port *port, char *ch) {
+	struct gserial *gs = port->port_usb;
+	struct usb_ep *ept = gs->out;
+	struct usb_request *usb_req;
+	int rv;
+
+	int read_ch = -EINVAL;
+
+	BUG_ON(!ept);
+
+	for (;;) {
+		read_ch = gs_poll_pop_buffer();
+		if (read_ch >= 0) {
+			break; /* got a character, done */
+		}
+
+		/**
+		 * There is nothing in buffer, start the USB endpoint to
+		 * receive something
+		 */
+
+		/* Replace complete function to intercept usb read */
+		usb_req = gs_alloc_req(ept, ept->maxpacket, GFP_ATOMIC);
+		if (!usb_req) {
+			pr_err("%s: OOM for read req\n", __func__);
+			return -ENOMEM;
+		}
+
+		/* Queue request */
+		usb_req->length = ept->maxpacket;
+		usb_req->complete = gs_poll_read_complete;
+		if ((rv = usb_ep_queue(ept, usb_req, GFP_ATOMIC))) {
+			pr_err("%s: usb_ep_queue err %d\n", __func__, rv);
+			return rv;
+		}
+
+		/* Read and free request */
+		pr_vdebug("%s: polling for read\n", __func__);
+		while ( usb_loop_poll_hw(ept, 1 /*rx*/) );
+		gs_free_req(ept, usb_req);
+	}
+
+	*ch = read_ch;
+	return 0;
+}
+
+/**
+ * gs_poll_write_complete
+ */
+static void gs_poll_write_complete(struct usb_ep *ep,
+		struct usb_request *req) {
+	/* nothing needs to be done here */
+}
+
+/**
+ * Outputs a character to the usb endpoint under the
+ * interrupt context
+ */
+static int __gs_poll_put_char(struct gs_port *port, char ch) {
+	struct gserial *gs = port->port_usb;
+	struct usb_ep *ept = gs->in;
+	struct usb_request *usb_req;
+	char send_ch = ch;
+	int rv;
+
+	BUG_ON(!ept);
+
+	usb_req = gs_alloc_req(ept, ept->maxpacket, GFP_ATOMIC);
+	if (!usb_req) {
+		pr_err("%s: OOM for read req\n", __func__);
+		return -ENOMEM;
+	}
+
+	usb_req->complete = gs_poll_write_complete;
+	memcpy(usb_req->buf, &send_ch, 1);
+	usb_req->length = 1;
+	if ((rv = usb_ep_queue(ept, usb_req, GFP_ATOMIC))) {
+		pr_err("%s: usb_ep_queue err %d\n", __func__, rv);
+		return rv;
+	}
+
+	/* Send and free request */
+	pr_vdebug("%s: polling for write\n", __func__);
+	while ( usb_loop_poll_hw(ept, 0 /*tx*/) );
+	gs_free_req(ept, usb_req);
+
+	return 0;
+}
+
+static int gs_poll_get_char(struct tty_driver *driver, int line) {
+	struct gs_port *port;
+	char char_to_read = 0;
+	int rc = 0;
+
+	if (!(line >= 0 && line < N_PORTS))
+		return -EINVAL;
+
+	if (!(port = ports[line].port))
+		return -ENODEV;
+
+	rc = __gs_poll_get_char(port, &char_to_read);
+
+	return !rc ? char_to_read : rc;
+}
+
+static void gs_poll_put_char(struct tty_driver *driver, int line, char ch) {
+	struct gs_port *port;
+
+	if (!(line >= 0 && line < N_PORTS))
+		return;
+
+	if (!(port = ports[line].port))
+		return;
+
+	__gs_poll_put_char(port, ch);
+}
+#endif /* CONFIG_CONSOLE_POLL */
-- 
1.7.0.4

