Skip to contentSkip to conversation

serial: 8250 driver improvements & Cavium OCTEON serial support

PATCH In review David Daney 2 participants 7 messages Oct 7, 2008 – Oct 8, 2008

Subsystems (3)
lore
Share & tools
lore
#0 lore raw

cover serial: 8250 driver improvements & Cavium OCTEON serial support

This is a follow up to the 'Allow for replaceable I/O functions in 8250 driver' patch I sent yesterday. I hope it addresses the issues raised by Alan Cox and Arnd Bergmann.

The four parts of the patch are as follows:

1/4) Add replaceable I/O functions to the 8250 driver. This allows
platform specific register access code to be moved out of the driver into the platform support files.

2/4) Add a new port flag UPF_FIXED_TYPE that allows callers of
serial8250_register_port() to specify the port type and disables probing.

3/4) Add a 'bugs' field to the serial8250_config. Used in conjunction
with 2/4, this allows the bugs flags to be set without probing.

4/4) Add an entry to uart_config for PORT_OCTEON describing the
OCTEON's internal UARTs. Two new bug flags are defined to account for PORT_OCTEON's peculiarities.

Comments welcome,

David Daney

#1 lore raw

PATCH 1/4 serial: 8250 driver replaceable i/o functions.

In order to use Cavium OCTEON specific serial i/o drivers, we first patch the 8250 driver to use replaceable i/o functions.

---

drivers/serial/8250.c       |  185 +++++++++++++++++++++++++++++--------------
include/linux/serial_8250.h |    2 +
include/linux/serial_core.h |    2 +
3 files changed, 131 insertions(+), 58 deletions(-)
drivers/serial/8250.c +0 -0
index 9ccc563..02771d6 100644
@@ -288,16 +288,16 @@ static const u8 au_io_out_map[] = {

};

/* sane hardware needs no mapping */
-static inline int map_8250_in_reg(struct uart_8250_port *up, int offset) +static inline int map_8250_in_reg(struct uart_port *p, int offset) {
- if (up->port.iotype != UPIO_AU)
+ if (p->iotype != UPIO_AU)

		return offset;
	return au_io_in_map[offset];
}

-static inline int map_8250_out_reg(struct uart_8250_port *up, int offset) +static inline int map_8250_out_reg(struct uart_port *p, int offset) {
- if (up->port.iotype != UPIO_AU)
+ if (p->iotype != UPIO_AU)

		return offset;
	return au_io_out_map[offset];
}
@@ -326,16 +326,16 @@ static const u8
		[UART_SCR]	= 0x2c
	};

-static inline int map_8250_in_reg(struct uart_8250_port *up, int offset) +static inline int map_8250_in_reg(struct uart_port *p, int offset) {
- if (up->port.iotype != UPIO_RM9000)
+ if (p->iotype != UPIO_RM9000)

		return offset;
	return regmap_in[offset];
}

-static inline int map_8250_out_reg(struct uart_8250_port *up, int offset) +static inline int map_8250_out_reg(struct uart_port *p, int offset) {
- if (up->port.iotype != UPIO_RM9000)
+ if (p->iotype != UPIO_RM9000)

		return offset;
	return regmap_out[offset];
}

@@ -348,108 +348,170 @@ static inline int map_8250_out_reg(struct uart_8250_port *up, int offset)

#endif

-static unsigned int serial_in(struct uart_8250_port *up, int offset) +static unsigned int hub6_serial_in_fn(struct uart_port *p, int offset) {
- unsigned int tmp;
- offset = map_8250_in_reg(up, offset) << up->port.regshift;
+ offset = map_8250_in_reg(p, offset) << p->regshift;
+ outb(p->hub6 - 1 + offset, p->iobase);
+ return inb(p->iobase + 1);
+}

