On Tuesday 08 February 2005 11:05 am, Andi Kleen wrote:
> My plan was to just drive a normal host2host cable through 
> debug port. It'll need some hacks, but should not be too bad.

I'd have expected some of them to _be_ "debug devices" in fact,
but I'm not sure how common that is.  My extremely limited sample
shows no success there ... they didn't have debug descriptors.

Much easier just to tweak the net2280 + g_serial stack ... ;)


> On Tue, Feb 08, 2005 at 10:11:25AM -0800, David Brownell wrote:
> > > It's annoying 
> > > to have to fight against such dumb code in the kernel then.
> > 
> > Do you actually have a debug port driver for Linux?  And some
> > hardware that it can talk to?
> 
> Not yet. But I have(had) plans to write one. Unfortunately 
> it dropped back a bit on the todo list, so not too much has happened
> yet:/

I have the untested guts of one ... attached, purely FYI.  Adds
a couple KBytes of object code, and doesn't care if IRQs or DMA
are working (obviously).


> > I started looking at that issue a while back:
> > 
> >   - What model to use for it?  OK, "console" for output is
> >     at least a (unidirectional) start; how about remote GDB?
> 
> Not sure I understand the q. (model?) My plan was to start with output 
> only, and then later implement receiving too.

The question is how to _use_ the port.  What data to write to
it (console data?  binary event records?), or read from it.
It's a bidirectional channel, so more featureful models are
possible, like GDB stubs for debug-as-target.


> >   - But usually one wants consoles that start working well
> >     before PCI is initialized.
> 
> Early console is a different problem. The first goal is to
> just get any console at all e.g. on laptops and those new desktops
> who don't have a serial port anymore.
> 
> For early console you could always use early pci scanning
> like the x86-64 code does for some things. It's possible, although
> not very pretty. But it's a secondary step really.

More or less what I was thinking.  With the addition of an
"once it works, someone will solve early console ..."  ;)

 
> >   - The EHCI driver currently doesn't know how to share
> >     access to the debug port.  ("mode 2" or whatever.)
> 
> It would need to be taught. Shouldn't be too hard.

No; but that gets back to the model question.  My assumption
was that something simple should work:  if the port is in use
by the debug channel when EHCI starts, then ignore it from
then on ... or at least, until the debug port driver drops it.

- Dave


