[POWERPC] PS3: Vuart rework

PS3 vuart updates to reflect the new PS3 unified device support.
 - Move vuart devices to the PS3 system bus.
 - Replace use of ps3_vuart_port_device with ps3_system_bus_device.
 - Make the PS3 vuart bus driver a loadable module.
 - Add remove() and shutdown() routines.
 - Move ps3_vuart_work into ps3_vuart_port_priv.tx_list.
 - Remove redundant spinlock ps3_vuart_work.lock.
 - No longer free ps3_vuart_port_device.priv on shutdown.
 - Cleanup Kconfig defs.
 - Export symbols needed for modular port drivers.
 - Arrange to use port numbers found in repository.
 - Fix bugs in ps3_vuart_read_async() and polled reading
 - Cleanup handling of shared interrupt with ps3_vuart_bus_interrupt_get()
   and ps3_vuart_bus_interrupt_put()
 - Add more comments to vuart.c.

Signed-off-by: Geoff Levand <geoffrey.levand@am.sony.com>
Signed-off-by: Paul Mackerras <paulus@samba.org>
diff --git a/arch/powerpc/platforms/ps3/Kconfig b/arch/powerpc/platforms/ps3/Kconfig
index 40f0008..b5122a7 100644
--- a/arch/powerpc/platforms/ps3/Kconfig
+++ b/arch/powerpc/platforms/ps3/Kconfig
@@ -73,18 +73,12 @@
 
 config PS3_VUART
 	depends on PPC_PS3
-	bool "PS3 Virtual UART support" if PS3_ADVANCED
-	default y
-	help
-	  Include support for the PS3 Virtual UART.
-
-	  This support is required for several system services
-	  including the System Manager and AV Settings.  In
-	  general, all users will say Y.
+	tristate
 
 config PS3_PS3AV
+	depends on PPC_PS3
 	tristate "PS3 AV settings driver" if PS3_ADVANCED
-	depends on PS3_VUART
+	select PS3_VUART
 	default y
 	help
 	  Include support for the PS3 AV Settings driver.
@@ -93,13 +87,14 @@
 	  general, all users will say Y or M.
 
 config PS3_SYS_MANAGER
-	bool "PS3 System Manager driver" if PS3_ADVANCED
-	depends on PS3_VUART
-	default y
+	depends on PPC_PS3
+	tristate "PS3 System Manager driver" if PS3_ADVANCED
+	select PS3_VUART
+	default m
 	help
 	  Include support for the PS3 System Manager.
 
 	  This support is required for system control.  In
-	  general, all users will say Y.
+	  general, all users will say Y or M.
 
 endmenu
diff --git a/arch/powerpc/platforms/ps3/interrupt.c b/arch/powerpc/platforms/ps3/interrupt.c
index 51141dc..99a0826 100644
--- a/arch/powerpc/platforms/ps3/interrupt.c
+++ b/arch/powerpc/platforms/ps3/interrupt.c
@@ -564,6 +564,7 @@
 
 	return result;
 }
+EXPORT_SYMBOL_GPL(ps3_vuart_irq_setup);
 
 int ps3_vuart_irq_destroy(unsigned int virq)
 {
@@ -583,6 +584,7 @@
 
 	return result;
 }