- switch (up->port.iotype) {
- case UPIO_HUB6:
- outb(up->port.hub6 - 1 + offset, up->port.iobase);
- return inb(up->port.iobase + 1);
+static void hub6_serial_out_fn(struct uart_port *p, int offset, int value) +{
+ offset = map_8250_out_reg(p, offset) << p->regshift;
+ outb(p->hub6 - 1 + offset, p->iobase);
+ outb(value, p->iobase + 1);
+}

- case UPIO_MEM:
- case UPIO_DWAPB:
- return readb(up->port.membase + offset);
+static unsigned int mem_serial_in_fn(struct uart_port *p, int offset) +{
+ offset = map_8250_in_reg(p, offset) << p->regshift;
+ return readb(p->membase + offset);
+}

- case UPIO_RM9000:
- case UPIO_MEM32:
- return readl(up->port.membase + offset);
+static void mem_serial_out_fn(struct uart_port *p, int offset, int value) +{
+ offset = map_8250_out_reg(p, offset) << p->regshift;
+ writeb(value, p->membase + offset);
+}
+
+static void mem32_serial_out_fn(struct uart_port *p, int offset, int value) +{
+ offset = map_8250_out_reg(p, offset) << p->regshift;
+ writel(value, p->membase + offset);
+}
+
+static unsigned int mem32_serial_in_fn(struct uart_port *p, int offset) +{
+ offset = map_8250_in_reg(p, offset) << p->regshift;
+ return readl(p->membase + offset);
+}

#ifdef CONFIG_SERIAL_8250_AU1X00
- case UPIO_AU:
- return __raw_readl(up->port.membase + offset);
+static unsigned int au_serial_in_fn(struct uart_port *p, int offset) +{
+ offset = map_8250_in_reg(p, offset) << p->regshift;
+ return __raw_readl(p->membase + offset);
+}
+
+static void au_serial_out_fn(struct uart_port *p, int offset, int value) +{
+ offset = map_8250_out_reg(p, offset) << p->regshift;
+ __raw_writel(value, p->membase + offset);
+}
#endif

- case UPIO_TSI:
- if (offset == UART_IIR) {
- tmp = readl(up->port.membase + (UART_IIR & ~3));
- return (tmp >> 16) & 0xff; /* UART_IIR % 4 == 2 */
- } else
- return readb(up->port.membase + offset);
+static unsigned int tsi_serial_in_fn(struct uart_port *p, int offset) +{
+ unsigned int tmp;
+ offset = map_8250_in_reg(p, offset) << p->regshift;
+ if (offset == UART_IIR) {
+ tmp = readl(p->membase + (UART_IIR & ~3));
+ return (tmp >> 16) & 0xff; /* UART_IIR % 4 == 2 */
+ } else
+ return readb(p->membase + offset);
+}

- default:
- return inb(up->port.iobase + offset);
- }
+static void tsi_serial_out_fn(struct uart_port *p, int offset, int value) +{
+ offset = map_8250_out_reg(p, offset) << p->regshift;
+ if (!((offset == UART_IER) && (value & UART_IER_UUE)))
+ writeb(value, p->membase + offset);
}

-static void
-serial_out(struct uart_8250_port *up, int offset, int value) +static void dwapb_serial_out_fn(struct uart_port *p, int offset, int value) {
- /* Save the offset before it's remapped */

	int save_offset = offset;

- offset = map_8250_out_reg(up, offset) << up->port.regshift;
+ offset = map_8250_out_reg(p, offset) << p->regshift;
+ /* Save the LCR value so it can be re-written when a
+ * Busy Detect interrupt occurs. */
+ if (save_offset == UART_LCR) {
+ struct uart_8250_port *up = (struct uart_8250_port *)p;
+ up->lcr = value;
+ }
+ writeb(value, p->membase + offset);
+ /* Read the IER to ensure any interrupt is cleared before
+ * returning from ISR. */
+ if (save_offset == UART_TX || save_offset == UART_IER)
+ value = p->serial_in_fn(p, UART_IER);
+}