> > More hardware seems to come without serial ports lately, so
> > it starts to look like Linux could benefit from a solution
> > there.  (Actually several, but the EHCI debug port option
> 
> Yes, a solution is badly needed.

/*
 * Copyright (c) 2005 by David Brownell
 * 
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * The "Debug Port" is optionally supported by EHCI controllers, and is
 * "intended to replace the legacy COM ports for debugging use as platforms
 * move towards legacy-free configurations".  It works with a specialized
 * (and at least partly nonstandard) high speed "Debug Device".
 *
 * The Linux host with a debug port normally serves as a debug target.
 * 
 * In this driver version:
 *  - Output is used as a console
 *  - Input is not implemented
 *  - PCI must work when this starts; no "earlyprintk=usb"
 *  - Debug device must present at probe() time
 *  - Does NOT (yet) coexist with EHCI HCD
 *
 * This hardware COULD be used as a GDB kernel debug stub ...
 */

#define	DEBUG

#include <linux/kernel.h>
#include <linux/console.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/pci.h>
#include <linux/usb_ch9.h>

#include <asm/unaligned.h>


MODULE_DESCRIPTION("USB2 Debug Port driver");
MODULE_LICENSE("GPL");

static const char driver_name[] = "usb_debug_port";

/*-------------------------------------------------------------------------*/

/* reuse the ehci.h definitions ... sharing of the port is established
 * through a hardware semaphore, all we really need from the HCD is
 * reusable register declarations.
 */

#include <linux/interrupt.h>
#include <linux/usb.h>
#include <linux/reboot.h>
#include "../core/hcd.h"

#define EHCI_IAA_JIFFIES	0
#define EHCI_IO_JIFFIES		0
#define EHCI_ASYNC_JIFFIES	0
#define EHCI_SHRINK_JIFFIES	0

#include "ehci.h"

/*-------------------------------------------------------------------------*/

struct dbg_port {
	spinlock_t			lock;

	struct pci_dev			*pdev;
	void __iomem			*base;

	struct ehci_caps __iomem	*caps;
	struct ehci_regs __iomem	*regs;
	struct ehci_dbg_port __iomem	*dbg;

	u32				hcs_params;
	u8				port;
	u8				toggle_out;
	u8				toggle_in;
	struct usb_debug_descriptor	desc;

	struct console			console;
};

/*
 * handshake - spin reading hc until handshake completes or fails
 * @ptr: address of hc register to be read
 * @mask: bits to look at in result of read
 * @done: value of those bits when handshake succeeds
 * @usec: timeout in microseconds
 *
 * Returns negative errno, or zero on success
 *
 * Success happens when the "mask" bits have the specified value (hardware
 * handshake done).  There are two failure modes:  "usec" have passed (major
 * hardware flakeout), or the register reads as all-ones (hardware removed).
 *
 * That last failure should_only happen in cases like physical cardbus eject
 * before driver shutdown. But it also seems to be caused by bugs in cardbus
 * bridge shutdown:  shutting down the bridge before the devices using it.
 */
static int handshake(void __iomem *ptr, u32 mask, u32 done, int usec)
{
	u32	result;

	do {
		result = readl(ptr);
		if (result == ~(u32)0)		/* card removed */
			return -ENODEV;
		result &= mask;
		if (result == done)
			return 0;
		udelay(1);
		usec--;
	} while (usec > 0);
	return -ETIMEDOUT;
}

static int
spin_done(struct dbg_port *dev)
{
	int		retval;

	/* controller checks at least every uframe */
	retval = handshake (&dev->dbg->control, DBGP_DONE, DBGP_DONE, 130);
	if (retval < 0) {
		dev_err(&dev->pdev->dev, "handshake %d?\n", retval);
		return retval;
	}
	return (readl(&dev->dbg->control) & DBGP_ERROR) ? -EPROTO : 0;
}

#define DBGP_CTRL	(DBGP_OWNER | DBGP_ENABLED | DBGP_GO)

static void dbg_write(struct console *con, const char *buf, unsigned len)
{
	struct dbg_port	*dev = container_of(con, struct dbg_port, console);
	int		retval = 0;

	while (retval == 0 && len) {
		u32	*bufp = (u32 *)buf;
		u32	buflen = min(len, (unsigned) 8);
		u32	word;

		/* allow irqs or IN packets between OUT packets */ 

		spin_lock_irq (&dev->lock);

		writel(DBGP_PID_SET(dev->toggle_out, USB_PID_OUT),
				&dev->dbg->pids);
		writel(DBGP_EPADDR(127, dev->desc.bDebugOutEndpoint),
				&dev->dbg->address);

		word = get_unaligned(bufp);
		bufp += sizeof word;
		writel(word, &dev->dbg->data03);

		if (buflen > sizeof word) {
			word = get_unaligned(bufp);
			bufp += sizeof word;
			writel(word, &dev->dbg->data47);
		}

		writel(DBGP_OUT | DBGP_CTRL | buflen, &dev->dbg->control);

		retval = spin_done(dev);
		if (retval == 0)
			word = DBGP_PID_GET(readl (&dev->dbg->pids));
		else
			word = 0;
		spin_unlock_irq (&dev->lock);

		switch (word) {
		case USB_PID_ACK:
			buf += buflen;
			len -= buflen;
			dev->toggle_out = (dev->toggle_out == USB_PID_DATA0)
					? USB_PID_DATA1
					: USB_PID_DATA0;
			break;
		case USB_PID_STALL:
			/* NOTE:  could recover by clearing halt */
			dev_err(&dev->pdev->dev, "stall?\n");
			retval = -EPIPE;
			break;
		case USB_PID_NAK:
			udelay(200);
			break;
		default:
			dev_err(&dev->pdev->dev, "write error %d\n", retval);
			break;
		}
	}
}

static void
dbg_halt(struct dbg_port *dev)
{
	if (dev->console.flags & CON_ENABLED)
		unregister_console(&dev->console);

	writel(0, &dev->dbg->control);

	// FIXME halt/reset the HC
}

static void __exit
dbg_remove(struct pci_dev *pdev)
{
	struct dbg_port		*dev = pci_get_drvdata(pdev);

	dbg_halt(dev);
	iounmap(dev->base);
	release_mem_region(pci_resource_start(pdev, 0),
				pci_resource_len(pdev, 0));
	kfree(dev);
}

static inline int __init
dbg_send_setup(struct dbg_port *dev, u8 type, u8 req, u16 wValue,
		u16 wIndex, u16 wLength)
{
	writel(DBGP_PID_SET(USB_PID_DATA0, USB_PID_SETUP), &dev->dbg->pids);

	writel((wValue << 16) | (req << 8) | type, &dev->dbg->data03);
	writel((wLength << 16) | wIndex, &dev->dbg->data47);
	writel(DBGP_OUT | DBGP_CTRL | 8, &dev->dbg->control);

	return spin_done(dev);
}

static inline int __init
dbg_status_stage(struct dbg_port *dev, u8 pid)
{
	writel(DBGP_PID_SET(USB_PID_DATA1, pid), &dev->dbg->pids);
	writel(((pid == USB_PID_OUT) ? DBGP_OUT : 0)
			| DBGP_CTRL, &dev->dbg->control);

	return spin_done(dev);
}

/* put it into "mode 1" sending only debug traffic and SYNC keepalive;
 * we guaranteed EHCI isn't using this port.
 *
 * FIXME eventually "mode 2" support would be nice, so ehci-hcd can
 * optionally use the other ports.
 */
static int __init
dbg_config(struct dbg_port *dev)
{
	u32 __iomem	*port = &dev->regs->port_status[dev->port - 1];
	u32		stat, tmp;
	int		retval;
	unsigned long	flags;

	/* VBUS on (20ms); D+ or D- (100ms); debounce (100ms) */
	writel(DBGP_OWNER, &dev->dbg->control);
	writel(PORT_POWER, port);
	stat = readl(port);
	msleep(220);
	stat = readl(port);
	if (!(stat & PORT_CONNECT))
		return -ENODEV;

	/* turn on RUN during enumeration */
	tmp = readl (&dev->regs->command);
	tmp |= CMD_RUN;
	writel (tmp, &dev->regs->command);
	(void) handshake (&dev->regs->status, STS_HALT, 0, 16 * 125);

	/* reset the device; must be high speed */
	writel(stat | PORT_RESET, port);
	msleep(50);

	/* stop reset, and make sure the port doesn't suspend */
	local_irq_save(flags);
	writel(stat, port);
	retval = handshake(port, PORT_RESET, 0, 500);
	if (retval < 0)
		return retval;

	stat = readl(port);
	if (stat & PORT_PE) {
		writel(DBGP_OWNER | DBGP_ENABLED, &dev->dbg->control);
		writel(stat & ~PORT_PE, port);
	}
	local_irq_restore(flags);

	/* turn off RUN ... no SOFs now, just SYNC keepalives */
	tmp = readl (&dev->regs->command);
	tmp &= ~CMD_RUN;
	writel (tmp, &dev->regs->command);
	(void) handshake (&dev->regs->status, STS_HALT, STS_HALT, 16 * 125);

	if (!(stat & PORT_PE))
		return -ENODEV;

	/* it could be dedicated, address 127 */
	writel(DBGP_EPADDR(0, 0), &dev->dbg->address);
	retval = dbg_send_setup(dev, USB_DIR_OUT, USB_REQ_SET_ADDRESS,
			127, 0, 0);
	if (retval < 0)
		return retval;
	(void) dbg_status_stage(dev, USB_PID_IN);
	writel(DBGP_EPADDR(127, 0), &dev->dbg->address);

	/* now we can safely read the debug descriptor */
	retval = dbg_send_setup(dev, USB_DIR_IN, USB_REQ_GET_DESCRIPTOR,
			USB_DT_DEBUG << 8, 0, 4);
	if (retval < 0)
		return retval;
// IN/4	-> fail if STALL
	retval = dbg_status_stage(dev, USB_PID_OUT);
	if (retval < 0)
		return retval;

	/* this is equivalent to set_config */
	retval = dbg_send_setup(dev, USB_DIR_OUT, USB_REQ_SET_FEATURE,
			USB_DEVICE_DEBUG_MODE, 0, 0);
	if (retval < 0)
		return retval;
	retval = dbg_status_stage(dev, USB_PID_IN);
	if (retval < 0)
		return retval;

	writel(DBGP_OWNER | DBGP_ENABLED, &dev->dbg->control);
	dev->toggle_out = dev->toggle_in = USB_PID_DATA0;

	return 0;
}

static int __init
dbg_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
	int		cap;
	int		retval;
	u16		offset;
	unsigned long	resource, len;
	void __iomem	*base;
	struct dbg_port	*dev;

	/* NOTE:  at least one NForce3 board lists a debug port in
	 * EHCI registers but not in PCI capabilities ... more error
	 * checks here may be useful.
	 */

	/* handle debug registers if they're in the normal mem region */
	cap = pci_find_capability(pdev, 0x0a);
	if (cap < 0)
		return -ENODEV;
	pci_read_config_word(pdev, cap + 2, &offset);
	if ((offset & 0xc000) != 0x2000)
		return -ENODEV;

	retval = pci_enable_device(pdev);
	if (retval < 0) {
done:
		dev_err (&pdev->dev, "init fail, %d\n", retval);
		return retval;
	}

	resource = pci_resource_start(pdev, 0);
	len = pci_resource_len(pdev, 0);
#if 1
/* THIS is what prevents coexistence with EHCI ...  it could be
 * removed once EHCI learns how to share the debug port.
 */
	if (!request_mem_region(resource, len, driver_name)) {
		retval = -EBUSY;
		goto done;
	}
#endif
	base = ioremap_nocache (resource, len);
	if (base == NULL) {
		retval = -EFAULT;
clean_1:
		release_mem_region (resource, len);
		goto done;
	}

	dev = kcalloc(1, sizeof *dev, SLAB_KERNEL);
	if (!dev) {
		retval = -ENOMEM;
clean_2:
		iounmap(dev->base);
		goto clean_1;
	}

	spin_lock_init(&dev->lock);
	dev->pdev = pdev;
	dev->base = base;
	dev->caps = base;
	dev->regs = base + HC_LENGTH (readl (&dev->caps->hc_capbase));
	dev->dbg = base + offset;
	dev->hcs_params = readl (&dev->caps->hcs_params);
	dev->port = HCS_DEBUG_PORT(dev->hcs_params);
	dev_info(&pdev->dev, "USB debug port %d\n", dev->port);

	retval = dbg_config(dev);
	if (retval < 0) {
		dev_info(&pdev->dev, "no debug device connected\n");
clean_3:
		dbg_halt(dev);
		kfree(dev);
		goto clean_2;
	}

	strncpy(dev->console.name, "usb", sizeof dev->console.name);
	dev->console.write = dbg_write;
	// dev->console.read = dbg_read;
	dev->console.flags = CON_PRINTBUFFER;
	dev->console.index = -1;
	register_console(&dev->console);
	if (!(dev->console.flags & CON_ENABLED)) {
		dev_dbg(&pdev->dev, "console not enabled\n");
		goto clean_3;
	}

	pci_set_drvdata(pdev, dev);
	return retval;
}

static const struct pci_device_id pci_ids [] = {
	{ PCI_DEVICE_CLASS(((PCI_CLASS_SERIAL_USB << 8) | 0x20), ~0), },
	{ }
};
MODULE_DEVICE_TABLE (pci, pci_ids);

static struct pci_driver dbg_pci_driver = {
	.name =		(char *) driver_name,
	.id_table =	pci_ids,

	.probe =	dbg_probe,
	.remove =	dbg_remove,
};

static int __init init(void) 
{
	return pci_register_driver(&dbg_pci_driver);
}
subsys_initcall(init);		/* pci is initialized pretty late */

static void __exit cleanup(void) 
{	
	pci_unregister_driver(&dbg_pci_driver);
}
module_exit(cleanup);

Reply via email to