+EXPORT_SYMBOL_GPL(ps3_vuart_irq_destroy);
 
 /**
  * ps3_spe_irq_setup - Setup an spe virq.
diff --git a/drivers/ps3/vuart.c b/drivers/ps3/vuart.c
index 5333fb2..bea25a1 100644
--- a/drivers/ps3/vuart.c
+++ b/drivers/ps3/vuart.c
@@ -71,6 +71,34 @@
 };
 
 /**
+ * struct ps3_vuart_port_priv - private vuart device data.
+ */
+
+struct ps3_vuart_port_priv {
+	u64 interrupt_mask;
+
+	struct {
+		spinlock_t lock;
+		struct list_head head;
+	} tx_list;
+	struct {
+		struct ps3_vuart_work work;
+		unsigned long bytes_held;
+		spinlock_t lock;
+		struct list_head head;
+	} rx_list;
+	struct ps3_vuart_stats stats;
+};
+
+static struct ps3_vuart_port_priv *to_port_priv(
+	struct ps3_system_bus_device *dev)
+{
+	BUG_ON(!dev);
+	BUG_ON(!dev->driver_priv);
+	return (struct ps3_vuart_port_priv *)dev->driver_priv;
+}
+
+/**
  * struct ports_bmp - bitmap indicating ports needing service.
  *
  * A 256 bit read only bitmap indicating ports needing service.  Do not write
@@ -89,23 +117,6 @@
 	pr_debug("%s:%d: ports_bmp: %016lxh\n", func, line, bmp->status);
 }
 
-static int ps3_vuart_match_id_to_port(enum ps3_match_id match_id,
-	unsigned int *port_number)
-{
-	switch(match_id) {
-	case PS3_MATCH_ID_AV_SETTINGS:
-		*port_number = 0;
-		return 0;
-	case PS3_MATCH_ID_SYSTEM_MANAGER:
-		*port_number = 2;
-		return 0;
-	default:
-		WARN_ON(1);
-		*port_number = UINT_MAX;
-		return -EINVAL;
-	};
-}
-
 #define dump_port_params(_b) _dump_port_params(_b, __func__, __LINE__)
 static void __maybe_unused _dump_port_params(unsigned int port_number,
 	const char* func, int line)
@@ -144,14 +155,14 @@
 	unsigned long tx;
 };
 
-int ps3_vuart_get_triggers(struct ps3_vuart_port_device *dev,
+int ps3_vuart_get_triggers(struct ps3_system_bus_device *dev,
 	struct vuart_triggers *trig)
 {
 	int result;
 	unsigned long size;
 	unsigned long val;
 
-	result = lv1_get_virtual_uart_param(dev->priv->port_number,
+	result = lv1_get_virtual_uart_param(dev->port_number,
 		PARAM_TX_TRIGGER, &trig->tx);
 
 	if (result) {
@@ -160,7 +171,7 @@
 		return result;
 	}
 
-	result = lv1_get_virtual_uart_param(dev->priv->port_number,
+	result = lv1_get_virtual_uart_param(dev->port_number,
 		PARAM_RX_BUF_SIZE, &size);
 
 	if (result) {
@@ -169,7 +180,7 @@
 		return result;
 	}
 
-	result = lv1_get_virtual_uart_param(dev->priv->port_number,
+	result = lv1_get_virtual_uart_param(dev->port_number,
 		PARAM_RX_TRIGGER, &val);
 
 	if (result) {
@@ -186,13 +197,13 @@
 	return result;
 }
 
-int ps3_vuart_set_triggers(struct ps3_vuart_port_device *dev, unsigned int tx,
+int ps3_vuart_set_triggers(struct ps3_system_bus_device *dev, unsigned int tx,
 	unsigned int rx)
 {
 	int result;
 	unsigned long size;
 
-	result = lv1_set_virtual_uart_param(dev->priv->port_number,
+	result = lv1_set_virtual_uart_param(dev->port_number,
 		PARAM_TX_TRIGGER, tx);
 
 	if (result) {
@@ -201,7 +212,7 @@
 		return result;
 	}
 
-	result = lv1_get_virtual_uart_param(dev->priv->port_number,
+	result = lv1_get_virtual_uart_param(dev->port_number,
 		PARAM_RX_BUF_SIZE, &size);
 
 	if (result) {
@@ -210,7 +221,7 @@
 		return result;
 	}
 
-	result = lv1_set_virtual_uart_param(dev->priv->port_number,
+	result = lv1_set_virtual_uart_param(dev->port_number,
 		PARAM_RX_TRIGGER, size - rx);
 
 	if (result) {
@@ -225,10 +236,12 @@
 	return result;
 }
 
-static int ps3_vuart_get_rx_bytes_waiting(struct ps3_vuart_port_device *dev,
+static int ps3_vuart_get_rx_bytes_waiting(struct ps3_system_bus_device *dev,
 	u64 *bytes_waiting)
 {
-	int result = lv1_get_virtual_uart_param(dev->priv->port_number,
+	int result;
+
+	result = lv1_get_virtual_uart_param(dev->port_number,
 		PARAM_RX_BYTES, bytes_waiting);
 
 	if (result)
@@ -240,17 +253,24 @@
 	return result;
 }
 
-static int ps3_vuart_set_interrupt_mask(struct ps3_vuart_port_device *dev,
+/**
+ * ps3_vuart_set_interrupt_mask - Enable/disable the port interrupt sources.
+ * @dev: The struct ps3_system_bus_device instance.
+ * @bmp: Logical OR of enum vuart_interrupt_mask values. A zero bit disables.
+ */
+
+static int ps3_vuart_set_interrupt_mask(struct ps3_system_bus_device *dev,
 	unsigned long mask)
 {
 	int result;
+	struct ps3_vuart_port_priv *priv = to_port_priv(dev);
 
 	dev_dbg(&dev->core, "%s:%d: %lxh\n", __func__, __LINE__, mask);
 
-	dev->priv->interrupt_mask = mask;
+	priv->interrupt_mask = mask;
 
-	result = lv1_set_virtual_uart_param(dev->priv->port_number,
-		PARAM_INTERRUPT_MASK, dev->priv->interrupt_mask);
+	result = lv1_set_virtual_uart_param(dev->port_number,
+		PARAM_INTERRUPT_MASK, priv->interrupt_mask);
 
 	if (result)
 		dev_dbg(&dev->core, "%s:%d: interrupt_mask failed: %s\n",
@@ -259,79 +279,96 @@
 	return result;
 }
 
-static int ps3_vuart_get_interrupt_status(struct ps3_vuart_port_device *dev,
+static int ps3_vuart_get_interrupt_status(struct ps3_system_bus_device *dev,
 	unsigned long *status)
 {
+	int result;
+	struct ps3_vuart_port_priv *priv = to_port_priv(dev);
 	u64 tmp;
-	int result = lv1_get_virtual_uart_param(dev->priv->port_number,
+
+	result = lv1_get_virtual_uart_param(dev->port_number,
 		PARAM_INTERRUPT_STATUS, &tmp);
 
 	if (result)
 		dev_dbg(&dev->core, "%s:%d: interrupt_status failed: %s\n",
 			__func__, __LINE__, ps3_result(result));
 
-	*status = tmp & dev->priv->interrupt_mask;
+	*status = tmp & priv->interrupt_mask;
 
 	dev_dbg(&dev->core, "%s:%d: m %lxh, s %lxh, m&s %lxh\n",
-		__func__, __LINE__, dev->priv->interrupt_mask, tmp, *status);
+		__func__, __LINE__, priv->interrupt_mask, tmp, *status);
 
 	return result;
 }
 
-int ps3_vuart_enable_interrupt_tx(struct ps3_vuart_port_device *dev)
+int ps3_vuart_enable_interrupt_tx(struct ps3_system_bus_device *dev)
 {
-	return (dev->priv->interrupt_mask & INTERRUPT_MASK_TX) ? 0
-		: ps3_vuart_set_interrupt_mask(dev, dev->priv->interrupt_mask
+	struct ps3_vuart_port_priv *priv = to_port_priv(dev);
+
+	return (priv->interrupt_mask & INTERRUPT_MASK_TX) ? 0
+		: ps3_vuart_set_interrupt_mask(dev, priv->interrupt_mask
 		| INTERRUPT_MASK_TX);
 }
 
-int ps3_vuart_enable_interrupt_rx(struct ps3_vuart_port_device *dev)
+int ps3_vuart_enable_interrupt_rx(struct ps3_system_bus_device *dev)
 {
-	return (dev->priv->interrupt_mask & INTERRUPT_MASK_RX) ? 0
-		: ps3_vuart_set_interrupt_mask(dev, dev->priv->interrupt_mask
+	struct ps3_vuart_port_priv *priv = to_port_priv(dev);
+
+	return (priv->interrupt_mask & INTERRUPT_MASK_RX) ? 0
+		: ps3_vuart_set_interrupt_mask(dev, priv->interrupt_mask
 		| INTERRUPT_MASK_RX);
 }
 
-int ps3_vuart_enable_interrupt_disconnect(struct ps3_vuart_port_device *dev)
+int ps3_vuart_enable_interrupt_disconnect(struct ps3_system_bus_device *dev)
 {
-	return (dev->priv->interrupt_mask & INTERRUPT_MASK_DISCONNECT) ? 0
-		: ps3_vuart_set_interrupt_mask(dev, dev->priv->interrupt_mask
+	struct ps3_vuart_port_priv *priv = to_port_priv(dev);
+
+	return (priv->interrupt_mask & INTERRUPT_MASK_DISCONNECT) ? 0
+		: ps3_vuart_set_interrupt_mask(dev, priv->interrupt_mask
 		| INTERRUPT_MASK_DISCONNECT);
 }
 
-int ps3_vuart_disable_interrupt_tx(struct ps3_vuart_port_device *dev)
+int ps3_vuart_disable_interrupt_tx(struct ps3_system_bus_device *dev)
 {
-	return (dev->priv->interrupt_mask & INTERRUPT_MASK_TX)
-		? ps3_vuart_set_interrupt_mask(dev, dev->priv->interrupt_mask
+	struct ps3_vuart_port_priv *priv = to_port_priv(dev);
+
+	return (priv->interrupt_mask & INTERRUPT_MASK_TX)
+		? ps3_vuart_set_interrupt_mask(dev, priv->interrupt_mask
 		& ~INTERRUPT_MASK_TX) : 0;
 }
 
-int ps3_vuart_disable_interrupt_rx(struct ps3_vuart_port_device *dev)
+int ps3_vuart_disable_interrupt_rx(struct ps3_system_bus_device *dev)
 {
-	return (dev->priv->interrupt_mask & INTERRUPT_MASK_RX)
-		? ps3_vuart_set_interrupt_mask(dev, dev->priv->interrupt_mask
+	struct ps3_vuart_port_priv *priv = to_port_priv(dev);
+
+	return (priv->interrupt_mask & INTERRUPT_MASK_RX)
+		? ps3_vuart_set_interrupt_mask(dev, priv->interrupt_mask
 		& ~INTERRUPT_MASK_RX) : 0;
 }
 
-int ps3_vuart_disable_interrupt_disconnect(struct ps3_vuart_port_device *dev)
+int ps3_vuart_disable_interrupt_disconnect(struct ps3_system_bus_device *dev)
 {
-	return (dev->priv->interrupt_mask & INTERRUPT_MASK_DISCONNECT)
-		? ps3_vuart_set_interrupt_mask(dev, dev->priv->interrupt_mask
+	struct ps3_vuart_port_priv *priv = to_port_priv(dev);
+
+	return (priv->interrupt_mask & INTERRUPT_MASK_DISCONNECT)
+		? ps3_vuart_set_interrupt_mask(dev, priv->interrupt_mask
 		& ~INTERRUPT_MASK_DISCONNECT) : 0;
 }
 
 /**
  * ps3_vuart_raw_write - Low level write helper.
+ * @dev: The struct ps3_system_bus_device instance.
  *
  * Do not call ps3_vuart_raw_write directly, use ps3_vuart_write.
  */
 
-static int ps3_vuart_raw_write(struct ps3_vuart_port_device *dev,
+static int ps3_vuart_raw_write(struct ps3_system_bus_device *dev,
 	const void* buf, unsigned int bytes, unsigned long *bytes_written)
 {
 	int result;
+	struct ps3_vuart_port_priv *priv = to_port_priv(dev);
 
-	result = lv1_write_virtual_uart(dev->priv->port_number,
+	result = lv1_write_virtual_uart(dev->port_number,
 		ps3_mm_phys_to_lpar(__pa(buf)), bytes, bytes_written);
 
 	if (result) {
@@ -340,28 +377,30 @@
 		return result;
 	}
 
-	dev->priv->stats.bytes_written += *bytes_written;
+	priv->stats.bytes_written += *bytes_written;
 
 	dev_dbg(&dev->core, "%s:%d: wrote %lxh/%xh=>%lxh\n", __func__, __LINE__,
-		*bytes_written, bytes, dev->priv->stats.bytes_written);
+		*bytes_written, bytes, priv->stats.bytes_written);
 
 	return result;
 }
 
 /**
  * ps3_vuart_raw_read - Low level read helper.
+ * @dev: The struct ps3_system_bus_device instance.
  *
  * Do not call ps3_vuart_raw_read directly, use ps3_vuart_read.
  */
 
-static int ps3_vuart_raw_read(struct ps3_vuart_port_device *dev, void* buf,
+static int ps3_vuart_raw_read(struct ps3_system_bus_device *dev, void *buf,
 	unsigned int bytes, unsigned long *bytes_read)
 {
 	int result;
+	struct ps3_vuart_port_priv *priv = to_port_priv(dev);
 
 	dev_dbg(&dev->core, "%s:%d: %xh\n", __func__, __LINE__, bytes);
 
-	result = lv1_read_virtual_uart(dev->priv->port_number,
+	result = lv1_read_virtual_uart(dev->port_number,
 		ps3_mm_phys_to_lpar(__pa(buf)), bytes, bytes_read);
 
 	if (result) {
@@ -370,25 +409,27 @@
 		return result;
 	}
 
-	dev->priv->stats.bytes_read += *bytes_read;
+	priv->stats.bytes_read += *bytes_read;
 
 	dev_dbg(&dev->core, "%s:%d: read %lxh/%xh=>%lxh\n", __func__, __LINE__,
-		*bytes_read, bytes, dev->priv->stats.bytes_read);
+		*bytes_read, bytes, priv->stats.bytes_read);
 
 	return result;
 }
 
 /**
  * ps3_vuart_clear_rx_bytes - Discard bytes received.
+ * @dev: The struct ps3_system_bus_device instance.
  * @bytes: Max byte count to discard, zero = all pending.
  *
  * Used to clear pending rx interrupt source.  Will not block.
  */
 
-void ps3_vuart_clear_rx_bytes(struct ps3_vuart_port_device *dev,
+void ps3_vuart_clear_rx_bytes(struct ps3_system_bus_device *dev,
 	unsigned int bytes)
 {
 	int result;
+	struct ps3_vuart_port_priv *priv = to_port_priv(dev);
 	u64 bytes_waiting;
 	void* tmp;
 
@@ -418,8 +459,9 @@
 
 	/* Don't include these bytes in the stats. */
 
-	dev->priv->stats.bytes_read -= bytes_waiting;
+	priv->stats.bytes_read -= bytes_waiting;
 }
+EXPORT_SYMBOL_GPL(ps3_vuart_clear_rx_bytes);
 
 /**
  * struct list_buffer - An element for a port device fifo buffer list.
@@ -435,6 +477,7 @@
 
 /**
  * ps3_vuart_write - the entry point for writing data to a port
+ * @dev: The struct ps3_system_bus_device instance.
  *
  * If the port is idle on entry as much of the incoming data is written to
  * the port as the port will accept.  Otherwise a list buffer is created
@@ -442,25 +485,26 @@
  * then enqueued for transmision via the transmit interrupt.
  */
 
-int ps3_vuart_write(struct ps3_vuart_port_device *dev, const void* buf,
+int ps3_vuart_write(struct ps3_system_bus_device *dev, const void *buf,
 	unsigned int bytes)
 {
 	static unsigned long dbg_number;
 	int result;
+	struct ps3_vuart_port_priv *priv = to_port_priv(dev);
 	unsigned long flags;
 	struct list_buffer *lb;
 
 	dev_dbg(&dev->core, "%s:%d: %u(%xh) bytes\n", __func__, __LINE__,
 		bytes, bytes);
 
-	spin_lock_irqsave(&dev->priv->tx_list.lock, flags);
+	spin_lock_irqsave(&priv->tx_list.lock, flags);
 
-	if (list_empty(&dev->priv->tx_list.head)) {
+	if (list_empty(&priv->tx_list.head)) {
 		unsigned long bytes_written;
 
 		result = ps3_vuart_raw_write(dev, buf, bytes, &bytes_written);
 
-		spin_unlock_irqrestore(&dev->priv->tx_list.lock, flags);
+		spin_unlock_irqrestore(&priv->tx_list.lock, flags);
 
 		if (result) {
 			dev_dbg(&dev->core,
@@ -478,7 +522,7 @@
 		bytes -= bytes_written;
 		buf += bytes_written;
 	} else
-		spin_unlock_irqrestore(&dev->priv->tx_list.lock, flags);
+		spin_unlock_irqrestore(&priv->tx_list.lock, flags);
 
 	lb = kmalloc(sizeof(struct list_buffer) + bytes, GFP_KERNEL);
 
@@ -491,29 +535,86 @@
 	lb->tail = lb->data + bytes;
 	lb->dbg_number = ++dbg_number;
 
-	spin_lock_irqsave(&dev->priv->tx_list.lock, flags);
-	list_add_tail(&lb->link, &dev->priv->tx_list.head);
+	spin_lock_irqsave(&priv->tx_list.lock, flags);
+	list_add_tail(&lb->link, &priv->tx_list.head);
 	ps3_vuart_enable_interrupt_tx(dev);
-	spin_unlock_irqrestore(&dev->priv->tx_list.lock, flags);
+	spin_unlock_irqrestore(&priv->tx_list.lock, flags);
 
 	dev_dbg(&dev->core, "%s:%d: queued buf_%lu, %xh bytes\n",
 		__func__, __LINE__, lb->dbg_number, bytes);
 
 	return 0;
 }
+EXPORT_SYMBOL_GPL(ps3_vuart_write);
 
 /**
- * ps3_vuart_read - the entry point for reading data from a port
+ * ps3_vuart_queue_rx_bytes - Queue waiting bytes into the buffer list.
+ * @dev: The struct ps3_system_bus_device instance.
+ * @bytes_queued: Number of bytes queued to the buffer list.
  *
- * If enough bytes to satisfy the request are held in the buffer list those
- * bytes are dequeued and copied to the caller's buffer.  Emptied list buffers
- * are retiered.  If the request cannot be statified by bytes held in the list
- * buffers -EAGAIN is returned.
+ * Must be called with priv->rx_list.lock held.
  */
 
-int ps3_vuart_read(struct ps3_vuart_port_device *dev, void* buf,
+static int ps3_vuart_queue_rx_bytes(struct ps3_system_bus_device *dev,
+	u64 *bytes_queued)
+{
+	static unsigned long dbg_number;
+	int result;
+	struct ps3_vuart_port_priv *priv = to_port_priv(dev);
+	struct list_buffer *lb;
+	u64 bytes;
+
+	*bytes_queued = 0;
+
+	result = ps3_vuart_get_rx_bytes_waiting(dev, &bytes);
+	BUG_ON(result);
+
+	if (result)
+		return -EIO;
+
+	if (!bytes)
+		return 0;
+
+	/* Add some extra space for recently arrived data. */
+
+	bytes += 128;
+
+	lb = kmalloc(sizeof(struct list_buffer) + bytes, GFP_ATOMIC);
+
+	if (!lb)
+		return -ENOMEM;
+
+	ps3_vuart_raw_read(dev, lb->data, bytes, &bytes);
+
+	lb->head = lb->data;
+	lb->tail = lb->data + bytes;
+	lb->dbg_number = ++dbg_number;
+
+	list_add_tail(&lb->link, &priv->rx_list.head);
+	priv->rx_list.bytes_held += bytes;
+
+	dev_dbg(&dev->core, "%s:%d: buf_%lu: queued %lxh bytes\n",
+		__func__, __LINE__, lb->dbg_number, bytes);
+
+	*bytes_queued = bytes;
+
+	return 0;
+}
+
+/**
+ * ps3_vuart_read - The entry point for reading data from a port.
+ *
+ * Queue data waiting at the port, and if enough bytes to satisfy the request
+ * are held in the buffer list those bytes are dequeued and copied to the
+ * caller's buffer.  Emptied list buffers are retiered.  If the request cannot
+ * be statified by bytes held in the list buffers -EAGAIN is returned.
+ */
+
+int ps3_vuart_read(struct ps3_system_bus_device *dev, void *buf,
 	unsigned int bytes)
 {
+	int result;
+	struct ps3_vuart_port_priv *priv = to_port_priv(dev);
 	unsigned long flags;
 	struct list_buffer *lb, *n;
 	unsigned long bytes_read;
@@ -521,30 +622,37 @@
 	dev_dbg(&dev->core, "%s:%d: %u(%xh) bytes\n", __func__, __LINE__,
 		bytes, bytes);
 
-	spin_lock_irqsave(&dev->priv->rx_list.lock, flags);
+	spin_lock_irqsave(&priv->rx_list.lock, flags);
 
-	if (dev->priv->rx_list.bytes_held < bytes) {
-		spin_unlock_irqrestore(&dev->priv->rx_list.lock, flags);
-		dev_dbg(&dev->core, "%s:%d: starved for %lxh bytes\n",
-			__func__, __LINE__,
-			bytes - dev->priv->rx_list.bytes_held);
-		return -EAGAIN;
+	/* Queue rx bytes here for polled reads. */
+
+	while (priv->rx_list.bytes_held < bytes) {
+		u64 tmp;
+
+		result = ps3_vuart_queue_rx_bytes(dev, &tmp);
+		if (result || !tmp) {
+			dev_dbg(&dev->core, "%s:%d: starved for %lxh bytes\n",
+				__func__, __LINE__,
+				bytes - priv->rx_list.bytes_held);
+			spin_unlock_irqrestore(&priv->rx_list.lock, flags);
+			return -EAGAIN;
+		}
 	}
 
-	list_for_each_entry_safe(lb, n, &dev->priv->rx_list.head, link) {
+	list_for_each_entry_safe(lb, n, &priv->rx_list.head, link) {
 		bytes_read = min((unsigned int)(lb->tail - lb->head), bytes);
 
 		memcpy(buf, lb->head, bytes_read);
 		buf += bytes_read;
 		bytes -= bytes_read;
-		dev->priv->rx_list.bytes_held -= bytes_read;
+		priv->rx_list.bytes_held -= bytes_read;
 
 		if (bytes_read < lb->tail - lb->head) {
 			lb->head += bytes_read;
 			dev_dbg(&dev->core, "%s:%d: buf_%lu: dequeued %lxh "
 				"bytes\n", __func__, __LINE__, lb->dbg_number,
 				bytes_read);
-			spin_unlock_irqrestore(&dev->priv->rx_list.lock, flags);
+			spin_unlock_irqrestore(&priv->rx_list.lock, flags);
 			return 0;
 		}
 
@@ -556,16 +664,32 @@
 		kfree(lb);
 	}
 
-	spin_unlock_irqrestore(&dev->priv->rx_list.lock, flags);
+	spin_unlock_irqrestore(&priv->rx_list.lock, flags);
 	return 0;
 }
+EXPORT_SYMBOL_GPL(ps3_vuart_read);
 
-int ps3_vuart_read_async(struct ps3_vuart_port_device *dev, work_func_t func,
-	unsigned int bytes)
+/**
+ * ps3_vuart_work - Asynchronous read handler.
+ */
+
+static void ps3_vuart_work(struct work_struct *work)
 {
+	struct ps3_system_bus_device *dev =
+		ps3_vuart_work_to_system_bus_dev(work);
+	struct ps3_vuart_port_driver *drv =
+		ps3_system_bus_dev_to_vuart_drv(dev);
+
+	BUG_ON(!drv);
+	drv->work(dev);
+}
+
+int ps3_vuart_read_async(struct ps3_system_bus_device *dev, unsigned int bytes)
+{
+	struct ps3_vuart_port_priv *priv = to_port_priv(dev);
 	unsigned long flags;
 
-	if(dev->priv->work.trigger) {
+	if (priv->rx_list.work.trigger) {
 		dev_dbg(&dev->core, "%s:%d: warning, multiple calls\n",
 			__func__, __LINE__);
 		return -EAGAIN;
@@ -573,30 +697,32 @@
 
 	BUG_ON(!bytes);
 
-	PREPARE_WORK(&dev->priv->work.work, func);
+	PREPARE_WORK(&priv->rx_list.work.work, ps3_vuart_work);
 
-	spin_lock_irqsave(&dev->priv->work.lock, flags);
-	if(dev->priv->rx_list.bytes_held >= bytes) {
+	spin_lock_irqsave(&priv->rx_list.lock, flags);
+	if (priv->rx_list.bytes_held >= bytes) {
 		dev_dbg(&dev->core, "%s:%d: schedule_work %xh bytes\n",
 			__func__, __LINE__, bytes);
-		schedule_work(&dev->priv->work.work);
-		spin_unlock_irqrestore(&dev->priv->work.lock, flags);
+		schedule_work(&priv->rx_list.work.work);
+		spin_unlock_irqrestore(&priv->rx_list.lock, flags);
 		return 0;
 	}
 
-	dev->priv->work.trigger = bytes;
-	spin_unlock_irqrestore(&dev->priv->work.lock, flags);
+	priv->rx_list.work.trigger = bytes;
+	spin_unlock_irqrestore(&priv->rx_list.lock, flags);
 
 	dev_dbg(&dev->core, "%s:%d: waiting for %u(%xh) bytes\n", __func__,
 		__LINE__, bytes, bytes);
 
 	return 0;
 }
+EXPORT_SYMBOL_GPL(ps3_vuart_read_async);
 
-void ps3_vuart_cancel_async(struct ps3_vuart_port_device *dev)
+void ps3_vuart_cancel_async(struct ps3_system_bus_device *dev)
 {
-	dev->priv->work.trigger = 0;
+	to_port_priv(dev)->rx_list.work.trigger = 0;
 }
+EXPORT_SYMBOL_GPL(ps3_vuart_cancel_async);
 
 /**
  * ps3_vuart_handle_interrupt_tx - third stage transmit interrupt handler
@@ -606,18 +732,19 @@
  * adjusts the final list buffer state for a partial write.
  */
 
-static int ps3_vuart_handle_interrupt_tx(struct ps3_vuart_port_device *dev)
+static int ps3_vuart_handle_interrupt_tx(struct ps3_system_bus_device *dev)
 {
 	int result = 0;
+	struct ps3_vuart_port_priv *priv = to_port_priv(dev);
 	unsigned long flags;
 	struct list_buffer *lb, *n;
 	unsigned long bytes_total = 0;
 
 	dev_dbg(&dev->core, "%s:%d\n", __func__, __LINE__);
 
-	spin_lock_irqsave(&dev->priv->tx_list.lock, flags);
+	spin_lock_irqsave(&priv->tx_list.lock, flags);
 
-	list_for_each_entry_safe(lb, n, &dev->priv->tx_list.head, link) {
+	list_for_each_entry_safe(lb, n, &priv->tx_list.head, link) {
 
 		unsigned long bytes_written;
 
@@ -651,7 +778,7 @@
 
 	ps3_vuart_disable_interrupt_tx(dev);
 port_full:
-	spin_unlock_irqrestore(&dev->priv->tx_list.lock, flags);
+	spin_unlock_irqrestore(&priv->tx_list.lock, flags);
 	dev_dbg(&dev->core, "%s:%d wrote %lxh bytes total\n",
 		__func__, __LINE__, bytes_total);
 	return result;
@@ -665,60 +792,37 @@
  * buffer list.  Buffer list data is dequeued via ps3_vuart_read.
  */
 
-static int ps3_vuart_handle_interrupt_rx(struct ps3_vuart_port_device *dev)
+static int ps3_vuart_handle_interrupt_rx(struct ps3_system_bus_device *dev)
 {
-	static unsigned long dbg_number;
-	int result = 0;
+	int result;
+	struct ps3_vuart_port_priv *priv = to_port_priv(dev);
 	unsigned long flags;
-	struct list_buffer *lb;
-	unsigned long bytes;
+	u64 bytes;
 
 	dev_dbg(&dev->core, "%s:%d\n", __func__, __LINE__);
 
-	result = ps3_vuart_get_rx_bytes_waiting(dev, &bytes);
+	spin_lock_irqsave(&priv->rx_list.lock, flags);
+	result = ps3_vuart_queue_rx_bytes(dev, &bytes);
 
-	if (result)
-		return -EIO;
-
-	BUG_ON(!bytes);
-
-	/* Add some extra space for recently arrived data. */
-
-	bytes += 128;
-
-	lb = kmalloc(sizeof(struct list_buffer) + bytes, GFP_ATOMIC);
-
-	if (!lb)
-		return -ENOMEM;
-
-	ps3_vuart_raw_read(dev, lb->data, bytes, &bytes);
-
-	lb->head = lb->data;
-	lb->tail = lb->data + bytes;
-	lb->dbg_number = ++dbg_number;
-
-	spin_lock_irqsave(&dev->priv->rx_list.lock, flags);
-	list_add_tail(&lb->link, &dev->priv->rx_list.head);
-	dev->priv->rx_list.bytes_held += bytes;
-	spin_unlock_irqrestore(&dev->priv->rx_list.lock, flags);
-
-	dev_dbg(&dev->core, "%s:%d: buf_%lu: queued %lxh bytes\n",
-		__func__, __LINE__, lb->dbg_number, bytes);
-
-	spin_lock_irqsave(&dev->priv->work.lock, flags);
-	if(dev->priv->work.trigger
-		&& dev->priv->rx_list.bytes_held >= dev->priv->work.trigger) {
-		dev_dbg(&dev->core, "%s:%d: schedule_work %lxh bytes\n",
-			__func__, __LINE__, dev->priv->work.trigger);
-		dev->priv->work.trigger = 0;
-		schedule_work(&dev->priv->work.work);
+	if (result) {
+		spin_unlock_irqrestore(&priv->rx_list.lock, flags);
+		return result;
 	}
-	spin_unlock_irqrestore(&dev->priv->work.lock, flags);
-	return 0;
+
+	if (priv->rx_list.work.trigger && priv->rx_list.bytes_held
+		>= priv->rx_list.work.trigger) {
+		dev_dbg(&dev->core, "%s:%d: schedule_work %lxh bytes\n",
+			__func__, __LINE__, priv->rx_list.work.trigger);
+		priv->rx_list.work.trigger = 0;
+		schedule_work(&priv->rx_list.work.work);
+	}
+
+	spin_unlock_irqrestore(&priv->rx_list.lock, flags);
+	return result;
 }
 
 static int ps3_vuart_handle_interrupt_disconnect(
-	struct ps3_vuart_port_device *dev)
+	struct ps3_system_bus_device *dev)
 {
 	dev_dbg(&dev->core, "%s:%d\n", __func__, __LINE__);
 	BUG_ON("no support");
@@ -733,9 +837,10 @@
  * stage handler after one iteration.
  */
 
-static int ps3_vuart_handle_port_interrupt(struct ps3_vuart_port_device *dev)
+static int ps3_vuart_handle_port_interrupt(struct ps3_system_bus_device *dev)
 {
 	int result;
+	struct ps3_vuart_port_priv *priv = to_port_priv(dev);
 	unsigned long status;
 
 	result = ps3_vuart_get_interrupt_status(dev, &status);
@@ -747,21 +852,21 @@
 		status);
 
 	if (status & INTERRUPT_MASK_DISCONNECT) {
-		dev->priv->stats.disconnect_interrupts++;
+		priv->stats.disconnect_interrupts++;
 		result = ps3_vuart_handle_interrupt_disconnect(dev);
 		if (result)
 			ps3_vuart_disable_interrupt_disconnect(dev);
 	}
 
 	if (status & INTERRUPT_MASK_TX) {
-		dev->priv->stats.tx_interrupts++;
+		priv->stats.tx_interrupts++;
 		result = ps3_vuart_handle_interrupt_tx(dev);
 		if (result)
 			ps3_vuart_disable_interrupt_tx(dev);
 	}
 
 	if (status & INTERRUPT_MASK_RX) {
-		dev->priv->stats.rx_interrupts++;
+		priv->stats.rx_interrupts++;
 		result = ps3_vuart_handle_interrupt_rx(dev);
 		if (result)
 			ps3_vuart_disable_interrupt_rx(dev);
@@ -771,11 +876,11 @@
 }
 
 struct vuart_bus_priv {
-	const struct ports_bmp bmp;
+	struct ports_bmp *bmp;
 	unsigned int virq;
 	struct semaphore probe_mutex;
 	int use_count;
-	struct ps3_vuart_port_device *devices[PORT_COUNT];
+	struct ps3_system_bus_device *devices[PORT_COUNT];
 } static vuart_bus_priv;
 
 /**
@@ -788,17 +893,16 @@
 
 static irqreturn_t ps3_vuart_irq_handler(int irq, void *_private)
 {
-	struct vuart_bus_priv *bus_priv;
+	struct vuart_bus_priv *bus_priv = _private;
 
-	BUG_ON(!_private);
-	bus_priv = (struct vuart_bus_priv *)_private;
+	BUG_ON(!bus_priv);
 
 	while (1) {
 		unsigned int port;
 
-		dump_ports_bmp(&bus_priv->bmp);
+		dump_ports_bmp(bus_priv->bmp);
 
-		port = (BITS_PER_LONG - 1) - __ilog2(bus_priv->bmp.status);
+		port = (BITS_PER_LONG - 1) - __ilog2(bus_priv->bmp->status);
 
 		if (port == BITS_PER_LONG)
 			break;
@@ -812,100 +916,144 @@
 	return IRQ_HANDLED;
 }
 
-static int ps3_vuart_match(struct device *_dev, struct device_driver *_drv)
+static int ps3_vuart_bus_interrupt_get(void)
 {
 	int result;
-	struct ps3_vuart_port_driver *drv = to_ps3_vuart_port_driver(_drv);
-	struct ps3_vuart_port_device *dev = to_ps3_vuart_port_device(_dev);
 
-	result = dev->match_id == drv->match_id;
+	pr_debug(" -> %s:%d\n", __func__, __LINE__);
 
-	dev_info(&dev->core, "%s:%d: dev=%u(%s), drv=%u(%s): %s\n", __func__,
-		__LINE__, dev->match_id, dev->core.bus_id, drv->match_id,
-		drv->core.name, (result ? "match" : "miss"));
+	vuart_bus_priv.use_count++;
 
+	BUG_ON(vuart_bus_priv.use_count > 2);
+
+	if (vuart_bus_priv.use_count != 1) {
+		return 0;
+	}
+
+	BUG_ON(vuart_bus_priv.bmp);
+
+	vuart_bus_priv.bmp = kzalloc(sizeof(struct ports_bmp), GFP_KERNEL);
+
+	if (!vuart_bus_priv.bmp) {
+		pr_debug("%s:%d: kzalloc failed.\n", __func__, __LINE__);
+		result = -ENOMEM;
+		goto fail_bmp_malloc;
+	}
+
+	result = ps3_vuart_irq_setup(PS3_BINDING_CPU_ANY, vuart_bus_priv.bmp,
+		&vuart_bus_priv.virq);
+
+	if (result) {
+		pr_debug("%s:%d: ps3_vuart_irq_setup failed (%d)\n",
+			__func__, __LINE__, result);
+		result = -EPERM;
+		goto fail_alloc_irq;
+	}
+
+	result = request_irq(vuart_bus_priv.virq, ps3_vuart_irq_handler,
+		IRQF_DISABLED, "vuart", &vuart_bus_priv);
+
+	if (result) {
+		pr_debug("%s:%d: request_irq failed (%d)\n",
+			__func__, __LINE__, result);
+		goto fail_request_irq;
+	}
+
+	pr_debug(" <- %s:%d: ok\n", __func__, __LINE__);
+	return result;
+
+fail_request_irq:
+	ps3_vuart_irq_destroy(vuart_bus_priv.virq);
+	vuart_bus_priv.virq = NO_IRQ;
+fail_alloc_irq:
+	kfree(vuart_bus_priv.bmp);
+	vuart_bus_priv.bmp = NULL;
+fail_bmp_malloc:
+	vuart_bus_priv.use_count--;
+	pr_debug(" <- %s:%d: failed\n", __func__, __LINE__);
 	return result;
 }
 
-static int ps3_vuart_probe(struct device *_dev)
+static int ps3_vuart_bus_interrupt_put(void)
+{
+	pr_debug(" -> %s:%d\n", __func__, __LINE__);
+
+	vuart_bus_priv.use_count--;
+
+	BUG_ON(vuart_bus_priv.use_count < 0);
+
+	if (vuart_bus_priv.use_count != 0)
+		return 0;
+
+	free_irq(vuart_bus_priv.virq, &vuart_bus_priv);
+
+	ps3_vuart_irq_destroy(vuart_bus_priv.virq);
+	vuart_bus_priv.virq = NO_IRQ;
+
+	kfree(vuart_bus_priv.bmp);
+	vuart_bus_priv.bmp = NULL;
+
+	pr_debug(" <- %s:%d\n", __func__, __LINE__);
+	return 0;
+}
+
+static int ps3_vuart_probe(struct ps3_system_bus_device *dev)
 {
 	int result;
-	unsigned int port_number;
-	struct ps3_vuart_port_device *dev = to_ps3_vuart_port_device(_dev);
-	struct ps3_vuart_port_driver *drv =
-		to_ps3_vuart_port_driver(_dev->driver);
+	struct ps3_vuart_port_driver *drv;
+	struct ps3_vuart_port_priv *priv = NULL;
 
 	dev_dbg(&dev->core, "%s:%d\n", __func__, __LINE__);
 
+	drv = ps3_system_bus_dev_to_vuart_drv(dev);
+
+	dev_dbg(&dev->core, "%s:%d: (%s)\n", __func__, __LINE__,
+		drv->core.core.name);
+
 	BUG_ON(!drv);
 
+	if (dev->port_number >= PORT_COUNT) {
+		BUG();
+		return -EINVAL;
+	}
+
 	down(&vuart_bus_priv.probe_mutex);
 
-	/* Setup vuart_bus_priv.devices[]. */
+	result = ps3_vuart_bus_interrupt_get();
 
-	result = ps3_vuart_match_id_to_port(dev->match_id,
-		&port_number);
+	if (result)
+		goto fail_setup_interrupt;
 
-	if (result) {
-		dev_dbg(&dev->core, "%s:%d: unknown match_id (%d)\n",
-			__func__, __LINE__, dev->match_id);
-		result = -EINVAL;
-		goto fail_match;
-	}
-
-	if (vuart_bus_priv.devices[port_number]) {
+	if (vuart_bus_priv.devices[dev->port_number]) {
 		dev_dbg(&dev->core, "%s:%d: port busy (%d)\n", __func__,
-			__LINE__, port_number);
+			__LINE__, dev->port_number);
 		result = -EBUSY;
-		goto fail_match;
+		goto fail_busy;
 	}
 
-	vuart_bus_priv.devices[port_number] = dev;
+	vuart_bus_priv.devices[dev->port_number] = dev;
 
-	/* Setup dev->priv. */
+	/* Setup dev->driver_priv. */
 
-	dev->priv = kzalloc(sizeof(struct ps3_vuart_port_priv), GFP_KERNEL);
+	dev->driver_priv = kzalloc(sizeof(struct ps3_vuart_port_priv),
+		GFP_KERNEL);
 
-	if (!dev->priv) {
+	if (!dev->driver_priv) {
 		result = -ENOMEM;
-		goto fail_alloc;
+		goto fail_dev_malloc;
 	}
 
-	dev->priv->port_number = port_number;
+	priv = to_port_priv(dev);
 
-	INIT_LIST_HEAD(&dev->priv->tx_list.head);
-	spin_lock_init(&dev->priv->tx_list.lock);
+	INIT_LIST_HEAD(&priv->tx_list.head);
+	spin_lock_init(&priv->tx_list.lock);
 
-	INIT_LIST_HEAD(&dev->priv->rx_list.head);
-	spin_lock_init(&dev->priv->rx_list.lock);
+	INIT_LIST_HEAD(&priv->rx_list.head);
+	spin_lock_init(&priv->rx_list.lock);
 
-	INIT_WORK(&dev->priv->work.work, NULL);
-	spin_lock_init(&dev->priv->work.lock);
-	dev->priv->work.trigger = 0;
-	dev->priv->work.dev = dev;
-
-	if (++vuart_bus_priv.use_count == 1) {
-
-		result = ps3_vuart_irq_setup(PS3_BINDING_CPU_ANY,
-			(void*)&vuart_bus_priv.bmp.status, &vuart_bus_priv.virq);
-
-		if (result) {
-			dev_dbg(&dev->core,
-				"%s:%d: ps3_vuart_irq_setup failed (%d)\n",
-				__func__, __LINE__, result);
-			result = -EPERM;
-			goto fail_alloc_irq;
-		}
-
-		result = request_irq(vuart_bus_priv.virq, ps3_vuart_irq_handler,
-			IRQF_DISABLED, "vuart", &vuart_bus_priv);
-
-		if (result) {
-			dev_info(&dev->core, "%s:%d: request_irq failed (%d)\n",
-				__func__, __LINE__, result);
-			goto fail_request_irq;
-		}
-	}
+	INIT_WORK(&priv->rx_list.work.work, NULL);
+	priv->rx_list.work.trigger = 0;
+	priv->rx_list.work.dev = dev;
 
 	/* clear stale pending interrupts */
 
@@ -936,152 +1084,160 @@
 
 fail_probe:
 	ps3_vuart_set_interrupt_mask(dev, 0);
-fail_request_irq:
-	ps3_vuart_irq_destroy(vuart_bus_priv.virq);
-	vuart_bus_priv.virq = NO_IRQ;
-fail_alloc_irq:
-	--vuart_bus_priv.use_count;
-	kfree(dev->priv);
-	dev->priv = NULL;
-fail_alloc:
-	vuart_bus_priv.devices[port_number] = NULL;
-fail_match:
+	kfree(dev->driver_priv);
+	dev->driver_priv = NULL;
+fail_dev_malloc:
+	vuart_bus_priv.devices[dev->port_number] = NULL;
+fail_busy:
+	ps3_vuart_bus_interrupt_put();
+fail_setup_interrupt:
 	up(&vuart_bus_priv.probe_mutex);
-	dev_dbg(&dev->core, "%s:%d failed\n", __func__, __LINE__);
+	dev_dbg(&dev->core, "%s:%d: failed\n", __func__, __LINE__);
 	return result;
 }
 
-static int ps3_vuart_remove(struct device *_dev)
+/**
+ * ps3_vuart_cleanup - common cleanup helper.
+ * @dev: The struct ps3_system_bus_device instance.
+ *
+ * Cleans interrupts and HV resources.  Must be called with
+ * vuart_bus_priv.probe_mutex held.  Used by ps3_vuart_remove and
+ * ps3_vuart_shutdown.  After this call, polled reading will still work.
+ */
+
+static int ps3_vuart_cleanup(struct ps3_system_bus_device *dev)
 {
-	struct ps3_vuart_port_device *dev = to_ps3_vuart_port_device(_dev);
-	struct ps3_vuart_port_driver *drv =
-		to_ps3_vuart_port_driver(_dev->driver);
+	dev_dbg(&dev->core, "%s:%d\n", __func__, __LINE__);
+
+	ps3_vuart_cancel_async(dev);
+	ps3_vuart_set_interrupt_mask(dev, 0);
+	ps3_vuart_bus_interrupt_put();
+	return 0;
+}
+
+/**
+ * ps3_vuart_remove - Completely clean the device instance.
+ * @dev: The struct ps3_system_bus_device instance.
+ *
+ * Cleans all memory, interrupts and HV resources.  After this call the
+ * device can no longer be used.
+ */
+
+static int ps3_vuart_remove(struct ps3_system_bus_device *dev)
+{
+	struct ps3_vuart_port_priv *priv = to_port_priv(dev);
+	struct ps3_vuart_port_driver *drv;
+
+	BUG_ON(!dev);
 
 	down(&vuart_bus_priv.probe_mutex);
 
-	dev_dbg(&dev->core, "%s:%d: %s\n", __func__, __LINE__,
-		dev->core.bus_id);
+	dev_dbg(&dev->core, " -> %s:%d: match_id %d\n", __func__, __LINE__,
+		dev->match_id);
 
-	BUG_ON(vuart_bus_priv.use_count < 1);
-
-	if (drv->remove)
-		drv->remove(dev);
-	else
-		dev_dbg(&dev->core, "%s:%d: %s no remove method\n", __func__,
-			__LINE__, dev->core.bus_id);
-
-	vuart_bus_priv.devices[dev->priv->port_number] = NULL;
-
-	if (--vuart_bus_priv.use_count == 0) {
-		BUG();
-		free_irq(vuart_bus_priv.virq, &vuart_bus_priv);
-		ps3_vuart_irq_destroy(vuart_bus_priv.virq);
-		vuart_bus_priv.virq = NO_IRQ;
+	if (!dev->core.driver) {
+		dev_dbg(&dev->core, "%s:%d: no driver bound\n", __func__,
+			__LINE__);
+		up(&vuart_bus_priv.probe_mutex);
+		return 0;
 	}
 
-	kfree(dev->priv);
-	dev->priv = NULL;
+	drv = ps3_system_bus_dev_to_vuart_drv(dev);
+
+	BUG_ON(!drv);
+
+	if (drv->remove) {
+		drv->remove(dev);
+	} else {
+		dev_dbg(&dev->core, "%s:%d: no remove method\n", __func__,
+		__LINE__);
+		BUG();
+	}
+
+	ps3_vuart_cleanup(dev);
+
+	vuart_bus_priv.devices[dev->port_number] = NULL;
+	kfree(priv);
+	priv = NULL;
+
+	dev_dbg(&dev->core, " <- %s:%d\n", __func__, __LINE__);
+	up(&vuart_bus_priv.probe_mutex);
+	return 0;
+}
+
+/**
+ * ps3_vuart_shutdown - Cleans interrupts and HV resources.
+ * @dev: The struct ps3_system_bus_device instance.
+ *
+ * Cleans interrupts and HV resources.  After this call the
+ * device can still be used in polling mode.  This behavior required
+ * by sys-manager to be able to complete the device power operation
+ * sequence.
+ */
+
+static int ps3_vuart_shutdown(struct ps3_system_bus_device *dev)
+{
+	struct ps3_vuart_port_driver *drv;
+
+	BUG_ON(!dev);
+
+	down(&vuart_bus_priv.probe_mutex);
+
+	dev_dbg(&dev->core, " -> %s:%d: match_id %d\n", __func__, __LINE__,
+		dev->match_id);
+
+	if (!dev->core.driver) {
+		dev_dbg(&dev->core, "%s:%d: no driver bound\n", __func__,
+			__LINE__);
+		up(&vuart_bus_priv.probe_mutex);
+		return 0;
+	}
+
+	drv = ps3_system_bus_dev_to_vuart_drv(dev);
+
+	BUG_ON(!drv);
+
+	if (drv->shutdown)
+		drv->shutdown(dev);
+	else if (drv->remove) {
+		dev_dbg(&dev->core, "%s:%d: no shutdown, calling remove\n",
+			__func__, __LINE__);
+		drv->remove(dev);
+	} else {
+		dev_dbg(&dev->core, "%s:%d: no shutdown method\n", __func__,
+			__LINE__);
+		BUG();
+	}
+
+	ps3_vuart_cleanup(dev);
+
+	dev_dbg(&dev->core, " <- %s:%d\n", __func__, __LINE__);
 
 	up(&vuart_bus_priv.probe_mutex);
 	return 0;
 }
 
-static void ps3_vuart_shutdown(struct device *_dev)
+static int __init ps3_vuart_bus_init(void)
 {
-	struct ps3_vuart_port_device *dev = to_ps3_vuart_port_device(_dev);
-	struct ps3_vuart_port_driver *drv =
-		to_ps3_vuart_port_driver(_dev->driver);
-
-	dev_dbg(&dev->core, "%s:%d: %s\n", __func__, __LINE__,
-		dev->core.bus_id);
-
-	if (drv->shutdown)
-		drv->shutdown(dev);
-	else
-		dev_dbg(&dev->core, "%s:%d: %s no shutdown method\n", __func__,
-			__LINE__, dev->core.bus_id);
-}
-
-/**
- * ps3_vuart_bus - The vuart bus instance.
- *
- * The vuart is managed as a bus that port devices connect to.
- */
-
-struct bus_type ps3_vuart_bus = {
-        .name = "ps3_vuart",
-	.match = ps3_vuart_match,
-	.probe = ps3_vuart_probe,
-	.remove = ps3_vuart_remove,
-	.shutdown = ps3_vuart_shutdown,
-};
-
-int __init ps3_vuart_bus_init(void)
-{
-	int result;
-
 	pr_debug("%s:%d:\n", __func__, __LINE__);
 
 	if (!firmware_has_feature(FW_FEATURE_PS3_LV1))
 		return -ENODEV;
 
 	init_MUTEX(&vuart_bus_priv.probe_mutex);
-	result = bus_register(&ps3_vuart_bus);
-	BUG_ON(result);
 
-	return result;
+	return 0;
 }
 
-void __exit ps3_vuart_bus_exit(void)
+static void __exit ps3_vuart_bus_exit(void)
 {
 	pr_debug("%s:%d:\n", __func__, __LINE__);
-	bus_unregister(&ps3_vuart_bus);
 }
 
 core_initcall(ps3_vuart_bus_init);
 module_exit(ps3_vuart_bus_exit);
 
 /**
- * ps3_vuart_port_release_device - Remove a vuart port device.
- */
-
-static void ps3_vuart_port_release_device(struct device *_dev)
-{
-#if defined(DEBUG)
-	struct ps3_vuart_port_device *dev = to_ps3_vuart_port_device(_dev);
-
-	dev_dbg(&dev->core, "%s:%d\n", __func__, __LINE__);
-
-	BUG_ON(dev->priv && "forgot to free");
-	memset(&dev->core, 0, sizeof(dev->core));
-#endif
-}
-
-/**
- * ps3_vuart_port_device_register - Add a vuart port device.
- */
-
-int ps3_vuart_port_device_register(struct ps3_vuart_port_device *dev)
-{
-	static unsigned int dev_count = 1;
-
-	BUG_ON(dev->priv && "forgot to free");
-
-	dev->core.parent = NULL;
-	dev->core.bus = &ps3_vuart_bus;
-	dev->core.release = ps3_vuart_port_release_device;
-
-	snprintf(dev->core.bus_id, sizeof(dev->core.bus_id), "vuart_%02x",
-		dev_count++);
-
-	dev_dbg(&dev->core, "%s:%d register\n", __func__, __LINE__);
-
-	return device_register(&dev->core);
-}
-
-EXPORT_SYMBOL_GPL(ps3_vuart_port_device_register);
-
-/**
  * ps3_vuart_port_driver_register - Add a vuart port device driver.
  */
 
@@ -1089,12 +1245,18 @@
 {
 	int result;
 
-	pr_debug("%s:%d: (%s)\n", __func__, __LINE__, drv->core.name);
-	drv->core.bus = &ps3_vuart_bus;
-	result = driver_register(&drv->core);
+	pr_debug("%s:%d: (%s)\n", __func__, __LINE__, drv->core.core.name);
+
+	BUG_ON(!drv->core.match_id);
+	BUG_ON(!drv->core.core.name);
+
+	drv->core.probe = ps3_vuart_probe;
+	drv->core.remove = ps3_vuart_remove;
+	drv->core.shutdown = ps3_vuart_shutdown;
+
+	result = ps3_system_bus_driver_register(&drv->core);
 	return result;
 }
-
 EXPORT_SYMBOL_GPL(ps3_vuart_port_driver_register);
 
 /**
@@ -1103,8 +1265,7 @@
 
 void ps3_vuart_port_driver_unregister(struct ps3_vuart_port_driver *drv)
 {
-	pr_debug("%s:%d: (%s)\n", __func__, __LINE__, drv->core.name);
-	driver_unregister(&drv->core);
+	pr_debug("%s:%d: (%s)\n", __func__, __LINE__, drv->core.core.name);
+	ps3_system_bus_driver_unregister(&drv->core);
 }
-
 EXPORT_SYMBOL_GPL(ps3_vuart_port_driver_unregister);
diff --git a/drivers/ps3/vuart.h b/drivers/ps3/vuart.h
index 1be992d..eb7f6d9 100644
--- a/drivers/ps3/vuart.h
+++ b/drivers/ps3/vuart.h
@@ -34,29 +34,7 @@
 struct ps3_vuart_work {
 	struct work_struct work;
 	unsigned long trigger;
-	spinlock_t lock;
-	struct ps3_vuart_port_device* dev; /* to convert work to device */
-};
-
-/**
- * struct ps3_vuart_port_priv - private vuart device data.
- */
-
-struct ps3_vuart_port_priv {
-	unsigned int port_number;
-	u64 interrupt_mask;
-
-	struct {
-		spinlock_t lock;
-		struct list_head head;
-	} tx_list;
-	struct {
-		unsigned long bytes_held;
-		spinlock_t lock;
-		struct list_head head;
-	} rx_list;
-	struct ps3_vuart_stats stats;
-	struct ps3_vuart_work work;
+	struct ps3_system_bus_device *dev; /* to convert work to device */
 };
 
 /**
@@ -64,32 +42,30 @@
  */
 
 struct ps3_vuart_port_driver {
-	enum ps3_match_id match_id;
-	struct device_driver core;
-	int (*probe)(struct ps3_vuart_port_device *);
-	int (*remove)(struct ps3_vuart_port_device *);
-	void (*shutdown)(struct ps3_vuart_port_device *);
-	int (*tx_event)(struct ps3_vuart_port_device *dev);
-	int (*rx_event)(struct ps3_vuart_port_device *dev);
-	int (*disconnect_event)(struct ps3_vuart_port_device *dev);
-	/* int (*suspend)(struct ps3_vuart_port_device *, pm_message_t); */
-	/* int (*resume)(struct ps3_vuart_port_device *); */
+	struct ps3_system_bus_driver core;
+	int (*probe)(struct ps3_system_bus_device *);
+	int (*remove)(struct ps3_system_bus_device *);
+	void (*shutdown)(struct ps3_system_bus_device *);
+	void (*work)(struct ps3_system_bus_device *);
+	/* int (*tx_event)(struct ps3_system_bus_device *dev); */
+	/* int (*rx_event)(struct ps3_system_bus_device *dev); */
+	/* int (*disconnect_event)(struct ps3_system_bus_device *dev); */
+	/* int (*suspend)(struct ps3_system_bus_device *, pm_message_t); */
+	/* int (*resume)(struct ps3_system_bus_device *); */
 };
 
 int ps3_vuart_port_driver_register(struct ps3_vuart_port_driver *drv);
 void ps3_vuart_port_driver_unregister(struct ps3_vuart_port_driver *drv);
 
-static inline struct ps3_vuart_port_driver *to_ps3_vuart_port_driver(
-	struct device_driver *_drv)
+static inline struct ps3_vuart_port_driver *
+	ps3_system_bus_dev_to_vuart_drv(struct ps3_system_bus_device *_dev)
 {
-	return container_of(_drv, struct ps3_vuart_port_driver, core);
+	struct ps3_system_bus_driver *sbd =
+		ps3_system_bus_dev_to_system_bus_drv(_dev);
+	BUG_ON(!sbd);
+	return container_of(sbd, struct ps3_vuart_port_driver, core);
 }
-static inline struct ps3_vuart_port_device *to_ps3_vuart_port_device(
-	struct device *_dev)
-{
-	return container_of(_dev, struct ps3_vuart_port_device, core);
-}
-static inline struct ps3_vuart_port_device *ps3_vuart_work_to_port_device(
+static inline struct ps3_system_bus_device *ps3_vuart_work_to_system_bus_dev(
 	struct work_struct *_work)
 {
 	struct ps3_vuart_work *vw = container_of(_work, struct ps3_vuart_work,
@@ -97,14 +73,13 @@
 	return vw->dev;
 }
 
-int ps3_vuart_write(struct ps3_vuart_port_device *dev, const void* buf,
+int ps3_vuart_write(struct ps3_system_bus_device *dev, const void *buf,
 	unsigned int bytes);
-int ps3_vuart_read(struct ps3_vuart_port_device *dev, void* buf,
+int ps3_vuart_read(struct ps3_system_bus_device *dev, void *buf,
 	unsigned int bytes);
-int ps3_vuart_read_async(struct ps3_vuart_port_device *dev, work_func_t func,
-	unsigned int bytes);
-void ps3_vuart_cancel_async(struct ps3_vuart_port_device *dev);
-void ps3_vuart_clear_rx_bytes(struct ps3_vuart_port_device *dev,
+int ps3_vuart_read_async(struct ps3_system_bus_device *dev, unsigned int bytes);
+void ps3_vuart_cancel_async(struct ps3_system_bus_device *dev);
+void ps3_vuart_clear_rx_bytes(struct ps3_system_bus_device *dev,
 	unsigned int bytes);
 
 #endif
diff --git a/include/asm-powerpc/ps3.h b/include/asm-powerpc/ps3.h
index 4f75390..433c38e 100644
--- a/include/asm-powerpc/ps3.h
+++ b/include/asm-powerpc/ps3.h
@@ -407,23 +407,6 @@
 
 extern struct bus_type ps3_system_bus_type;
 
-/* vuart routines */
-
-struct ps3_vuart_port_priv;
-
-/**
- * struct ps3_vuart_port_device - a device on a vuart port
- */
-
-struct ps3_vuart_port_device {
-	enum ps3_match_id match_id;
-	struct device core;
-	struct ps3_vuart_port_priv* priv; /* private driver variables */
-
-};
-
-int ps3_vuart_port_device_register(struct ps3_vuart_port_device *dev);
-
 /* system manager */
 
 #ifdef CONFIG_PS3_SYS_MANAGER