- switch (up->port.iotype) {
+static unsigned int io_serial_in_fn(struct uart_port *p, int offset) +{
+ offset = map_8250_in_reg(p, offset) << p->regshift;
+ return inb(p->iobase + offset);
+}
+
+static void io_serial_out_fn(struct uart_port *p, int offset, int value) +{
+ offset = map_8250_out_reg(p, offset) << p->regshift;
+ outb(value, p->iobase + offset);
+}
+
+static void set_io_fns_from_upio(struct uart_port *p)
+{
+ switch (p->iotype) {
case UPIO_HUB6:
- outb(up->port.hub6 - 1 + offset, up->port.iobase);
- outb(value, up->port.iobase + 1);
+ p->serial_in_fn = hub6_serial_in_fn;

+		p->serial_out_fn = hub6_serial_out_fn;
		break;

	case UPIO_MEM:
-		writeb(value, up->port.membase + offset);
+		p->serial_in_fn = mem_serial_in_fn;
+		p->serial_out_fn = mem_serial_out_fn;
		break;

	case UPIO_RM9000:
	case UPIO_MEM32:
-		writel(value, up->port.membase + offset);
+		p->serial_in_fn = mem32_serial_in_fn;
+		p->serial_out_fn = mem32_serial_out_fn;
		break;

#ifdef CONFIG_SERIAL_8250_AU1X00
case UPIO_AU:
- __raw_writel(value, up->port.membase + offset);
+ p->serial_in_fn = au_serial_in_fn;

+		p->serial_out_fn = au_serial_out_fn;
		break;

#endif
case UPIO_TSI:
- if (!((offset == UART_IER) && (value & UART_IER_UUE)))
- writeb(value, up->port.membase + offset);
+ p->serial_in_fn = tsi_serial_in_fn;

+		p->serial_out_fn = tsi_serial_out_fn;
		break;

	case UPIO_DWAPB:

- /* Save the LCR value so it can be re-written when a
- * Busy Detect interrupt occurs. */
- if (save_offset == UART_LCR)
- up->lcr = value;
- writeb(value, up->port.membase + offset);
- /* Read the IER to ensure any interrupt is cleared before
- * returning from ISR. */
- if (save_offset == UART_TX || save_offset == UART_IER)
- value = serial_in(up, UART_IER);
+ p->serial_in_fn = mem_serial_in_fn;

+		p->serial_out_fn = dwapb_serial_out_fn;
		break;

	default:
-		outb(value, up->port.iobase + offset);
+		p->serial_in_fn = io_serial_in_fn;
+		p->serial_out_fn = io_serial_out_fn;
+		break;
	}
}

static void
serial_out_sync(struct uart_8250_port *up, int offset, int value) {
- switch (up->port.iotype) {
+ struct uart_port *p = &up->port;
+ switch (p->iotype) {
case UPIO_MEM:
case UPIO_MEM32:
#ifdef CONFIG_SERIAL_8250_AU1X00
case UPIO_AU:
#endif
case UPIO_DWAPB:
- serial_out(up, offset, value);
- serial_in(up, UART_LCR); /* safe, no side-effects */
+ p->serial_out_fn(p, offset, value);
+ p->serial_in_fn(p, UART_LCR); /* safe, no side-effects */

		break;
	default:
-		serial_out(up, offset, value);
+		p->serial_out_fn(p, offset, value);
	}
}

+#define serial_in(up, offset) \
+ (up->port.serial_in_fn(&(up)->port, (offset)))
+#define serial_out(up, offset, value) \
+ (up->port.serial_out_fn(&(up)->port, (offset), (value)))
/*
* We used to support using pause I/O for certain machines. We
* haven't supported this for a while, but just in case it's badly
@@ -2511,6 +2573,7 @@ static void __init serial8250_isa_init_ports(void)

		up->port.membase  = old_serial_port[i].iomem_base;
		up->port.iotype   = old_serial_port[i].io_type;
		up->port.regshift = old_serial_port[i].iomem_reg_shift;
+		set_io_fns_from_upio(&up->port);
		if (share_irqs)
			up->port.flags |= UPF_SHARE_IRQ;
	}
@@ -2912,6 +2975,12 @@ int serial8250_register_port(struct uart_port *port)
		uart->port.private_data = port->private_data;
		if (port->dev)
			uart->port.dev = port->dev;

+ set_io_fns_from_upio(&uart->port);
+ /* Possibly override default I/O functions. */
+ if (port->serial_in_fn)
+ uart->port.serial_in_fn = port->serial_in_fn;
+ if (port->serial_out_fn)
+ uart->port.serial_out_fn = port->serial_out_fn;

		ret = uart_add_one_port(&serial8250_reg, &uart->port);
		if (ret == 0)
include/linux/serial_8250.h +0 -0
index 3d37c94..eb08b04 100644
@@ -28,6 +28,8 @@ struct plat_serial8250_port {
	unsigned char	iotype;		/* UPIO_* */
	unsigned char	hub6;
	upf_t		flags;		/* UPF_* flags */

+ unsigned int (*serial_in_fn)(struct uart_port *, int);
+ void (*serial_out_fn)(struct uart_port *, int, int);
};

/*

include/linux/serial_core.h +0 -0
index 3b2f6c0..3a4afcf 100644
@@ -243,6 +243,8 @@ struct uart_port {
	spinlock_t		lock;			/* port lock */
	unsigned int		iobase;			/* in/out[bwl] */
	unsigned char __iomem	*membase;		/* read/write[bwl] */

+ unsigned int (*serial_in_fn)(struct uart_port *, int);
+ void (*serial_out_fn)(struct uart_port *, int, int);
unsigned int irq; /* irq number */
unsigned int uartclk; /* base uart clock */
unsigned int fifosize; /* tx fifo size */

#2 lore raw

Re: [PATCH 1/4] serial: 8250 driver replaceable i/o functions.

Quoted 9 lines · David Daney

/* sane hardware needs no mapping */

(quoted diff) +1 -1
-static inline int map_8250_in_reg(struct uart_8250_port *up, int offset)
+static inline int map_8250_in_reg(struct uart_port *p, int offset)

{

(quoted diff) +1 -1
-    if (up->port.iotype != UPIO_AU)
+    if (p->iotype != UPIO_AU)
        return offset;
    return au_io_in_map[offset];

}

With your changes, these functions cannot be called with p->iotype != UPIO_AU anymore, correct? So there is no need for this test...

-hpa

#3 lore raw

Re: [PATCH 1/4] serial: 8250 driver replaceable i/o functions.

Quoted 13 lines · H. Peter Anvin
Quoted 9 lines · David Daney

/* sane hardware needs no mapping */

(quoted diff) +1 -1
-static inline int map_8250_in_reg(struct uart_8250_port *up, int offset)
+static inline int map_8250_in_reg(struct uart_port *p, int offset)

{

(quoted diff) +1 -1
-    if (up->port.iotype != UPIO_AU)
+    if (p->iotype != UPIO_AU)
        return offset;
    return au_io_in_map[offset];

}

With your changes, these functions cannot be called with p->iotype != UPIO_AU anymore, correct? So there is no need for this test...

I think you are probably correct. However, with the patch it is possible to move all this target specific code out of the driver. So if the patch is accepted, a better follow up would be to get rid of the UPIO_AU things altogether.

I gave an example of how that could be done with UPIO_TSI here:

http://marc.info/?l=linux-serial&m=122333633802691&w=2

David Daney

#4 lore raw

PATCH 2/4 serial: Allow port type to be specified when calling serial8250_register_port.

In reply to David Daney

Allow port type to be specified when calling serial8250_register_port.

Add flag value UPF_FIXED_TYPE which specifies that the UART type is known and should not be probed. For this case the UARTs properties are just copied out of the uart_config entry.

---

drivers/serial/8250.c        |    8 ++++++++
drivers/serial/serial_core.c |    7 +++++--
include/linux/serial_core.h  |    2 ++
3 files changed, 15 insertions(+), 2 deletions(-)
drivers/serial/8250.c +0 -0
index 02771d6..c575b61 100644
@@ -2975,6 +2975,14 @@ int serial8250_register_port(struct uart_port *port)
		uart->port.private_data = port->private_data;
		if (port->dev)
			uart->port.dev = port->dev;

+
+ if (port->flags & UPF_FIXED_TYPE) {
+ uart->port.type = port->type;
+ uart->port.fifosize = uart_config[port->type].fifo_size;
+ uart->capabilities = uart_config[port->type].flags;
+ uart->tx_loadsz = uart_config[port->type].tx_loadsz;
+ }
+

		set_io_fns_from_upio(&uart->port);
		/* Possibly override default I/O functions.  */
		if (port->serial_in_fn)
drivers/serial/serial_core.c +0 -0
index f977c98..65b8a74 100644
@@ -2196,11 +2196,14 @@ uart_configure_port(struct uart_driver *drv, struct uart_state *state,

* Now do the auto configuration stuff. Note that config_port
* is expected to claim the resources and map the port for us. */
- flags = UART_CONFIG_TYPE;

+	flags = 0;
	if (port->flags & UPF_AUTO_IRQ)
		flags |= UART_CONFIG_IRQ;
	if (port->flags & UPF_BOOT_AUTOCONF) {

- port->type = PORT_UNKNOWN;
+ if (!(port->flags & UPF_FIXED_TYPE)) {
+ port->type = PORT_UNKNOWN;
+ flags |= UART_CONFIG_TYPE;
+ }

		port->ops->config_port(port, flags);
	}
include/linux/serial_core.h +0 -0
index 3a4afcf..c68dc94 100644
@@ -290,6 +290,8 @@ struct uart_port {

#define UPF_MAGIC_MULTIPLIER ((__force upf_t) (1 << 16))
#define UPF_CONS_FLOW ((__force upf_t) (1 << 23))
#define UPF_SHARE_IRQ ((__force upf_t) (1 << 24))
+/* The exact UART type is known and should not be probed. */
+#define UPF_FIXED_TYPE ((__force upf_t) (1 << 27))
#define UPF_BOOT_AUTOCONF ((__force upf_t) (1 << 28))
#define UPF_FIXED_PORT ((__force upf_t) (1 << 29))
#define UPF_DEAD ((__force upf_t) (1 << 30))

#5 lore raw

PATCH 3/4 serial: Allow port type to specify bugs that are not probed for.

In reply to David Daney

Allow port type to specify bugs that are not probed for.

Add a bugs field to the serial8250_config and propagate it to the port's bugs field when the port is registered and configured.

---

drivers/serial/8250.c |    2 ++
drivers/serial/8250.h |    1 +
2 files changed, 3 insertions(+), 0 deletions(-)
drivers/serial/8250.c +0 -0
index c575b61..19a8373 100644
@@ -1197,6 +1197,7 @@ static void autoconfig(struct uart_8250_port *up, unsigned int probeflags)
	up->port.fifosize = uart_config[up->port.type].fifo_size;
	up->capabilities = uart_config[up->port.type].flags;
	up->tx_loadsz = uart_config[up->port.type].tx_loadsz;

+ up->bugs |= uart_config[up->port.type].bugs;

	if (up->port.type == PORT_UNKNOWN)
		goto out;
@@ -2981,6 +2982,7 @@ int serial8250_register_port(struct uart_port *port)
			uart->port.fifosize = uart_config[port->type].fifo_size;
			uart->capabilities = uart_config[port->type].flags;
			uart->tx_loadsz = uart_config[port->type].tx_loadsz;
+			uart->bugs = uart_config[port->type].bugs;
		}

		set_io_fns_from_upio(&uart->port);
drivers/serial/8250.h +0 -0
index 5202603..c9b3002 100644
@@ -34,6 +34,7 @@ struct serial8250_config {
	const char	*name;
	unsigned short	fifo_size;
	unsigned short	tx_loadsz;
+	unsigned short	bugs;
	unsigned char	fcr;
	unsigned int	flags;
};
#6 lore raw

PATCH 4/4 serial: Add new uart_config for PORT_OCTEON

In reply to David Daney

Cavium UART implementation won't work with the standard 8250 driver as-is. Define a new uart_config (PORT_OCTEON) and use it to enable special handling required by the OCTEON's serial port.

---

drivers/serial/8250.c       |   16 +++++++++++++++-
drivers/serial/8250.h       |    2 ++
include/linux/serial_core.h |    3 ++-
include/linux/serial_reg.h  |    6 ++++++
4 files changed, 25 insertions(+), 2 deletions(-)
drivers/serial/8250.c +0 -0
index 19a8373..7afd07f 100644
@@ -264,6 +264,14 @@ static const struct serial8250_config uart_config[] = {
		.fcr		= UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIG_10,
		.flags		= UART_CAP_FIFO,
	},

+ [PORT_OCTEON] = {
+ .name = "OCTEON",
+ .fifo_size = 64,
+ .tx_loadsz = 64,
+ .bugs = UART_BUG_TIMEOUT | UART_BUG_OCTEON_IIR,
+ .fcr = UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIG_10,
+ .flags = UART_CAP_FIFO,
+ },
};

#if defined (CONFIG_SERIAL_8250_AU1X00)
@@ -1540,6 +1548,12 @@ static irqreturn_t serial8250_interrupt(int irq, void *dev_id)

		up = list_entry(l, struct uart_8250_port, list);

		iir = serial_in(up, UART_IIR);

+ if ((up->bugs & UART_BUG_OCTEON_IIR) && (iir & 0xf) == 7) {
+ /* Busy interrupt */
+ serial_in(up, UART_OCTEON_USR);
+ iir = serial_in(up, UART_IIR);
+ }
+

		if (!(iir & UART_IIR_NO_INT)) {
			serial8250_handle_port(up);

@@ -1658,7 +1672,7 @@ static void serial8250_timeout(unsigned long data)

	unsigned int iir;

	iir = serial_in(up, UART_IIR);
-	if (!(iir & UART_IIR_NO_INT))
+	if (!(iir & UART_IIR_NO_INT) || (up->bugs & UART_BUG_TIMEOUT))
		serial8250_handle_port(up);
	mod_timer(&up->timer, jiffies + poll_timeout(up->port.timeout));
}
drivers/serial/8250.h +0 -0
index c9b3002..56b3cb7 100644
@@ -49,6 +49,8 @@ struct serial8250_config {

#define UART_BUG_TXEN (1 << 1) /* UART has buggy TX IIR status */
#define UART_BUG_NOMSR (1 << 2) /* UART has buggy MSR status bits (Au1x00) */
#define UART_BUG_THRE (1 << 3) /* UART has buggy THRE reassertion */
+#define UART_BUG_TIMEOUT (1 << 4) /* UART should always handle timeout */
+#define UART_BUG_OCTEON_IIR (1 << 5) /* UART OCTEON IIR workaround */

#define PROBE_RSA (1 << 0)
#define PROBE_ANY (~0)

include/linux/serial_core.h +0 -0
index c68dc94..c920c43 100644
@@ -40,7 +40,8 @@

#define PORT_NS16550A 14
#define PORT_XSCALE 15
#define PORT_RM9000 16 /* PMC-Sierra RM9xxx internal UART */
-#define PORT_MAX_8250 16 /* max port ID */
+#define PORT_OCTEON 17 /* Cavium OCTEON internal UART */
+#define PORT_MAX_8250 17 /* max port ID */

/*
* ARM specific type numbers. These are not currently guaranteed

include/linux/serial_reg.h +0 -0
index 96c0d93..a96bd50 100644
@@ -324,5 +324,11 @@

#define UART_OMAP_SYSC 0x15 /* System configuration register */
#define UART_OMAP_SYSS 0x16 /* System status register */

+/*
+ * Extra serial register definitions for the internal UARTs in Cavium
+ * Networks OCTEON processors.
+ */
+#define UART_OCTEON_USR 0x27 /* UART Status Register */
+
#endif /* _LINUX_SERIAL_REG_H */