linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/1] staging: Add firewire-serial driver
@ 2012-10-18 12:56 Peter Hurley
  2012-10-22 22:45 ` Greg Kroah-Hartman
  2012-11-02 12:16 ` [PATCH v2 " Peter Hurley
  0 siblings, 2 replies; 20+ messages in thread
From: Peter Hurley @ 2012-10-18 12:56 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Stefan Richter; +Cc: devel, linux-kernel, linux1394-devel

Please consider this serial driver for review for submission to staging.
The firewire-serial driver implements TTY over IEEE 1394. In its default
configuration, it creates 4 TTY devices and one loopback device per
firewire card (respectively, named fwtty<n>~fwtty<n+3> and fwloop<n>).

Currently, the TTY devices auto-connect to every cabled peer (the TODO
list includes plans for providing a sysfs interface to control virtual
cabling with whitelist/blacklist support per GUID).

Efforts are still ongoing for a companion console driver, with plans to
eventually add early_printk & kgdb support (via additional drivers).

Some issues did arise with both the TTY and Firewire subsystems which
are noted in the TODO file. Please review these workarounds.

Peter Hurley (1):
  staging: fwserial: Add TTY-over-Firewire serial driver

 drivers/staging/Kconfig             |    2 +
 drivers/staging/Makefile            |    1 +
 drivers/staging/fwserial/Kconfig    |    9 +
 drivers/staging/fwserial/Makefile   |    2 +
 drivers/staging/fwserial/TODO       |   89 ++
 drivers/staging/fwserial/dma_fifo.c |  310 ++++
 drivers/staging/fwserial/dma_fifo.h |  130 ++
 drivers/staging/fwserial/fwserial.c | 2885 +++++++++++++++++++++++++++++++++++
 drivers/staging/fwserial/fwserial.h |  355 +++++
 9 files changed, 3783 insertions(+)
 create mode 100644 drivers/staging/fwserial/Kconfig
 create mode 100644 drivers/staging/fwserial/Makefile
 create mode 100644 drivers/staging/fwserial/TODO
 create mode 100644 drivers/staging/fwserial/dma_fifo.c
 create mode 100644 drivers/staging/fwserial/dma_fifo.h
 create mode 100644 drivers/staging/fwserial/fwserial.c
 create mode 100644 drivers/staging/fwserial/fwserial.h

-- 
1.7.12.3




^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 0/1] staging: Add firewire-serial driver
  2012-10-18 12:56 [PATCH 0/1] staging: Add firewire-serial driver Peter Hurley
@ 2012-10-22 22:45 ` Greg Kroah-Hartman
  2012-10-23  2:34   ` Peter Hurley
  2012-10-24 13:41   ` Stefan Richter
  2012-11-02 12:16 ` [PATCH v2 " Peter Hurley
  1 sibling, 2 replies; 20+ messages in thread
From: Greg Kroah-Hartman @ 2012-10-22 22:45 UTC (permalink / raw)
  To: Peter Hurley; +Cc: Stefan Richter, devel, linux1394-devel, linux-kernel

On Thu, Oct 18, 2012 at 08:56:55AM -0400, Peter Hurley wrote:
> Please consider this serial driver for review for submission to staging.
> The firewire-serial driver implements TTY over IEEE 1394. In its default
> configuration, it creates 4 TTY devices and one loopback device per
> firewire card (respectively, named fwtty<n>~fwtty<n+3> and fwloop<n>).
> 
> Currently, the TTY devices auto-connect to every cabled peer (the TODO
> list includes plans for providing a sysfs interface to control virtual
> cabling with whitelist/blacklist support per GUID).
> 
> Efforts are still ongoing for a companion console driver, with plans to
> eventually add early_printk & kgdb support (via additional drivers).
> 
> Some issues did arise with both the TTY and Firewire subsystems which
> are noted in the TODO file. Please review these workarounds.
> 
> Peter Hurley (1):
>   staging: fwserial: Add TTY-over-Firewire serial driver

I'd like to get an Ack from Stefan here, before I'll add this to the
staging tree.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 0/1] staging: Add firewire-serial driver
  2012-10-22 22:45 ` Greg Kroah-Hartman
@ 2012-10-23  2:34   ` Peter Hurley
  2012-10-23  3:15     ` Greg Kroah-Hartman
  2012-10-23  9:51     ` Alan Cox
  2012-10-24 13:41   ` Stefan Richter
  1 sibling, 2 replies; 20+ messages in thread
From: Peter Hurley @ 2012-10-23  2:34 UTC (permalink / raw)
  To: Greg Kroah-Hartman; +Cc: Stefan Richter, devel, linux1394-devel, linux-kernel

On Mon, 2012-10-22 at 15:45 -0700, Greg Kroah-Hartman wrote:
> On Thu, Oct 18, 2012 at 08:56:55AM -0400, Peter Hurley wrote:
> > Please consider this serial driver for review for submission to staging.
> > The firewire-serial driver implements TTY over IEEE 1394. In its default
> > configuration, it creates 4 TTY devices and one loopback device per
> > firewire card (respectively, named fwtty<n>~fwtty<n+3> and fwloop<n>).
> > 
> > Currently, the TTY devices auto-connect to every cabled peer (the TODO
> > list includes plans for providing a sysfs interface to control virtual
> > cabling with whitelist/blacklist support per GUID).
> > 
> > Efforts are still ongoing for a companion console driver, with plans to
> > eventually add early_printk & kgdb support (via additional drivers).
> > 
> > Some issues did arise with both the TTY and Firewire subsystems which
> > are noted in the TODO file. Please review these workarounds.
> > 
> > Peter Hurley (1):
> >   staging: fwserial: Add TTY-over-Firewire serial driver
> 
> I'd like to get an Ack from Stefan here, before I'll add this to the
> staging tree.

Of course.

Jiri's newest series ("TTY buffer in tty_port and other stuff") does
break this driver, so I'll need to resubmit to address those changes.
Two of the workarounds mentioned in the TODO are related to the
tty_buffer interface; specifically,

1. The ldisc drops the contents of tty_buffer on hangup (rather than
waiting for completion). Maybe for other devices this isn't so
noticeable because the ldisc can mostly keep up with the device, but on
firewire the ldisc lags well behind. Right now, this driver works around
this by holding off the hangup until the ldisc empties the tty_buffer.
The driver determines how much data is still left in the tty_buffer by
walking the flip buffers.

2. Because this driver can fill the entire tty_buffer (64K +) before the
ldisc even runs once, this driver has to self-throttle when feeding the
tty_buffer. So as not to drop data, the driver has to know when a
watermark is reached in the tty_buffer, to ensure that in-flight +
already-accepted data can safely be moved to what remains avail in the
tty_buffer. (The tty_buffer maxs at 64K). Currently, the avail level in
the tty_buffer is computed by this driver (in the same manner as
tty_buffer_alloc()).

Eventually, I'd like to move these changes into tty_buffer but I wanted
to present the use case first.

Regards,
Peter Hurley


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 0/1] staging: Add firewire-serial driver
  2012-10-23  2:34   ` Peter Hurley
@ 2012-10-23  3:15     ` Greg Kroah-Hartman
  2012-10-23  9:51     ` Alan Cox
  1 sibling, 0 replies; 20+ messages in thread
From: Greg Kroah-Hartman @ 2012-10-23  3:15 UTC (permalink / raw)
  To: Peter Hurley; +Cc: devel, Stefan Richter, linux1394-devel, linux-kernel

On Mon, Oct 22, 2012 at 10:34:39PM -0400, Peter Hurley wrote:
> On Mon, 2012-10-22 at 15:45 -0700, Greg Kroah-Hartman wrote:
> > On Thu, Oct 18, 2012 at 08:56:55AM -0400, Peter Hurley wrote:
> > > Please consider this serial driver for review for submission to staging.
> > > The firewire-serial driver implements TTY over IEEE 1394. In its default
> > > configuration, it creates 4 TTY devices and one loopback device per
> > > firewire card (respectively, named fwtty<n>~fwtty<n+3> and fwloop<n>).
> > > 
> > > Currently, the TTY devices auto-connect to every cabled peer (the TODO
> > > list includes plans for providing a sysfs interface to control virtual
> > > cabling with whitelist/blacklist support per GUID).
> > > 
> > > Efforts are still ongoing for a companion console driver, with plans to
> > > eventually add early_printk & kgdb support (via additional drivers).
> > > 
> > > Some issues did arise with both the TTY and Firewire subsystems which
> > > are noted in the TODO file. Please review these workarounds.
> > > 
> > > Peter Hurley (1):
> > >   staging: fwserial: Add TTY-over-Firewire serial driver
> > 
> > I'd like to get an Ack from Stefan here, before I'll add this to the
> > staging tree.
> 
> Of course.
> 
> Jiri's newest series ("TTY buffer in tty_port and other stuff") does
> break this driver, so I'll need to resubmit to address those changes.
> Two of the workarounds mentioned in the TODO are related to the
> tty_buffer interface; specifically,
> 
> 1. The ldisc drops the contents of tty_buffer on hangup (rather than
> waiting for completion). Maybe for other devices this isn't so
> noticeable because the ldisc can mostly keep up with the device, but on
> firewire the ldisc lags well behind. Right now, this driver works around
> this by holding off the hangup until the ldisc empties the tty_buffer.
> The driver determines how much data is still left in the tty_buffer by
> walking the flip buffers.

That's not good.  Surely other drivers must see this, as we handle tty
over ethernet, that should be just as fast as firewire, right?  What do
those drivers do to handle this issue?

> 2. Because this driver can fill the entire tty_buffer (64K +) before the
> ldisc even runs once, this driver has to self-throttle when feeding the
> tty_buffer. So as not to drop data, the driver has to know when a
> watermark is reached in the tty_buffer, to ensure that in-flight +
> already-accepted data can safely be moved to what remains avail in the
> tty_buffer. (The tty_buffer maxs at 64K). Currently, the avail level in
> the tty_buffer is computed by this driver (in the same manner as
> tty_buffer_alloc()).

I suggest bringing this up on the linux-serial@vger mailing list, Alan
and Jiri should be able to help you out.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 0/1] staging: Add firewire-serial driver
  2012-10-23  2:34   ` Peter Hurley
  2012-10-23  3:15     ` Greg Kroah-Hartman
@ 2012-10-23  9:51     ` Alan Cox
  2012-10-23 16:30       ` Peter Hurley
  1 sibling, 1 reply; 20+ messages in thread
From: Alan Cox @ 2012-10-23  9:51 UTC (permalink / raw)
  To: Peter Hurley
  Cc: Greg Kroah-Hartman, Stefan Richter, devel, linux1394-devel, linux-kernel

> 1. The ldisc drops the contents of tty_buffer on hangup (rather than
> waiting for completion). Maybe for other devices this isn't so
> noticeable because the ldisc can mostly keep up with the device, but on
> firewire the ldisc lags well behind. Right now, this driver works around
> this by holding off the hangup until the ldisc empties the tty_buffer.
> The driver determines how much data is still left in the tty_buffer by
> walking the flip buffers.

The driver should not be trying to look at this, so it's good that it
broke now. If you need it to process the data on then hangup then the
core code wants fixing to allow this to occur.

> 2. Because this driver can fill the entire tty_buffer (64K +) before the
> ldisc even runs once, this driver has to self-throttle when feeding the
> tty_buffer.

That wants investigating properly. We do multiple megabits quite happily
via the tty layer with 3G modems over USB. What is your data rate ?

Again it wants dealing with properly.

Alan

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 0/1] staging: Add firewire-serial driver
  2012-10-23  9:51     ` Alan Cox
@ 2012-10-23 16:30       ` Peter Hurley
  2012-10-23 18:41         ` Stefan Richter
  0 siblings, 1 reply; 20+ messages in thread
From: Peter Hurley @ 2012-10-23 16:30 UTC (permalink / raw)
  To: Alan Cox
  Cc: Greg Kroah-Hartman, Stefan Richter, devel, linux1394-devel,
	linux-kernel, linux-serial

[cc'ing linux-serial]

On Tue, 2012-10-23 at 10:51 +0100, Alan Cox wrote:
> > 1. The ldisc drops the contents of tty_buffer on hangup (rather than
> > waiting for completion). Maybe for other devices this isn't so
> > noticeable because the ldisc can mostly keep up with the device, but on
> > firewire the ldisc lags well behind. Right now, this driver works around
> > this by holding off the hangup until the ldisc empties the tty_buffer.
> > The driver determines how much data is still left in the tty_buffer by
> > walking the flip buffers.
> 
> The driver should not be trying to look at this, so it's good that it
> broke now. If you need it to process the data on then hangup then the
> core code wants fixing to allow this to occur.

Sorry. I assumed not waiting for ldisc completion on hangup was
architectural.

tty_ldisc_hangup() has this comment from '09:

	/*
	 * FIXME: Once we trust the LDISC code better we can wait here for
	 * ldisc completion and fix the driver call race
	 */

and then several lines below specifically stops emptying the tty_buffer:

	cancel_work_sync(&tty->buf.work);

But even earlier in tty_ldisc_hangup(), the ldisc has been flushed
(which the n_tty ldisc interprets as a reset).

> > 2. Because this driver can fill the entire tty_buffer (64K +) before the
> > ldisc even runs once, this driver has to self-throttle when feeding the
> > tty_buffer.
> 
> That wants investigating properly. We do multiple megabits quite happily
> via the tty layer with 3G modems over USB. What is your data rate ?

The _slowest_ Firewire does 125 Mbits/sec using only the portion of the
bus cycle assigned for async tx (which is what this driver uses). That's
2Kbytes every 125us. Real world can be pretty close to that because
Firewire uses autonomous DMA and most controllers allow spillover into
the portion of bus cycle assigned for sync tx (which is another 4Kbytes
per 125us).  (Technical note: the actual total max for combined async
and sync tx is 6144 bytes per 125us clock)

2Kbytes/125us = 16Kbytes/1ms = 160Kbytes/jiffy (for CONFIG_HZ_100)

Some retail Firewire controllers do twice that, controllers in
early-production do 4x that, and reportedly at least one prototype does
8x that (although I haven't seen it or tested it).

'cat /dev/fwtty0' using this driver gets considerably less that, but
still enough to find this race.

Ignoring for a moment the data rates, the throttling logic is racy
because the ldisc sends the throttle from non-atomic context (so that
termios can be locked and accessed) but the driver fills the tty_buffer
from atomic context. In other words, the condition for which the ldisc
will send a throttle (receive_room < TTY_THRESHOLD_THROTTLE) hasn't yet
happened, but it will the next time receive_buf() runs.

So, the best that tty_insert_flip_string_* can do is return an error
that tty_buffer_alloc() refuses to allocate more than 64k. But the
problem is this driver has data that __must__ go somewhere at that
particular point in time. This driver could introduce another layer of
buffering in front of this but that would defeat the purpose of even
having tty_buffer in the first place.

Checking how much space might be avail for future rx and throttling if
too low seems like the natural solution here. Of course, directly
peeking at tty_buffer is the wrong way to go about doing it. But what if
that was properly exposed by tty_buffer, such as (adjusted for Jiri's
recent changes):

int tty_buffer_space_avail(struct tty_struct *tty)
{
	struct tty_port *port = tty->port;
	struct tty_bufhead *buf = &port->buf;
	unsigned long flags;
	int avail;

	spin_lock_irqsave(&buf->lock, flags);
	avail = 65536 - buf.memory_used;  /* threshold used by tty_buffer_alloc() */
	spin_unlock_irqrestore(&buf->lock, flags);
	return avail;
}

Regards,
Peter Hurley





^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 0/1] staging: Add firewire-serial driver
  2012-10-23 16:30       ` Peter Hurley
@ 2012-10-23 18:41         ` Stefan Richter
  0 siblings, 0 replies; 20+ messages in thread
From: Stefan Richter @ 2012-10-23 18:41 UTC (permalink / raw)
  To: Peter Hurley
  Cc: Alan Cox, Greg Kroah-Hartman, devel, linux1394-devel,
	linux-kernel, linux-serial

On Oct 23 Peter Hurley wrote:
> most controllers allow spillover into
> the portion of bus cycle assigned for sync tx (which is another 4Kbytes
> per 125us).

All controllers do so and are expected to do so.  Asynchronous traffic is
not supposed to depend on a cycle master being active.

> (Technical note: the actual total max for combined async
> and sync tx is 6144 bytes per 125us clock)

It depends on bus topology, transaction types, and more.  For a single-hop
S400 1394a bus (vulgo FireWire 400) I once calculated 44 MB/s bandwidth of
asynchronous unified write transactions:
http://marc.info/?l=linux1394-devel&m=109128028930225
That's at the physical layer; link layer and application layer performances
are less than that of course.
-- 
Stefan Richter
-=====-===-- =-=- =-===
http://arcgraph.de/sr/

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 0/1] staging: Add firewire-serial driver
  2012-10-22 22:45 ` Greg Kroah-Hartman
  2012-10-23  2:34   ` Peter Hurley
@ 2012-10-24 13:41   ` Stefan Richter
  2012-10-24 15:56     ` Peter Hurley
  1 sibling, 1 reply; 20+ messages in thread
From: Stefan Richter @ 2012-10-24 13:41 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Peter Hurley, devel, linux1394-devel, linux-kernel, linux-serial

On Oct 22 Greg Kroah-Hartman wrote:
> On Thu, Oct 18, 2012 at 08:56:55AM -0400, Peter Hurley wrote:
> > Please consider this serial driver for review for submission to staging.
> > The firewire-serial driver implements TTY over IEEE 1394. In its default
> > configuration, it creates 4 TTY devices and one loopback device per
> > firewire card (respectively, named fwtty<n>~fwtty<n+3> and fwloop<n>).
> > 
> > Currently, the TTY devices auto-connect to every cabled peer (the TODO
> > list includes plans for providing a sysfs interface to control virtual
> > cabling with whitelist/blacklist support per GUID).
> > 
> > Efforts are still ongoing for a companion console driver, with plans to
> > eventually add early_printk & kgdb support (via additional drivers).
> > 
> > Some issues did arise with both the TTY and Firewire subsystems which
> > are noted in the TODO file. Please review these workarounds.
> > 
> > Peter Hurley (1):
> >   staging: fwserial: Add TTY-over-Firewire serial driver
> 
> I'd like to get an Ack from Stefan here, before I'll add this to the
> staging tree.

I have not formed an opinion yet, and have not taken the time to look over
the code yet.  Personally, I am entirely unfamiliar with the application
domain of tty drivers in general and with the tty-over-"high"speed-bus
application domain in particular.  IOW, I don't know who is going to use
this for which purposes.

On the FireWire side, this driver implements an own transport protocol.
AFAIK a standardized serial-over-1394 does not exist.  This means that
interoperability of this driver will initially be limited to
Linux-to-Linux.  Devices or other operating systems which implement this
do not exist.

Apart from that aspect, the proposed fwserial driver looks to have
requirements similar to the existing IPv4-over-1394 and SBP target-mode
drivers (drivers/firewire/net.* and drivers/target/sbp/*).  This means that
(a) fw-serial motivated changes to the firewire subsystem and (b) firewire
subsystem motivated changes to fwserial could quite possibly also be
interesting for or required by those other drivers as well, meaning that
maintenance overhead should not increase much.

> >  drivers/staging/fwserial/dma_fifo.c |  310 ++++
> >  drivers/staging/fwserial/dma_fifo.h |  130 ++
> >  drivers/staging/fwserial/fwserial.c | 2885 +++++++++++++++++++++++++++++++++++
> >  drivers/staging/fwserial/fwserial.h |  355 +++++

drivers/firewire/net.c is 1721 lines, sbp_target is 2868 lines.
Why is fwserial bigger?
-- 
Stefan Richter
-=====-===-- =-=- ==---
http://arcgraph.de/sr/

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 0/1] staging: Add firewire-serial driver
  2012-10-24 13:41   ` Stefan Richter
@ 2012-10-24 15:56     ` Peter Hurley
  0 siblings, 0 replies; 20+ messages in thread
From: Peter Hurley @ 2012-10-24 15:56 UTC (permalink / raw)
  To: Stefan Richter
  Cc: Greg Kroah-Hartman, devel, linux1394-devel, linux-kernel, linux-serial

> > >  drivers/staging/fwserial/dma_fifo.c |  310 ++++
> > >  drivers/staging/fwserial/dma_fifo.h |  130 ++
> > >  drivers/staging/fwserial/fwserial.c | 2885 +++++++++++++++++++++++++++++++++++
> > >  drivers/staging/fwserial/fwserial.h |  355 +++++
> 
> drivers/firewire/net.c is 1721 lines, sbp_target is 2868 lines.
> Why is fwserial bigger?

1) Exclude the dma_fifo* files in the comparison, because block & net
devices get dma-able memory management from their subsystems.
2) Certain tty concepts don't map well to an automated bus, so these are
simulated (eg, break handling)
3) fwserial has to implement a protocol to virtually cable peers
4) sbp_target only supports 1 target.



^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v2 0/1] staging: Add firewire-serial driver
  2012-10-18 12:56 [PATCH 0/1] staging: Add firewire-serial driver Peter Hurley
  2012-10-22 22:45 ` Greg Kroah-Hartman
@ 2012-11-02 12:16 ` Peter Hurley
  2012-11-02 12:16   ` [PATCH v2 1/1] staging: fwserial: Add TTY-over-Firewire serial driver Peter Hurley
  1 sibling, 1 reply; 20+ messages in thread
From: Peter Hurley @ 2012-11-02 12:16 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Stefan Richter, Alan Cox
  Cc: linux-kernel, devel, linux1394-devel, linux-serial, Peter Hurley

v2 of this driver submission builds and runs cleanly against Greg KH's
tty-next and (hopefully) addresses the concerns raised regarding the
dependence on tty_buffer internals; specifically with the workarounds to fix
1) data loss on hangup and 2) throttling before the flip buffers are full.

1) Data loss on hangup
Although I experimented with alternative solutions as Alan suggested, none
were practical. For example, this sequence (which is almost identical to
what the n_tty ldisc does in input_available_p()):

     tty_flush_to_ldisc(tty);
     n = ldisc->ops->chars_in_buffer(tty);

suffers from a race that the ldisc could have _just_ emptied the read
buffer between these calls, so that n == 0 but data is still in the
flip buffers.

For now, the driver v2 delays the hangup on carrier loss by a fixed timeout.

2) Flip buffers full before throttle received from ldisc
Because driver throttling is performed by the ldisc, and not by the
tty_buffer, the flip buffers can be filled before receiving a throttle
request.

For now, the driver v2 pre-buffers in front of the tty_buffer; ie, rx data
which is not accepted by tty_insert_flip_string...() is buffered in the
driver, the sender is throttled and when ldisc unthrottles, normal operation
resumes by feeding the tty_buffer from the pre-buffered data.

This driver v2 also fixes the occasionally flaky auto-connect, 2 (valid)
lockdep warnings, racy line status changes, and delays fifo allocation
until .activate() to minimize memory footprint.

As before, the TODO file notes the remaining issues.

Regards,
Peter

Peter Hurley (1):
  staging: fwserial: Add TTY-over-Firewire serial driver

 drivers/staging/Kconfig             |    2 +
 drivers/staging/Makefile            |    1 +
 drivers/staging/fwserial/Kconfig    |    9 +
 drivers/staging/fwserial/Makefile   |    2 +
 drivers/staging/fwserial/TODO       |   37 +
 drivers/staging/fwserial/dma_fifo.c |  310 ++++
 drivers/staging/fwserial/dma_fifo.h |  130 ++
 drivers/staging/fwserial/fwserial.c | 2946 +++++++++++++++++++++++++++++++++++
 drivers/staging/fwserial/fwserial.h |  387 +++++
 9 files changed, 3824 insertions(+)
 create mode 100644 drivers/staging/fwserial/Kconfig
 create mode 100644 drivers/staging/fwserial/Makefile
 create mode 100644 drivers/staging/fwserial/TODO
 create mode 100644 drivers/staging/fwserial/dma_fifo.c
 create mode 100644 drivers/staging/fwserial/dma_fifo.h
 create mode 100644 drivers/staging/fwserial/fwserial.c
 create mode 100644 drivers/staging/fwserial/fwserial.h

-- 
1.7.12.3


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v2 1/1] staging: fwserial: Add TTY-over-Firewire serial driver
  2012-11-02 12:16 ` [PATCH v2 " Peter Hurley
@ 2012-11-02 12:16   ` Peter Hurley
  2012-11-12 23:33     ` Stefan Richter
  0 siblings, 1 reply; 20+ messages in thread
From: Peter Hurley @ 2012-11-02 12:16 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Stefan Richter, Alan Cox
  Cc: linux-kernel, devel, linux1394-devel, linux-serial, Peter Hurley

This patch provides the kernel driver for high-speed TTY
communication over the IEEE 1394 bus.

Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
---
v2
 - Removes dependencies on tty_buffer internals
 - Handles ldisc data loss on hangup with timer delay
 - Buffers rx if flip buffers are full
 - Fixes flaky auto-connect
 - Moves fifo allocation to .activate - this saves memory on
   unused ports
 - Fixes 2 lockdep warnings on allocations w/ spinlocks held
 - Fixes all checkpatch warnings
 - Fixes potential line status write order inversion
 - Fixes completely bogus attempt at running delayed work
   (by using new mod_delayed_work())
 - Fixes potential deadlock when cancelling peer work
 - Respects CLOCAL for carrier state
 - Properly resets port state on shutdown
---
 drivers/staging/Kconfig             |    2 +
 drivers/staging/Makefile            |    1 +
 drivers/staging/fwserial/Kconfig    |    9 +
 drivers/staging/fwserial/Makefile   |    2 +
 drivers/staging/fwserial/TODO       |   37 +
 drivers/staging/fwserial/dma_fifo.c |  310 ++++
 drivers/staging/fwserial/dma_fifo.h |  130 ++
 drivers/staging/fwserial/fwserial.c | 2946 +++++++++++++++++++++++++++++++++++
 drivers/staging/fwserial/fwserial.h |  387 +++++
 9 files changed, 3824 insertions(+)
 create mode 100644 drivers/staging/fwserial/Kconfig
 create mode 100644 drivers/staging/fwserial/Makefile
 create mode 100644 drivers/staging/fwserial/TODO
 create mode 100644 drivers/staging/fwserial/dma_fifo.c
 create mode 100644 drivers/staging/fwserial/dma_fifo.h
 create mode 100644 drivers/staging/fwserial/fwserial.c
 create mode 100644 drivers/staging/fwserial/fwserial.h

diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig
index d805eef..39696d6 100644
--- a/drivers/staging/Kconfig
+++ b/drivers/staging/Kconfig
@@ -144,4 +144,6 @@ source "drivers/staging/imx-drm/Kconfig"
 
 source "drivers/staging/dgrp/Kconfig"
 
+source "drivers/staging/fwserial/Kconfig"
+
 endif # STAGING
diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile
index 76e2ebd..5565d5f 100644
--- a/drivers/staging/Makefile
+++ b/drivers/staging/Makefile
@@ -64,3 +64,4 @@ obj-$(CONFIG_NET_VENDOR_SILICOM)	+= silicom/
 obj-$(CONFIG_CED1401)		+= ced1401/
 obj-$(CONFIG_DRM_IMX)		+= imx-drm/
 obj-$(CONFIG_DGRP)		+= dgrp/
+obj-$(CONFIG_FIREWIRE_SERIAL)	+= fwserial/
diff --git a/drivers/staging/fwserial/Kconfig b/drivers/staging/fwserial/Kconfig
new file mode 100644
index 0000000..580406c
--- /dev/null
+++ b/drivers/staging/fwserial/Kconfig
@@ -0,0 +1,9 @@
+config FIREWIRE_SERIAL
+       tristate "TTY over Firewire"
+       depends on FIREWIRE
+       help
+          This enables TTY over IEEE 1394, providing high-speed serial
+	  connectivity to cabled peers.
+
+	  To compile this driver as a module, say M here:  the module will
+	  be called firewire-serial.
diff --git a/drivers/staging/fwserial/Makefile b/drivers/staging/fwserial/Makefile
new file mode 100644
index 0000000..2170869
--- /dev/null
+++ b/drivers/staging/fwserial/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_FIREWIRE_SERIAL) += firewire-serial.o
+firewire-serial-objs := fwserial.o dma_fifo.o
diff --git a/drivers/staging/fwserial/TODO b/drivers/staging/fwserial/TODO
new file mode 100644
index 0000000..7269005
--- /dev/null
+++ b/drivers/staging/fwserial/TODO
@@ -0,0 +1,37 @@
+TODOs
+-----
+1. Implement retries for RCODE_BUSY, RCODE_NO_ACK and RCODE_SEND_ERROR
+   - I/O is handled asynchronously which presents some issues when error
+     conditions occur.
+2. Implement _robust_ console on top of this. The existing prototype console
+   driver is not ready for the big leagues yet.
+3. Expose means of controlling attach/detach of peers via sysfs. Include
+   GUID-to-port matching/whitelist/blacklist.
+
+-- Issues with firewire stack --
+1. This driver uses the same unregistered vendor id that the firewire core does
+     (0xd00d1e). Perhaps this could be exposed as a define in
+     firewire-constants.h?
+2. MAX_ASYNC_PAYLOAD needs to be publicly exposed by core/ohci
+   - otherwise how will this driver know the max size of address window to
+     open for one packet write?
+3. Maybe device_max_receive() and link_speed_to_max_payload() should be
+     taken up by the firewire core?
+4. To avoid dropping rx data while still limiting the maximum buffering,
+     the size of the AR context must be known. How to expose this to drivers?
+5. Explore if bigger AR context will reduce RCODE_BUSY responses
+   (or auto-grow to certain max size -- but this would require major surgery
+    as the current AR is contiguously mapped)
+
+-- Issues with TTY core --
+  1. Hack for alternate device name scheme
+     - because udev no longer allows device renaming, devices should have
+       their proper names on creation. This is an issue for creating the
+       fwloop<n> device with the fwtty<n> devices because although duplicating
+       roughly the same operations as tty_port_register_device() isn't difficult,
+       access to the tty_class & tty_fops is restricted in scope.
+
+       This is currently being worked around in create_loop_device() by
+       extracting the tty_class ptr and tty_fops ptr from the previously created
+       tty devices. Perhaps an add'l api can be added -- eg.,
+       tty_{port_}register_named_device().
diff --git a/drivers/staging/fwserial/dma_fifo.c b/drivers/staging/fwserial/dma_fifo.c
new file mode 100644
index 0000000..72aa053
--- /dev/null
+++ b/drivers/staging/fwserial/dma_fifo.c
@@ -0,0 +1,310 @@
+/*
+ * DMA-able FIFO implementation
+ *
+ * Copyright (C) 2012 Peter Hurley <peter@hurleysoftware.com>
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/bug.h>
+
+#include "dma_fifo.h"
+
+#ifdef DEBUG_TRACING
+#define df_trace(s, args...) pr_debug(s, ##args)
+#else
+#define df_trace(s, args...)
+#endif
+
+#define FAIL(fifo, condition, format...) ({				\
+	fifo->corrupt = !!(condition);					\
+	if (unlikely(fifo->corrupt)) {					\
+		__WARN_printf(format);					\
+	}								\
+	unlikely(fifo->corrupt);					\
+})
+
+/*
+ * private helper fn to determine if check is in open interval (lo,hi)
+ */
+static bool addr_check(unsigned check, unsigned lo, unsigned hi)
+{
+	return check - (lo + 1) < (hi - 1) - lo;
+}
+
+/**
+ * dma_fifo_init: initialize the fifo to a valid but inoperative state
+ * @fifo: address of in-place "struct dma_fifo" object
+ */
+void dma_fifo_init(struct dma_fifo *fifo)
+{
+	memset(fifo, 0, sizeof(*fifo));
+	INIT_LIST_HEAD(&fifo->pending);
+}
+
+/**
+ * dma_fifo_alloc - initialize and allocate dma_fifo
+ * @fifo: address of in-place "struct dma_fifo" object
+ * @size: 'apparent' size, in bytes, of fifo
+ * @align: dma alignment to maintain (should be at least cpu cache alignment),
+ *         must be power of 2
+ * @tx_limit: maximum # of bytes transmissable per dma (rounded down to
+ *            multiple of alignment, but at least align size)
+ * @open_limit: maximum # of outstanding dma transactions allowed
+ * @gfp_mask: get_free_pages mask, passed to kmalloc()
+ *
+ * The 'apparent' size will be rounded up to next greater aligned size.
+ * Returns 0 if no error, otherwise an error code
+ */
+int dma_fifo_alloc(struct dma_fifo *fifo, int size, unsigned align,
+		   int tx_limit, int open_limit, gfp_t gfp_mask)
+{
+	int capacity;
+
+	if (!is_power_of_2(align) || size < 0)
+		return -EINVAL;
+
+	size = round_up(size, align);
+	capacity = size + align * open_limit + align * DMA_FIFO_GUARD;
+	fifo->data = kmalloc(capacity, gfp_mask);
+	if (!fifo->data)
+		return -ENOMEM;
+
+	fifo->in = 0;
+	fifo->out = 0;
+	fifo->done = 0;
+	fifo->size = size;
+	fifo->avail = size;
+	fifo->align = align;
+	fifo->tx_limit = max_t(int, round_down(tx_limit, align), align);
+	fifo->open = 0;
+	fifo->open_limit = open_limit;
+	fifo->guard = size + align * open_limit;
+	fifo->capacity = capacity;
+	fifo->corrupt = 0;
+
+	return 0;
+}
+
+/**
+ * dma_fifo_free - frees the fifo
+ * @fifo: address of in-place "struct dma_fifo" to free
+ *
+ * Also reinits the fifo to a valid but inoperative state. This
+ * allows the fifo to be reused with a different target requiring
+ * different fifo parameters.
+ */
+void dma_fifo_free(struct dma_fifo *fifo)
+{
+	struct dma_pending *pending, *next;
+
+	if (fifo->data == NULL)
+		return;
+
+	list_for_each_entry_safe(pending, next, &fifo->pending, link)
+		list_del_init(&pending->link);
+	kfree(fifo->data);
+	fifo->data = NULL;
+}
+
+/**
+ * dma_fifo_reset - dumps the fifo contents and reinits for reuse
+ * @fifo: address of in-place "struct dma_fifo" to reset
+ */
+void dma_fifo_reset(struct dma_fifo *fifo)
+{
+	struct dma_pending *pending, *next;
+
+	if (fifo->data == NULL)
+		return;
+
+	list_for_each_entry_safe(pending, next, &fifo->pending, link)
+		list_del_init(&pending->link);
+	fifo->in = 0;
+	fifo->out = 0;
+	fifo->done = 0;
+	fifo->avail = fifo->size;
+	fifo->open = 0;
+	fifo->corrupt = 0;
+}
+
+/**
+ * dma_fifo_in - copies data into the fifo
+ * @fifo: address of in-place "struct dma_fifo" to write to
+ * @src: buffer to copy from
+ * @n: # of bytes to copy
+ *
+ * Returns the # of bytes actually copied, which can be less than requested if
+ * the fifo becomes full. If < 0, return is error code.
+ */
+int dma_fifo_in(struct dma_fifo *fifo, const void *src, int n)
+{
+	int ofs, l;
+
+	if (fifo->data == NULL)
+		return -ENOENT;
+	if (fifo->corrupt)
+		return -ENXIO;
+
+	if (n > fifo->avail)
+		n = fifo->avail;
+	if (n <= 0)
+		return 0;
+
+	ofs = fifo->in % fifo->capacity;
+	l = min(n, fifo->capacity - ofs);
+	memcpy(fifo->data + ofs, src, l);
+	memcpy(fifo->data, src + l, n - l);
+
+	if (FAIL(fifo, addr_check(fifo->done, fifo->in, fifo->in + n) ||
+			fifo->avail < n,
+			"fifo corrupt: in:%u out:%u done:%u n:%d avail:%d",
+			fifo->in, fifo->out, fifo->done, n, fifo->avail))
+		return -ENXIO;
+
+	fifo->in += n;
+	fifo->avail -= n;
+
+	df_trace("in:%u out:%u done:%u n:%d avail:%d", fifo->in, fifo->out,
+		 fifo->done, n, fifo->avail);
+
+	return n;
+}
+
+/**
+ * dma_fifo_out_pend - gets address/len of next avail read and marks as pended
+ * @fifo: address of in-place "struct dma_fifo" to read from
+ * @pended: address of structure to fill with read address/len
+ *          The data/len fields will be NULL/0 if no dma is pended.
+ *
+ * Returns the # of used bytes remaining in fifo (ie, if > 0, more data
+ * remains in the fifo that was not pended). If < 0, return is error code.
+ */
+int dma_fifo_out_pend(struct dma_fifo *fifo, struct dma_pending *pended)
+{
+	unsigned len, n, ofs, l, limit;
+
+	if (fifo->data == NULL)
+		return -ENOENT;
+	if (fifo->corrupt)
+		return -ENXIO;
+
+	pended->len = 0;
+	pended->data = NULL;
+	pended->out = fifo->out;
+
+	len = fifo->in - fifo->out;
+	if (!len)
+		return -ENODATA;
+	if (fifo->open == fifo->open_limit)
+		return -EAGAIN;
+
+	n = len;
+	ofs = fifo->out % fifo->capacity;
+	l = fifo->capacity - ofs;
+	limit = min_t(unsigned, l, fifo->tx_limit);
+	if (n > limit) {
+		n = limit;
+		fifo->out += limit;
+	} else if (ofs + n > fifo->guard) {
+		fifo->out += l;
+		fifo->in = fifo->out;
+	} else {
+		fifo->out += round_up(n, fifo->align);
+		fifo->in = fifo->out;
+	}
+
+	df_trace("in: %u out: %u done: %u n: %d len: %u avail: %d", fifo->in,
+		 fifo->out, fifo->done, n, len, fifo->avail);
+
+	pended->len = n;
+	pended->data = fifo->data + ofs;
+	pended->next = fifo->out;
+	list_add_tail(&pended->link, &fifo->pending);
+	++fifo->open;
+
+	if (FAIL(fifo, fifo->open > fifo->open_limit,
+			"past open limit:%d (limit:%d)",
+			fifo->open, fifo->open_limit))
+		return -ENXIO;
+	if (FAIL(fifo, fifo->out & (fifo->align - 1),
+			"fifo out unaligned:%u (align:%u)",
+			fifo->out, fifo->align))
+		return -ENXIO;
+
+	return len - n;
+}
+
+/**
+ * dma_fifo_out_complete - marks pended dma as completed
+ * @fifo: address of in-place "struct dma_fifo" which was read from
+ * @complete: address of structure for previously pended dma to mark completed
+ */
+int dma_fifo_out_complete(struct dma_fifo *fifo, struct dma_pending *complete)
+{
+	struct dma_pending *pending, *next, *tmp;
+
+	if (fifo->data == NULL)
+		return -ENOENT;
+	if (fifo->corrupt)
+		return -ENXIO;
+	if (list_empty(&fifo->pending) && fifo->open == 0)
+		return -EINVAL;
+
+	if (FAIL(fifo, list_empty(&fifo->pending) != (fifo->open == 0),
+			"pending list disagrees with open count:%d",
+			fifo->open))
+		return -ENXIO;
+
+	tmp = complete->data;
+	*tmp = *complete;
+	list_replace(&complete->link, &tmp->link);
+	dp_mark_completed(tmp);
+
+	/* Only update the fifo in the original pended order */
+	list_for_each_entry_safe(pending, next, &fifo->pending, link) {
+		if (!dp_is_completed(pending)) {
+			df_trace("still pending: saved out: %u len: %d",
+				 pending->out, pending->len);
+			break;
+		}
+
+		if (FAIL(fifo, pending->out != fifo->done ||
+				addr_check(fifo->in, fifo->done, pending->next),
+				"in:%u out:%u done:%u saved:%u next:%u",
+				fifo->in, fifo->out, fifo->done, pending->out,
+				pending->next))
+			return -ENXIO;
+
+		list_del_init(&pending->link);
+		fifo->done = pending->next;
+		fifo->avail += pending->len;
+		--fifo->open;
+
+		df_trace("in: %u out: %u done: %u len: %u avail: %d", fifo->in,
+			 fifo->out, fifo->done, pending->len, fifo->avail);
+	}
+
+	if (FAIL(fifo, fifo->open < 0, "open dma:%d < 0", fifo->open))
+		return -ENXIO;
+	if (FAIL(fifo, fifo->avail > fifo->size, "fifo avail:%d > size:%d",
+			fifo->avail, fifo->size))
+		return -ENXIO;
+
+	return 0;
+}
diff --git a/drivers/staging/fwserial/dma_fifo.h b/drivers/staging/fwserial/dma_fifo.h
new file mode 100644
index 0000000..a113fe1
--- /dev/null
+++ b/drivers/staging/fwserial/dma_fifo.h
@@ -0,0 +1,130 @@
+/*
+ * DMA-able FIFO interface
+ *
+ * Copyright (C) 2012 Peter Hurley <peter@hurleysoftware.com>
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _DMA_FIFO_H_
+#define _DMA_FIFO_H_
+
+/**
+ * The design basis for the DMA FIFO is to provide an output side that
+ * complies with the streaming DMA API design that can be DMA'd from directly
+ * (without additional copying), coupled with an input side that maintains a
+ * logically consistent 'apparent' size (ie, bytes in + bytes avail is static
+ * for the lifetime of the FIFO).
+ *
+ * DMA output transactions originate on a cache line boundary and can be
+ * variably-sized. DMA output transactions can be retired out-of-order but
+ * the FIFO will only advance the output in the original input sequence.
+ * This means the FIFO will eventually stall if a transaction is never retired.
+ *
+ * Chunking the output side into cache line multiples means that some FIFO
+ * memory is unused. For example, if all the avail input has been pended out,
+ * then the in and out markers are re-aligned to the next cache line.
+ * The maximum possible waste is
+ *     (cache line alignment - 1) * (max outstanding dma transactions)
+ * This potential waste requires additional hidden capacity within the FIFO
+ * to be able to accept input while the 'apparent' size has not been reached.
+ *
+ * Additional cache lines (ie, guard area) are used to minimize DMA
+ * fragmentation when wrapping at the end of the FIFO. Input is allowed into the
+ * guard area, but the in and out FIFO markers are wrapped when DMA is pended.
+ */
+
+#define DMA_FIFO_GUARD 3   /* # of cache lines to reserve for the guard area */
+
+struct dma_fifo {
+	unsigned	 in;
+	unsigned	 out;		/* updated when dma is pended         */
+	unsigned	 done;		/* updated upon dma completion        */
+	struct {
+		unsigned corrupt:1;
+	};
+	int		 size;		/* 'apparent' size of fifo	      */
+	int		 guard;		/* ofs of guard area		      */
+	int		 capacity;	/* size + reserved                    */
+	int		 avail;		/* # of unused bytes in fifo          */
+	unsigned	 align;		/* must be power of 2                 */
+	int		 tx_limit;	/* max # of bytes per dma transaction */
+	int		 open_limit;	/* max # of outstanding allowed       */
+	int		 open;		/* # of outstanding dma transactions  */
+	struct list_head pending;	/* fifo markers for outstanding dma   */
+	void		 *data;
+};
+
+struct dma_pending {
+	struct list_head link;
+	void		 *data;
+	unsigned	 len;
+	unsigned         next;
+	unsigned         out;
+};
+
+static inline void dp_mark_completed(struct dma_pending *dp)
+{
+	dp->data += 1;
+}
+
+static inline bool dp_is_completed(struct dma_pending *dp)
+{
+	return (unsigned long)dp->data & 1UL;
+}
+
+extern void dma_fifo_init(struct dma_fifo *fifo);
+extern int dma_fifo_alloc(struct dma_fifo *fifo, int size, unsigned align,
+			  int tx_limit, int open_limit, gfp_t gfp_mask);
+extern void dma_fifo_free(struct dma_fifo *fifo);
+extern void dma_fifo_reset(struct dma_fifo *fifo);
+extern int dma_fifo_in(struct dma_fifo *fifo, const void *src, int n);
+extern int dma_fifo_out_pend(struct dma_fifo *fifo, struct dma_pending *pended);
+extern int dma_fifo_out_complete(struct dma_fifo *fifo,
+				 struct dma_pending *complete);
+
+/* returns the # of used bytes in the fifo */
+static inline int dma_fifo_level(struct dma_fifo *fifo)
+{
+	return fifo->size - fifo->avail;
+}
+
+/* returns the # of bytes ready for output in the fifo */
+static inline int dma_fifo_out_level(struct dma_fifo *fifo)
+{
+	return fifo->in - fifo->out;
+}
+
+/* returns the # of unused bytes in the fifo */
+static inline int dma_fifo_avail(struct dma_fifo *fifo)
+{
+	return fifo->avail;
+}
+
+/* returns true if fifo has max # of outstanding dmas */
+static inline bool dma_fifo_busy(struct dma_fifo *fifo)
+{
+	return fifo->open == fifo->open_limit;
+}
+
+/* changes the max size of dma returned from dma_fifo_out_pend() */
+static inline int dma_fifo_change_tx_limit(struct dma_fifo *fifo, int tx_limit)
+{
+	tx_limit = round_down(tx_limit, fifo->align);
+	fifo->tx_limit = max_t(int, tx_limit, fifo->align);
+	return 0;
+}
+
+#endif /* _DMA_FIFO_H_ */
diff --git a/drivers/staging/fwserial/fwserial.c b/drivers/staging/fwserial/fwserial.c
new file mode 100644
index 0000000..5d4d64a
--- /dev/null
+++ b/drivers/staging/fwserial/fwserial.c
@@ -0,0 +1,2946 @@
+/*
+ * FireWire Serial driver
+ *
+ * Copyright (C) 2012 Peter Hurley <peter@hurleysoftware.com>
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/mod_devicetable.h>
+#include <linux/rculist.h>
+#include <linux/workqueue.h>
+#include <linux/ratelimit.h>
+#include <linux/bug.h>
+#include <linux/uaccess.h>
+
+#include "fwserial.h"
+
+#define be32_to_u64(hi, lo)  ((u64)be32_to_cpu(hi) << 32 | be32_to_cpu(lo))
+
+#define LINUX_VENDOR_ID   0xd00d1eU  /* same id used in card root directory   */
+#define FWSERIAL_VERSION  0x00e81cU  /* must be unique within LINUX_VENDOR_ID */
+
+/* configurable options */
+static int num_ttys = 4;	    /* # of std ttys to create per fw_card    */
+				    /* - doubles as loopback port index       */
+static bool auto_connect = true;    /* try to VIRT_CABLE to every peer        */
+static bool create_loop_dev = true; /* create a loopback device for each card */
+bool limit_bw;			    /* limit async bandwidth to 20% of max    */
+
+module_param_named(ttys, num_ttys, int, S_IRUGO | S_IWUSR);
+module_param_named(auto, auto_connect, bool, S_IRUGO | S_IWUSR);
+module_param_named(loop, create_loop_dev, bool, S_IRUGO | S_IWUSR);
+module_param(limit_bw, bool, S_IRUGO | S_IWUSR);
+
+/*
+ * Threshold below which the tty is woken for writing
+ * - should be equal to WAKEUP_CHARS in drivers/tty/n_tty.c because
+ *   even if the writer is woken, n_tty_poll() won't set POLLOUT until
+ *   our fifo is below this level
+ */
+#define WAKEUP_CHARS             256
+
+/**
+ * fwserial_list: list of every fw_serial created for each fw_card
+ * See discussion in fwserial_probe.
+ */
+static LIST_HEAD(fwserial_list);
+static DEFINE_MUTEX(fwserial_list_mutex);
+
+/**
+ * port_table: array of tty ports allocated to each fw_card
+ *
+ * tty ports are allocated during probe when an fw_serial is first
+ * created for a given fw_card. Ports are allocated in a contiguous block,
+ * each block consisting of 'num_ports' ports.
+ */
+static struct fwtty_port *port_table[MAX_TOTAL_PORTS];
+static DEFINE_MUTEX(port_table_lock);
+static bool port_table_corrupt;
+#define FWTTY_INVALID_INDEX  MAX_TOTAL_PORTS
+
+/* total # of tty ports created per fw_card */
+static int num_ports;
+
+/* slab used as pool for struct fwtty_transactions */
+static struct kmem_cache *fwtty_txn_cache;
+
+struct fwtty_transaction;
+typedef void (*fwtty_transaction_cb)(struct fw_card *card, int rcode,
+				     void *data, size_t length,
+				     struct fwtty_transaction *txn);
+
+struct fwtty_transaction {
+	struct fw_transaction      fw_txn;
+	fwtty_transaction_cb       callback;
+	struct fwtty_port	   *port;
+	union {
+		struct dma_pending dma_pended;
+	};
+};
+
+#define to_device(a, b)			(a->b)
+#define fwtty_err(p, s, v...)		dev_err(to_device(p, device), s, ##v)
+#define fwtty_info(p, s, v...)		dev_info(to_device(p, device), s, ##v)
+#define fwtty_notice(p, s, v...)	dev_notice(to_device(p, device), s, ##v)
+#define fwtty_dbg(p, s, v...)		\
+		dev_dbg(to_device(p, device), "%s: " s, __func__, ##v)
+#define fwtty_err_ratelimited(p, s, v...) \
+		dev_err_ratelimited(to_device(p, device), s, ##v)
+
+#ifdef DEBUG
+static inline void debug_short_write(struct fwtty_port *port, int c, int n)
+{
+	int avail;
+
+	if (n < c) {
+		spin_lock_bh(&port->lock);
+		avail = dma_fifo_avail(&port->tx_fifo);
+		spin_unlock_bh(&port->lock);
+		fwtty_dbg(port, "short write: avail:%d req:%d wrote:%d",
+			  avail, c, n);
+	}
+}
+#else
+#define debug_short_write(port, c, n)
+#endif
+
+static struct fwtty_peer *__fwserial_peer_by_node_id(struct fw_card *card,
+						     int generation, int id);
+
+#ifdef FWTTY_PROFILING
+
+static void profile_fifo_avail(struct fwtty_port *port, unsigned *stat)
+{
+	spin_lock_bh(&port->lock);
+	profile_size_distrib(stat, dma_fifo_avail(&port->tx_fifo));
+	spin_unlock_bh(&port->lock);
+}
+
+static void dump_profile(struct seq_file *m, struct stats *stats)
+{
+	/* for each stat, print sum of 0 to 2^k, then individually */
+	int k = 4;
+	unsigned sum;
+	int j;
+	char t[10];
+
+	snprintf(t, 10, "< %d", 1 << k);
+	seq_printf(m, "\n%14s  %6s", " ", t);
+	for (j = k + 1; j < DISTRIBUTION_MAX_INDEX; ++j)
+		seq_printf(m, "%6d", 1 << j);
+
+	++k;
+	for (j = 0, sum = 0; j <= k; ++j)
+		sum += stats->reads[j];
+	seq_printf(m, "\n%14s: %6d", "reads", sum);
+	for (j = k + 1; j <= DISTRIBUTION_MAX_INDEX; ++j)
+		seq_printf(m, "%6d", stats->reads[j]);
+
+	for (j = 0, sum = 0; j <= k; ++j)
+		sum += stats->writes[j];
+	seq_printf(m, "\n%14s: %6d", "writes", sum);
+	for (j = k + 1; j <= DISTRIBUTION_MAX_INDEX; ++j)
+		seq_printf(m, "%6d", stats->writes[j]);
+
+	for (j = 0, sum = 0; j <= k; ++j)
+		sum += stats->txns[j];
+	seq_printf(m, "\n%14s: %6d", "txns", sum);
+	for (j = k + 1; j <= DISTRIBUTION_MAX_INDEX; ++j)
+		seq_printf(m, "%6d", stats->txns[j]);
+
+	for (j = 0, sum = 0; j <= k; ++j)
+		sum += stats->unthrottle[j];
+	seq_printf(m, "\n%14s: %6d", "avail @ unthr", sum);
+	for (j = k + 1; j <= DISTRIBUTION_MAX_INDEX; ++j)
+		seq_printf(m, "%6d", stats->unthrottle[j]);
+}
+
+#else
+#define profile_fifo_avail(port, stat)
+#define dump_profile(m, stats)
+#endif
+
+/* Returns the max receive packet size for the given card */
+static inline int device_max_receive(struct fw_device *fw_device)
+{
+	return 1 <<  (clamp_t(int, fw_device->max_rec, 8U, 13U) + 1);
+}
+
+static void fwtty_log_tx_error(struct fwtty_port *port, int rcode)
+{
+	switch (rcode) {
+	case RCODE_SEND_ERROR:
+		fwtty_err_ratelimited(port, "card busy");
+		break;
+	case RCODE_ADDRESS_ERROR:
+		fwtty_err_ratelimited(port, "bad unit addr or write length");
+		break;
+	case RCODE_DATA_ERROR:
+		fwtty_err_ratelimited(port, "failed rx");
+		break;
+	case RCODE_NO_ACK:
+		fwtty_err_ratelimited(port, "missing ack");
+		break;
+	case RCODE_BUSY:
+		fwtty_err_ratelimited(port, "remote busy");
+		break;
+	default:
+		fwtty_err_ratelimited(port, "failed tx: %d", rcode);
+	}
+}
+
+static void fwtty_txn_constructor(void *this)
+{
+	struct fwtty_transaction *txn = this;
+
+	init_timer(&txn->fw_txn.split_timeout_timer);
+}
+
+static void fwtty_common_callback(struct fw_card *card, int rcode,
+				  void *payload, size_t len, void *cb_data)
+{
+	struct fwtty_transaction *txn = cb_data;
+	struct fwtty_port *port = txn->port;
+
+	if (port && rcode != RCODE_COMPLETE)
+		fwtty_log_tx_error(port, rcode);
+	if (txn->callback)
+		txn->callback(card, rcode, payload, len, txn);
+	kmem_cache_free(fwtty_txn_cache, txn);
+}
+
+static int fwtty_send_data_async(struct fwtty_peer *peer, int tcode,
+				 unsigned long long addr, void *payload,
+				 size_t len, fwtty_transaction_cb callback,
+				 struct fwtty_port *port)
+{
+	struct fwtty_transaction *txn;
+	int generation;
+
+	txn = kmem_cache_alloc(fwtty_txn_cache, GFP_ATOMIC);
+	if (!txn)
+		return -ENOMEM;
+
+	txn->callback = callback;
+	txn->port = port;
+
+	generation = peer->generation;
+	smp_rmb();
+	fw_send_request(peer->serial->card, &txn->fw_txn, tcode,
+			peer->node_id, generation, peer->speed, addr, payload,
+			len, fwtty_common_callback, txn);
+	return 0;
+}
+
+static void fwtty_send_txn_async(struct fwtty_peer *peer,
+				 struct fwtty_transaction *txn, int tcode,
+				 unsigned long long addr, void *payload,
+				 size_t len, fwtty_transaction_cb callback,
+				 struct fwtty_port *port)
+{
+	int generation;
+
+	txn->callback = callback;
+	txn->port = port;
+
+	generation = peer->generation;
+	smp_rmb();
+	fw_send_request(peer->serial->card, &txn->fw_txn, tcode,
+			peer->node_id, generation, peer->speed, addr, payload,
+			len, fwtty_common_callback, txn);
+}
+
+
+static void __fwtty_restart_tx(struct fwtty_port *port)
+{
+	int len, avail;
+
+	len = dma_fifo_out_level(&port->tx_fifo);
+	if (len)
+		schedule_delayed_work(&port->drain, 0);
+	avail = dma_fifo_avail(&port->tx_fifo);
+
+	fwtty_dbg(port, "fifo len: %d avail: %d", len, avail);
+}
+
+static void fwtty_restart_tx(struct fwtty_port *port)
+{
+	spin_lock_bh(&port->lock);
+	__fwtty_restart_tx(port);
+	spin_unlock_bh(&port->lock);
+}
+
+/**
+ * fwtty_update_port_status - decodes & dispatches line status changes
+ *
+ * Note: in loopback, the port->lock is being held. Only use functions that
+ * don't attempt to reclaim the port->lock.
+ */
+static void fwtty_update_port_status(struct fwtty_port *port, unsigned status)
+{
+	unsigned delta;
+	struct tty_struct *tty;
+
+	/* simulated LSR/MSR status from remote */
+	status &= ~MCTRL_MASK;
+	delta = (port->mstatus ^ status) & ~MCTRL_MASK;
+	delta &= ~(status & TIOCM_RNG);
+	port->mstatus = status;
+
+	if (delta & TIOCM_RNG)
+		++port->icount.rng;
+	if (delta & TIOCM_DSR)
+		++port->icount.dsr;
+	if (delta & TIOCM_CAR)
+		++port->icount.dcd;
+	if (delta & TIOCM_CTS)
+		++port->icount.cts;
+
+	fwtty_dbg(port, "status: %x delta: %x", status, delta);
+
+	if (delta & TIOCM_CAR) {
+		tty = tty_port_tty_get(&port->port);
+		if (tty && !C_CLOCAL(tty)) {
+			if (status & TIOCM_CAR)
+				wake_up_interruptible(&port->port.open_wait);
+			else
+				schedule_work(&port->hangup);
+		}
+		tty_kref_put(tty);
+	}
+
+	if (delta & TIOCM_CTS) {
+		tty = tty_port_tty_get(&port->port);
+		if (tty && C_CRTSCTS(tty)) {
+			if (tty->hw_stopped) {
+				if (status & TIOCM_CTS) {
+					tty->hw_stopped = 0;
+					if (port->loopback)
+						__fwtty_restart_tx(port);
+					else
+						fwtty_restart_tx(port);
+				}
+			} else {
+				if (~status & TIOCM_CTS)
+					tty->hw_stopped = 1;
+			}
+		}
+		tty_kref_put(tty);
+
+	} else if (delta & OOB_TX_THROTTLE) {
+		tty = tty_port_tty_get(&port->port);
+		if (tty) {
+			if (tty->hw_stopped) {
+				if (~status & OOB_TX_THROTTLE) {
+					tty->hw_stopped = 0;
+					if (port->loopback)
+						__fwtty_restart_tx(port);
+					else
+						fwtty_restart_tx(port);
+				}
+			} else {
+				if (status & OOB_TX_THROTTLE)
+					tty->hw_stopped = 1;
+			}
+		}
+		tty_kref_put(tty);
+	}
+
+	if (delta & (UART_LSR_BI << 24)) {
+		if (status & (UART_LSR_BI << 24)) {
+			port->break_last = jiffies;
+			schedule_delayed_work(&port->emit_breaks, 0);
+		} else {
+			/* run emit_breaks one last time (if pending) */
+			mod_delayed_work(system_wq, &port->emit_breaks, 0);
+		}
+	}
+
+	if (delta & (TIOCM_DSR | TIOCM_CAR | TIOCM_CTS | TIOCM_RNG))
+		wake_up_interruptible(&port->port.delta_msr_wait);
+}
+
+/**
+ * __fwtty_port_line_status - generate 'line status' for indicated port
+ *
+ * This function returns a remote 'MSR' state based on the local 'MCR' state,
+ * as if a null modem cable was attached. The actual status is a mangling
+ * of TIOCM_* bits suitable for sending to a peer's status_addr.
+ *
+ * Note: caller must be holding port lock
+ */
+static unsigned __fwtty_port_line_status(struct fwtty_port *port)
+{
+	unsigned status = 0;
+
+	/* TODO: add module param to tie RNG to DTR as well */
+
+	if (port->mctrl & TIOCM_DTR)
+		status |= TIOCM_DSR | TIOCM_CAR;
+	if (port->mctrl & TIOCM_RTS)
+		status |= TIOCM_CTS;
+	if (port->mctrl & OOB_RX_THROTTLE)
+		status |= OOB_TX_THROTTLE;
+	/* emulate BRK as add'l line status */
+	if (port->break_ctl)
+		status |= UART_LSR_BI << 24;
+
+	return status;
+}
+
+/**
+ * __fwtty_write_port_status - send the port line status to peer
+ *
+ * Note: caller must be holding the port lock.
+ */
+static int __fwtty_write_port_status(struct fwtty_port *port)
+{
+	struct fwtty_peer *peer;
+	int err = -ENOENT;
+	unsigned status = __fwtty_port_line_status(port);
+
+	rcu_read_lock();
+	peer = rcu_dereference(port->peer);
+	if (peer) {
+		err = fwtty_send_data_async(peer, TCODE_WRITE_QUADLET_REQUEST,
+					    peer->status_addr, &status,
+					    sizeof(status), NULL, port);
+	}
+	rcu_read_unlock();
+
+	return err;
+}
+
+/**
+ * fwtty_write_port_status - same as above but locked by port lock
+ */
+static int fwtty_write_port_status(struct fwtty_port *port)
+{
+	int err;
+
+	spin_lock_bh(&port->lock);
+	err = __fwtty_write_port_status(port);
+	spin_unlock_bh(&port->lock);
+	return err;
+}
+
+static void __fwtty_throttle(struct fwtty_port *port, struct tty_struct *tty)
+{
+	unsigned old;
+
+	old = port->mctrl;
+	port->mctrl |= OOB_RX_THROTTLE;
+	if (C_CRTSCTS(tty))
+		port->mctrl &= ~TIOCM_RTS;
+	if (~old & OOB_RX_THROTTLE)
+		__fwtty_write_port_status(port);
+}
+
+/**
+ * fwtty_do_hangup - wait for ldisc to deliver all pending rx; only then hangup
+ *
+ * When the remote has finished tx, and all in-flight rx has been received and
+ * and pushed to the flip buffer, the remote may close its device. This will
+ * drop DTR on the remote which will drop carrier here. Typically, the tty is
+ * hung up when carrier is dropped or lost.
+ *
+ * However, there is a race between the hang up and the line discipline
+ * delivering its data to the reader. A hangup will cause the ldisc to flush
+ * (ie., clear) the read buffer and flip buffer. Because of firewire's
+ * relatively high throughput, the ldisc frequently lags well behind the driver,
+ * resulting in lost data (which has already been received and written to
+ * the flip buffer) when the remote closes its end.
+ *
+ * Unfortunately, since the flip buffer offers no direct method for determining
+ * if it holds data, ensuring the ldisc has delivered all data is problematic.
+ */
+
+/* FIXME: drop this workaround when __tty_hangup waits for ldisc completion */
+static void fwtty_do_hangup(struct work_struct *work)
+{
+	struct fwtty_port *port = to_port(work, hangup);
+	struct tty_struct *tty;
+
+	schedule_timeout_uninterruptible(msecs_to_jiffies(50));
+
+	tty = tty_port_tty_get(&port->port);
+	if (tty)
+		tty_vhangup(tty);
+	tty_kref_put(tty);
+}
+
+
+static void fwtty_emit_breaks(struct work_struct *work)
+{
+	struct fwtty_port *port = to_port(to_delayed_work(work), emit_breaks);
+	struct tty_struct *tty;
+	static const char buf[16];
+	unsigned long now = jiffies;
+	unsigned long elapsed = now - port->break_last;
+	int n, t, c, brk = 0;
+
+	tty = tty_port_tty_get(&port->port);
+	if (!tty)
+		return;
+
+	/* generate breaks at the line rate (but at least 1) */
+	n = (elapsed * port->cps) / HZ + 1;
+	port->break_last = now;
+
+	fwtty_dbg(port, "sending %d brks", n);
+
+	while (n) {
+		t = min(n, 16);
+		c = tty_insert_flip_string_fixed_flag(tty, buf, TTY_BREAK, t);
+		n -= c;
+		brk += c;
+		if (c < t)
+			break;
+	}
+	tty_flip_buffer_push(tty);
+
+	tty_kref_put(tty);
+
+	if (port->mstatus & (UART_LSR_BI << 24))
+		schedule_delayed_work(&port->emit_breaks, FREQ_BREAKS);
+	port->icount.brk += brk;
+}
+
+static void fwtty_pushrx(struct work_struct *work)
+{
+	struct fwtty_port *port = to_port(work, push);
+	struct tty_struct *tty;
+	struct buffered_rx *buf, *next;
+	int n, c = 0;
+
+	tty = tty_port_tty_get(&port->port);
+	if (!tty)
+		return;
+
+	spin_lock_bh(&port->lock);
+	list_for_each_entry_safe(buf, next, &port->buf_list, list) {
+		n = tty_insert_flip_string_fixed_flag(tty, buf->data,
+						      TTY_NORMAL, buf->n);
+		c += n;
+		port->buffered -= n;
+		if (n < buf->n) {
+			if (n > 0) {
+				memmove(buf->data, buf->data + n, buf->n - n);
+				buf->n -= n;
+			}
+			__fwtty_throttle(port, tty);
+			break;
+		} else {
+			list_del(&buf->list);
+			kfree(buf);
+		}
+	}
+	if (c > 0)
+		tty_flip_buffer_push(tty);
+
+	if (list_empty(&port->buf_list))
+		clear_bit(BUFFERING_RX, &port->flags);
+	spin_unlock_bh(&port->lock);
+
+	tty_kref_put(tty);
+}
+
+static int fwtty_buffer_rx(struct fwtty_port *port, unsigned char *d, size_t n)
+{
+	struct buffered_rx *buf;
+	size_t size = (n + sizeof(struct buffered_rx) + 0xFF) & ~0xFF;
+
+	if (port->buffered + n > HIGH_WATERMARK)
+		return 0;
+	buf = kmalloc(size, GFP_ATOMIC);
+	if (!buf)
+		return 0;
+	INIT_LIST_HEAD(&buf->list);
+	buf->n = n;
+	memcpy(buf->data, d, n);
+
+	spin_lock_bh(&port->lock);
+	list_add_tail(&buf->list, &port->buf_list);
+	port->buffered += n;
+	if (port->buffered > port->stats.watermark)
+		port->stats.watermark = port->buffered;
+	set_bit(BUFFERING_RX, &port->flags);
+	spin_unlock_bh(&port->lock);
+
+	return n;
+}
+
+static int fwtty_rx(struct fwtty_port *port, unsigned char *data, size_t len)
+{
+	struct tty_struct *tty;
+	int c, n = len;
+	unsigned lsr;
+	int err = 0;
+
+	tty = tty_port_tty_get(&port->port);
+	if (!tty)
+		return -ENOENT;
+
+	fwtty_dbg(port, "%d", n);
+	profile_size_distrib(port->stats.reads, n);
+
+	if (port->write_only) {
+		n = 0;
+		goto out;
+	}
+
+	/* disregard break status; breaks are generated by emit_breaks work */
+	lsr = (port->mstatus >> 24) & ~UART_LSR_BI;
+
+	if (port->overrun)
+		lsr |= UART_LSR_OE;
+
+	if (lsr & UART_LSR_OE)
+		++port->icount.overrun;
+
+	lsr &= port->status_mask;
+	if (lsr & ~port->ignore_mask & UART_LSR_OE) {
+		if (!tty_insert_flip_char(tty, 0, TTY_OVERRUN)) {
+			err = -EIO;
+			goto out;
+		}
+	}
+	port->overrun = false;
+
+	if (lsr & port->ignore_mask & ~UART_LSR_OE) {
+		/* TODO: don't drop SAK and Magic SysRq here */
+		n = 0;
+		goto out;
+	}
+
+	if (!test_bit(BUFFERING_RX, &port->flags)) {
+		c = tty_insert_flip_string_fixed_flag(tty, data, TTY_NORMAL, n);
+		if (c > 0)
+			tty_flip_buffer_push(tty);
+		n -= c;
+
+		if (n) {
+			/* start buffering and throttling */
+			n -= fwtty_buffer_rx(port, &data[c], n);
+
+			spin_lock_bh(&port->lock);
+			__fwtty_throttle(port, tty);
+			spin_unlock_bh(&port->lock);
+		}
+	} else
+		n -= fwtty_buffer_rx(port, data, n);
+
+	if (n) {
+		port->overrun = true;
+		err = -EIO;
+	}
+
+out:
+	tty_kref_put(tty);
+
+	port->icount.rx += len;
+	port->stats.lost += n;
+	return err;
+}
+
+/**
+ * fwtty_port_handler - bus address handler for port reads/writes
+ * @parameters: fw_address_callback_t as specified by firewire core interface
+ *
+ * This handler is responsible for handling inbound read/write dma from remotes.
+ */
+static void fwtty_port_handler(struct fw_card *card,
+			       struct fw_request *request,
+			       int tcode, int destination, int source,
+			       int generation,
+			       unsigned long long addr,
+			       void *data, size_t len,
+			       void *callback_data)
+{
+	struct fwtty_port *port = callback_data;
+	struct fwtty_peer *peer;
+	int err;
+	int rcode;
+
+	/* Only accept rx from the peer virtual-cabled to this port */
+	rcu_read_lock();
+	peer = __fwserial_peer_by_node_id(card, generation, source);
+	rcu_read_unlock();
+	if (!peer || peer != rcu_access_pointer(port->peer)) {
+		rcode = RCODE_ADDRESS_ERROR;
+		fwtty_err_ratelimited(port, "ignoring unauthenticated data");
+		goto respond;
+	}
+
+	switch (tcode) {
+	case TCODE_WRITE_QUADLET_REQUEST:
+		if (addr != port->rx_handler.offset || len != 4)
+			rcode = RCODE_ADDRESS_ERROR;
+		else {
+			fwtty_update_port_status(port, *(unsigned *)data);
+			rcode = RCODE_COMPLETE;
+		}
+		break;
+
+	case TCODE_WRITE_BLOCK_REQUEST:
+		if (addr != port->rx_handler.offset + 4 ||
+		    len > port->rx_handler.length - 4) {
+			rcode = RCODE_ADDRESS_ERROR;
+		} else {
+			err = fwtty_rx(port, data, len);
+			switch (err) {
+			case 0:
+				rcode = RCODE_COMPLETE;
+				break;
+			case -EIO:
+				rcode = RCODE_DATA_ERROR;
+				break;
+			default:
+				rcode = RCODE_CONFLICT_ERROR;
+				break;
+			}
+		}
+		break;
+
+	default:
+		rcode = RCODE_TYPE_ERROR;
+	}
+
+respond:
+	fw_send_response(card, request, rcode);
+}
+
+/**
+ * fwtty_tx_complete - callback for tx dma
+ * @data: ignored, has no meaning for write txns
+ * @length: ignored, has no meaning for write txns
+ *
+ * The writer must be woken here if the fifo has been emptied because it
+ * may have slept if chars_in_buffer was != 0
+ */
+static void fwtty_tx_complete(struct fw_card *card, int rcode,
+			      void *data, size_t length,
+			      struct fwtty_transaction *txn)
+{
+	struct fwtty_port *port = txn->port;
+	struct tty_struct *tty;
+	int len;
+
+	fwtty_dbg(port, "rcode: %d", rcode);
+
+	switch (rcode) {
+	case RCODE_COMPLETE:
+		spin_lock_bh(&port->lock);
+		dma_fifo_out_complete(&port->tx_fifo, &txn->dma_pended);
+		len = dma_fifo_level(&port->tx_fifo);
+		spin_unlock_bh(&port->lock);
+
+		port->icount.tx += txn->dma_pended.len;
+		break;
+
+	default:
+		/* TODO: implement retries */
+		spin_lock_bh(&port->lock);
+		dma_fifo_out_complete(&port->tx_fifo, &txn->dma_pended);
+		len = dma_fifo_level(&port->tx_fifo);
+		spin_unlock_bh(&port->lock);
+
+		port->stats.dropped += txn->dma_pended.len;
+	}
+
+	if (len < WAKEUP_CHARS) {
+		tty = tty_port_tty_get(&port->port);
+		if (tty) {
+			tty_wakeup(tty);
+			tty_kref_put(tty);
+		}
+	}
+}
+
+static int fwtty_tx(struct fwtty_port *port, bool drain)
+{
+	struct fwtty_peer *peer;
+	struct fwtty_transaction *txn;
+	struct tty_struct *tty;
+	int n, len;
+
+	tty = tty_port_tty_get(&port->port);
+	if (!tty)
+		return -ENOENT;
+
+	rcu_read_lock();
+	peer = rcu_dereference(port->peer);
+	if (!peer) {
+		n = -EIO;
+		goto out;
+	}
+
+	if (test_and_set_bit(IN_TX, &port->flags)) {
+		n = -EALREADY;
+		goto out;
+	}
+
+	/* try to write as many dma transactions out as possible */
+	n = -EAGAIN;
+	while (!tty->stopped && !tty->hw_stopped &&
+			!test_bit(STOP_TX, &port->flags)) {
+		txn = kmem_cache_alloc(fwtty_txn_cache, GFP_ATOMIC);
+		if (!txn) {
+			n = -ENOMEM;
+			break;
+		}
+
+		spin_lock_bh(&port->lock);
+		n = dma_fifo_out_pend(&port->tx_fifo, &txn->dma_pended);
+		spin_unlock_bh(&port->lock);
+
+		fwtty_dbg(port, "out: %u rem: %d", txn->dma_pended.len, n);
+
+		if (n < 0) {
+			kmem_cache_free(fwtty_txn_cache, txn);
+			if (n == -EAGAIN)
+				++port->stats.tx_stall;
+			else if (n == -ENODATA)
+				profile_size_distrib(port->stats.txns, 0);
+			else {
+				++port->stats.fifo_errs;
+				fwtty_err_ratelimited(port, "fifo err: %d", n);
+			}
+			break;
+		}
+
+		profile_size_distrib(port->stats.txns, txn->dma_pended.len);
+
+		fwtty_send_txn_async(peer, txn, TCODE_WRITE_BLOCK_REQUEST,
+				     peer->fifo_addr, txn->dma_pended.data,
+				     txn->dma_pended.len, fwtty_tx_complete,
+				     port);
+		++port->stats.sent;
+
+		/*
+		 * Stop tx if the 'last view' of the fifo is empty or if
+		 * this is the writer and there's not enough data to bother
+		 */
+		if (n == 0 || (!drain && n < WRITER_MINIMUM))
+			break;
+	}
+
+	if (n >= 0 || n == -EAGAIN || n == -ENOMEM || n == -ENODATA) {
+		spin_lock_bh(&port->lock);
+		len = dma_fifo_out_level(&port->tx_fifo);
+		if (len) {
+			unsigned long delay = (n == -ENOMEM) ? HZ : 1;
+			schedule_delayed_work(&port->drain, delay);
+		}
+		len = dma_fifo_level(&port->tx_fifo);
+		spin_unlock_bh(&port->lock);
+
+		/* wakeup the writer */
+		if (drain && len < WAKEUP_CHARS)
+			tty_wakeup(tty);
+	}
+
+	clear_bit(IN_TX, &port->flags);
+	wake_up_interruptible(&port->wait_tx);
+
+out:
+	rcu_read_unlock();
+	tty_kref_put(tty);
+	return n;
+}
+
+static void fwtty_drain_tx(struct work_struct *work)
+{
+	struct fwtty_port *port = to_port(to_delayed_work(work), drain);
+
+	fwtty_tx(port, true);
+}
+
+static void fwtty_write_xchar(struct fwtty_port *port, char ch)
+{
+	struct fwtty_peer *peer;
+
+	++port->stats.xchars;
+
+	fwtty_dbg(port, "%02x", ch);
+
+	rcu_read_lock();
+	peer = rcu_dereference(port->peer);
+	if (peer) {
+		fwtty_send_data_async(peer, TCODE_WRITE_BLOCK_REQUEST,
+				      peer->fifo_addr, &ch, sizeof(ch),
+				      NULL, port);
+	}
+	rcu_read_unlock();
+}
+
+struct fwtty_port *fwtty_port_get(unsigned index)
+{
+	struct fwtty_port *port;
+
+	if (index >= MAX_TOTAL_PORTS)
+		return NULL;
+
+	mutex_lock(&port_table_lock);
+	port = port_table[index];
+	if (port)
+		kref_get(&port->serial->kref);
+	mutex_unlock(&port_table_lock);
+	return port;
+}
+EXPORT_SYMBOL(fwtty_port_get);
+
+static int fwtty_ports_add(struct fw_serial *serial)
+{
+	int err = -EBUSY;
+	int i, j;
+
+	if (port_table_corrupt)
+		return err;
+
+	mutex_lock(&port_table_lock);
+	for (i = 0; i + num_ports <= MAX_TOTAL_PORTS; i += num_ports) {
+		if (!port_table[i]) {
+			for (j = 0; j < num_ports; ++i, ++j) {
+				serial->ports[j]->index = i;
+				port_table[i] = serial->ports[j];
+			}
+			err = 0;
+			break;
+		}
+	}
+	mutex_unlock(&port_table_lock);
+	return err;
+}
+
+static void fwserial_destroy(struct kref *kref)
+{
+	struct fw_serial *serial = to_serial(kref, kref);
+	struct fwtty_port **ports = serial->ports;
+	int j, i = ports[0]->index;
+
+	synchronize_rcu();
+
+	mutex_lock(&port_table_lock);
+	for (j = 0; j < num_ports; ++i, ++j) {
+		static bool once;
+		int corrupt = port_table[i] != ports[j];
+		if (corrupt && !once) {
+			WARN(corrupt, "port_table[%d]: %p != ports[%d]: %p",
+			     i, port_table[i], j, ports[j]);
+			once = true;
+			port_table_corrupt = true;
+		}
+
+		port_table[i] = NULL;
+	}
+	mutex_unlock(&port_table_lock);
+
+	for (j = 0; j < num_ports; ++j) {
+		fw_core_remove_address_handler(&ports[j]->rx_handler);
+		dma_fifo_free(&ports[j]->tx_fifo);
+		kfree(ports[j]);
+	}
+	kfree(serial);
+}
+
+void fwtty_port_put(struct fwtty_port *port)
+{
+	kref_put(&port->serial->kref, fwserial_destroy);
+}
+EXPORT_SYMBOL(fwtty_port_put);
+
+static void fwtty_port_dtr_rts(struct tty_port *tty_port, int on)
+{
+	struct fwtty_port *port = to_port(tty_port, port);
+
+	fwtty_dbg(port, "on/off: %d", on);
+
+	spin_lock_bh(&port->lock);
+	/* Don't change carrier state if this is a console */
+	if (!port->port.console) {
+		if (on)
+			port->mctrl |= TIOCM_DTR | TIOCM_RTS;
+		else
+			port->mctrl &= ~(TIOCM_DTR | TIOCM_RTS);
+	}
+
+	__fwtty_write_port_status(port);
+	spin_unlock_bh(&port->lock);
+}
+
+/**
+ * fwtty_port_carrier_raised: required tty_port operation
+ *
+ * This port operation is polled after a tty has been opened and is waiting for
+ * carrier detect -- see drivers/tty/tty_port:tty_port_block_til_ready().
+ */
+static int fwtty_port_carrier_raised(struct tty_port *tty_port)
+{
+	struct fwtty_port *port = to_port(tty_port, port);
+	int rc;
+
+	rc = (port->mstatus & TIOCM_CAR);
+
+	fwtty_dbg(port, "%d", rc);
+
+	return rc;
+}
+
+static unsigned set_termios(struct fwtty_port *port, struct tty_struct *tty)
+{
+	unsigned baud, frame;
+
+	baud = tty_termios_baud_rate(&tty->termios);
+	tty_termios_encode_baud_rate(&tty->termios, baud, baud);
+
+	/* compute bit count of 2 frames */
+	frame = 12 + ((C_CSTOPB(tty)) ? 4 : 2) + ((C_PARENB(tty)) ? 2 : 0);
+
+	switch (C_CSIZE(tty)) {
+	case CS5:
+		frame -= (C_CSTOPB(tty)) ? 1 : 0;
+		break;
+	case CS6:
+		frame += 2;
+		break;
+	case CS7:
+		frame += 4;
+		break;
+	case CS8:
+		frame += 6;
+		break;
+	}
+
+	port->cps = (baud << 1) / frame;
+
+	port->status_mask = UART_LSR_OE;
+	if (_I_FLAG(tty, BRKINT | PARMRK))
+		port->status_mask |= UART_LSR_BI;
+
+	port->ignore_mask = 0;
+	if (I_IGNBRK(tty)) {
+		port->ignore_mask |= UART_LSR_BI;
+		if (I_IGNPAR(tty))
+			port->ignore_mask |= UART_LSR_OE;
+	}
+
+	port->write_only = !C_CREAD(tty);
+
+	/* turn off echo and newline xlat if loopback */
+	if (port->loopback) {
+		tty->termios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHOKE |
+					  ECHONL | ECHOPRT | ECHOCTL);
+		tty->termios.c_oflag &= ~ONLCR;
+	}
+
+	return baud;
+}
+
+static int fwtty_port_activate(struct tty_port *tty_port,
+			       struct tty_struct *tty)
+{
+	struct fwtty_port *port = to_port(tty_port, port);
+	unsigned baud;
+	int err;
+
+	set_bit(TTY_IO_ERROR, &tty->flags);
+
+	err = dma_fifo_alloc(&port->tx_fifo, FWTTY_PORT_TXFIFO_LEN,
+			     cache_line_size(),
+			     port->max_payload,
+			     FWTTY_PORT_MAX_PEND_DMA,
+			     GFP_KERNEL);
+	if (err)
+		return err;
+
+	spin_lock_bh(&port->lock);
+
+	baud = set_termios(port, tty);
+
+	/* if console, don't change carrier state */
+	if (!port->port.console) {
+		port->mctrl = 0;
+		if (baud != 0)
+			port->mctrl = TIOCM_DTR | TIOCM_RTS;
+	}
+
+	if (C_CRTSCTS(tty) && ~port->mstatus & TIOCM_CTS)
+		tty->hw_stopped = 1;
+
+	__fwtty_write_port_status(port);
+	spin_unlock_bh(&port->lock);
+
+	clear_bit(TTY_IO_ERROR, &tty->flags);
+
+	return 0;
+}
+
+/**
+ * fwtty_port_shutdown
+ *
+ * Note: the tty port core ensures this is not the console and
+ * manages TTY_IO_ERROR properly
+ */
+static void fwtty_port_shutdown(struct tty_port *tty_port)
+{
+	struct fwtty_port *port = to_port(tty_port, port);
+	struct buffered_rx *buf, *next;
+
+	/* TODO: cancel outstanding transactions */
+
+	cancel_delayed_work_sync(&port->emit_breaks);
+	cancel_delayed_work_sync(&port->drain);
+	cancel_work_sync(&port->push);
+
+	spin_lock_bh(&port->lock);
+	list_for_each_entry_safe(buf, next, &port->buf_list, list) {
+		list_del(&buf->list);
+		kfree(buf);
+	}
+	port->buffered = 0;
+	port->flags = 0;
+	port->break_ctl = 0;
+	port->overrun = 0;
+	__fwtty_write_port_status(port);
+	dma_fifo_free(&port->tx_fifo);
+	spin_unlock_bh(&port->lock);
+}
+
+static int fwtty_open(struct tty_struct *tty, struct file *fp)
+{
+	struct fwtty_port *port = tty->driver_data;
+
+	return tty_port_open(&port->port, tty, fp);
+}
+
+static void fwtty_close(struct tty_struct *tty, struct file *fp)
+{
+	struct fwtty_port *port = tty->driver_data;
+
+	tty_port_close(&port->port, tty, fp);
+}
+
+static void fwtty_hangup(struct tty_struct *tty)
+{
+	struct fwtty_port *port = tty->driver_data;
+
+	tty_port_hangup(&port->port);
+}
+
+static void fwtty_cleanup(struct tty_struct *tty)
+{
+	struct fwtty_port *port = tty->driver_data;
+
+	tty->driver_data = NULL;
+	fwtty_port_put(port);
+}
+
+static int fwtty_install(struct tty_driver *driver, struct tty_struct *tty)
+{
+	struct fwtty_port *port = fwtty_port_get(tty->index);
+	int err;
+
+	err = tty_standard_install(driver, tty);
+	if (!err)
+		tty->driver_data = port;
+	else
+		fwtty_port_put(port);
+	return err;
+}
+
+static int fwtty_write(struct tty_struct *tty, const unsigned char *buf, int c)
+{
+	struct fwtty_port *port = tty->driver_data;
+	int n, len;
+
+	fwtty_dbg(port, "%d", c);
+	profile_size_distrib(port->stats.writes, c);
+
+	spin_lock_bh(&port->lock);
+	n = dma_fifo_in(&port->tx_fifo, buf, c);
+	len = dma_fifo_out_level(&port->tx_fifo);
+	if (len < DRAIN_THRESHOLD)
+		schedule_delayed_work(&port->drain, 1);
+	spin_unlock_bh(&port->lock);
+
+	if (len >= DRAIN_THRESHOLD)
+		fwtty_tx(port, false);
+
+	debug_short_write(port, c, n);
+
+	return (n < 0) ? 0 : n;
+}
+
+static int fwtty_write_room(struct tty_struct *tty)
+{
+	struct fwtty_port *port = tty->driver_data;
+	int n;
+
+	spin_lock_bh(&port->lock);
+	n = dma_fifo_avail(&port->tx_fifo);
+	spin_unlock_bh(&port->lock);
+
+	fwtty_dbg(port, "%d", n);
+
+	return n;
+}
+
+static int fwtty_chars_in_buffer(struct tty_struct *tty)
+{
+	struct fwtty_port *port = tty->driver_data;
+	int n;
+
+	spin_lock_bh(&port->lock);
+	n = dma_fifo_level(&port->tx_fifo);
+	spin_unlock_bh(&port->lock);
+
+	fwtty_dbg(port, "%d", n);
+
+	return n;
+}
+
+static void fwtty_send_xchar(struct tty_struct *tty, char ch)
+{
+	struct fwtty_port *port = tty->driver_data;
+
+	fwtty_dbg(port, "%02x", ch);
+
+	fwtty_write_xchar(port, ch);
+}
+
+static void fwtty_throttle(struct tty_struct *tty)
+{
+	struct fwtty_port *port = tty->driver_data;
+
+	/*
+	 * Ignore throttling (but not unthrottling).
+	 * It only makes sense to throttle when data will no longer be
+	 * accepted by the tty flip buffer. For example, it is
+	 * possible for received data to overflow the tty buffer long
+	 * before the line discipline ever has a chance to throttle the driver.
+	 * Additionally, the driver may have already completed the I/O
+	 * but the tty buffer is still emptying, so the line discipline is
+	 * throttling and unthrottling nothing.
+	 */
+
+	++port->stats.throttled;
+}
+
+static void fwtty_unthrottle(struct tty_struct *tty)
+{
+	struct fwtty_port *port = tty->driver_data;
+
+	fwtty_dbg(port, "CRTSCTS: %d", (C_CRTSCTS(tty) != 0));
+
+	profile_fifo_avail(port, port->stats.unthrottle);
+
+	schedule_work(&port->push);
+
+	spin_lock_bh(&port->lock);
+	port->mctrl &= ~OOB_RX_THROTTLE;
+	if (C_CRTSCTS(tty))
+		port->mctrl |= TIOCM_RTS;
+	__fwtty_write_port_status(port);
+	spin_unlock_bh(&port->lock);
+}
+
+static int check_msr_delta(struct fwtty_port *port, unsigned long mask,
+			   struct async_icount *prev)
+{
+	struct async_icount now;
+	int delta;
+
+	now = port->icount;
+
+	delta = ((mask & TIOCM_RNG && prev->rng != now.rng) ||
+		 (mask & TIOCM_DSR && prev->dsr != now.dsr) ||
+		 (mask & TIOCM_CAR && prev->dcd != now.dcd) ||
+		 (mask & TIOCM_CTS && prev->cts != now.cts));
+
+	*prev = now;
+
+	return delta;
+}
+
+static int wait_msr_change(struct fwtty_port *port, unsigned long mask)
+{
+	struct async_icount prev;
+
+	prev = port->icount;
+
+	return wait_event_interruptible(port->port.delta_msr_wait,
+					check_msr_delta(port, mask, &prev));
+}
+
+static int get_serial_info(struct fwtty_port *port,
+			   struct serial_struct __user *info)
+{
+	struct serial_struct tmp;
+
+	memset(&tmp, 0, sizeof(tmp));
+
+	tmp.type =  PORT_UNKNOWN;
+	tmp.line =  port->port.tty->index;
+	tmp.flags = port->port.flags;
+	tmp.xmit_fifo_size = FWTTY_PORT_TXFIFO_LEN;
+	tmp.baud_base = 400000000;
+	tmp.close_delay = port->port.close_delay;
+
+	return (copy_to_user(info, &tmp, sizeof(*info))) ? -EFAULT : 0;
+}
+
+static int set_serial_info(struct fwtty_port *port,
+			   struct serial_struct __user *info)
+{
+	struct serial_struct tmp;
+
+	if (copy_from_user(&tmp, info, sizeof(tmp)))
+		return -EFAULT;
+
+	if (tmp.irq != 0 || tmp.port != 0 || tmp.custom_divisor != 0 ||
+			tmp.baud_base != 400000000)
+		return -EPERM;
+
+	if (!capable(CAP_SYS_ADMIN)) {
+		if (((tmp.flags & ~ASYNC_USR_MASK) !=
+		     (port->port.flags & ~ASYNC_USR_MASK)))
+			return -EPERM;
+	} else
+		port->port.close_delay = tmp.close_delay * HZ / 100;
+
+	return 0;
+}
+
+static int fwtty_ioctl(struct tty_struct *tty, unsigned cmd,
+		       unsigned long arg)
+{
+	struct fwtty_port *port = tty->driver_data;
+	int err;
+
+	switch (cmd) {
+	case TIOCGSERIAL:
+		mutex_lock(&port->port.mutex);
+		err = get_serial_info(port, (void __user *)arg);
+		mutex_unlock(&port->port.mutex);
+		break;
+
+	case TIOCSSERIAL:
+		mutex_lock(&port->port.mutex);
+		err = set_serial_info(port, (void __user *)arg);
+		mutex_unlock(&port->port.mutex);
+		break;
+
+	case TIOCMIWAIT:
+		err = wait_msr_change(port, arg);
+		break;
+
+	default:
+		err = -ENOIOCTLCMD;
+	}
+
+	return err;
+}
+
+static void fwtty_set_termios(struct tty_struct *tty, struct ktermios *old)
+{
+	struct fwtty_port *port = tty->driver_data;
+	unsigned baud;
+
+	spin_lock_bh(&port->lock);
+	baud = set_termios(port, tty);
+
+	if ((baud == 0) && (old->c_cflag & CBAUD))
+		port->mctrl &= ~(TIOCM_DTR | TIOCM_RTS);
+	else if ((baud != 0) && !(old->c_cflag & CBAUD)) {
+		if (C_CRTSCTS(tty) || !test_bit(TTY_THROTTLED, &tty->flags))
+			port->mctrl |= TIOCM_DTR | TIOCM_RTS;
+		else
+			port->mctrl |= TIOCM_DTR;
+	}
+	__fwtty_write_port_status(port);
+	spin_unlock_bh(&port->lock);
+
+	if (old->c_cflag & CRTSCTS) {
+		if (!C_CRTSCTS(tty)) {
+			tty->hw_stopped = 0;
+			fwtty_restart_tx(port);
+		}
+	} else if (C_CRTSCTS(tty) && ~port->mstatus & TIOCM_CTS) {
+		tty->hw_stopped = 1;
+	}
+}
+
+/**
+ * fwtty_break_ctl - start/stop sending breaks
+ *
+ * Signals the remote to start or stop generating simulated breaks.
+ * First, stop dequeueing from the fifo and wait for writer/drain to leave tx
+ * before signalling the break line status. This guarantees any pending rx will
+ * be queued to the line discipline before break is simulated on the remote.
+ * Conversely, turning off break_ctl requires signalling the line status change,
+ * then enabling tx.
+ */
+static int fwtty_break_ctl(struct tty_struct *tty, int state)
+{
+	struct fwtty_port *port = tty->driver_data;
+	long ret;
+
+	fwtty_dbg(port, "%d", state);
+
+	if (state == -1) {
+		set_bit(STOP_TX, &port->flags);
+		ret = wait_event_interruptible_timeout(port->wait_tx,
+					       !test_bit(IN_TX, &port->flags),
+					       10);
+		if (ret == 0 || ret == -ERESTARTSYS) {
+			clear_bit(STOP_TX, &port->flags);
+			fwtty_restart_tx(port);
+			return -EINTR;
+		}
+	}
+
+	spin_lock_bh(&port->lock);
+	port->break_ctl = (state == -1);
+	__fwtty_write_port_status(port);
+	spin_unlock_bh(&port->lock);
+
+	if (state == 0) {
+		spin_lock_bh(&port->lock);
+		dma_fifo_reset(&port->tx_fifo);
+		clear_bit(STOP_TX, &port->flags);
+		spin_unlock_bh(&port->lock);
+	}
+	return 0;
+}
+
+static int fwtty_tiocmget(struct tty_struct *tty)
+{
+	struct fwtty_port *port = tty->driver_data;
+	unsigned tiocm;
+
+	spin_lock_bh(&port->lock);
+	tiocm = (port->mctrl & MCTRL_MASK) | (port->mstatus & ~MCTRL_MASK);
+	spin_unlock_bh(&port->lock);
+
+	fwtty_dbg(port, "%x", tiocm);
+
+	return tiocm;
+}
+
+static int fwtty_tiocmset(struct tty_struct *tty, unsigned set, unsigned clear)
+{
+	struct fwtty_port *port = tty->driver_data;
+
+	fwtty_dbg(port, "set: %x clear: %x", set, clear);
+
+	/* TODO: simulate loopback if TIOCM_LOOP set */
+
+	spin_lock_bh(&port->lock);
+	port->mctrl &= ~(clear & MCTRL_MASK & 0xffff);
+	port->mctrl |= set & MCTRL_MASK & 0xffff;
+	__fwtty_write_port_status(port);
+	spin_unlock_bh(&port->lock);
+	return 0;
+}
+
+static int fwtty_get_icount(struct tty_struct *tty,
+			    struct serial_icounter_struct *icount)
+{
+	struct fwtty_port *port = tty->driver_data;
+	struct stats stats;
+
+	memcpy(&stats, &port->stats, sizeof(stats));
+	if (port->port.console)
+		(*port->fwcon_ops->stats)(&stats, port->con_data);
+
+	icount->cts = port->icount.cts;
+	icount->dsr = port->icount.dsr;
+	icount->rng = port->icount.rng;
+	icount->dcd = port->icount.dcd;
+	icount->rx  = port->icount.rx;
+	icount->tx  = port->icount.tx + stats.xchars;
+	icount->frame   = port->icount.frame;
+	icount->overrun = port->icount.overrun;
+	icount->parity  = port->icount.parity;
+	icount->brk     = port->icount.brk;
+	icount->buf_overrun = port->icount.overrun;
+	return 0;
+}
+
+static void fwtty_proc_show_port(struct seq_file *m, struct fwtty_port *port)
+{
+	struct stats stats;
+
+	memcpy(&stats, &port->stats, sizeof(stats));
+	if (port->port.console)
+		(*port->fwcon_ops->stats)(&stats, port->con_data);
+
+	seq_printf(m, " tx:%d rx:%d", port->icount.tx + stats.xchars,
+		   port->icount.rx);
+	seq_printf(m, " cts:%d dsr:%d rng:%d dcd:%d", port->icount.cts,
+		   port->icount.dsr, port->icount.rng, port->icount.dcd);
+	seq_printf(m, " fe:%d oe:%d pe:%d brk:%d", port->icount.frame,
+		   port->icount.overrun, port->icount.parity, port->icount.brk);
+	seq_printf(m, " dr:%d st:%d err:%d lost:%d", stats.dropped,
+		   stats.tx_stall, stats.fifo_errs, stats.lost);
+	seq_printf(m, " pkts:%d thr:%d wtrmk:%d", stats.sent, stats.throttled,
+		   stats.watermark);
+	seq_printf(m, " addr:%012llx", port->rx_handler.offset);
+
+	if (port->port.console) {
+		seq_printf(m, "\n    ");
+		(*port->fwcon_ops->proc_show)(m, port->con_data);
+	}
+
+	dump_profile(m, &port->stats);
+}
+
+static void fwtty_proc_show_peer(struct seq_file *m, struct fwtty_peer *peer)
+{
+	int generation = peer->generation;
+
+	smp_rmb();
+	seq_printf(m, " %s:", dev_name(&peer->unit->device));
+	seq_printf(m, " node:%04x gen:%d", peer->node_id, generation);
+	seq_printf(m, " sp:%d max:%d guid:%016llx", peer->speed,
+		   peer->max_payload, (unsigned long long) peer->guid);
+
+	if (capable(CAP_SYS_ADMIN)) {
+		seq_printf(m, " mgmt:%012llx",
+			   (unsigned long long) peer->mgmt_addr);
+		seq_printf(m, " addr:%012llx",
+			   (unsigned long long) peer->status_addr);
+	}
+	seq_putc(m, '\n');
+}
+
+static int fwtty_proc_show(struct seq_file *m, void *v)
+{
+	struct fwtty_port *port;
+	struct fw_serial *serial;
+	struct fwtty_peer *peer;
+	int i;
+
+	seq_puts(m, "fwserinfo: 1.0 driver: 1.0\n");
+	for (i = 0; i < MAX_TOTAL_PORTS && (port = fwtty_port_get(i)); ++i) {
+		seq_printf(m, "%2d:", i);
+		if (capable(CAP_SYS_ADMIN))
+			fwtty_proc_show_port(m, port);
+		fwtty_port_put(port);
+		seq_printf(m, "\n");
+	}
+	seq_putc(m, '\n');
+
+	rcu_read_lock();
+	list_for_each_entry_rcu(serial, &fwserial_list, list) {
+		seq_printf(m, "card: %s  guid: %016llx\n",
+			   dev_name(serial->card->device),
+			   (unsigned long long) serial->card->guid);
+		list_for_each_entry_rcu(peer, &serial->peer_list, list)
+			fwtty_proc_show_peer(m, peer);
+	}
+	rcu_read_unlock();
+	return 0;
+}
+
+static int fwtty_proc_open(struct inode *inode, struct file *fp)
+{
+	return single_open(fp, fwtty_proc_show, NULL);
+}
+
+static const struct file_operations fwtty_proc_fops = {
+	.owner =        THIS_MODULE,
+	.open =         fwtty_proc_open,
+	.read =         seq_read,
+	.llseek =       seq_lseek,
+	.release =      single_release,
+};
+
+static const struct tty_port_operations fwtty_port_ops = {
+	.dtr_rts =		fwtty_port_dtr_rts,
+	.carrier_raised =	fwtty_port_carrier_raised,
+	.shutdown =		fwtty_port_shutdown,
+	.activate =		fwtty_port_activate,
+};
+
+static const struct tty_operations fwtty_ops = {
+	.open =			fwtty_open,
+	.close =		fwtty_close,
+	.hangup =		fwtty_hangup,
+	.cleanup =		fwtty_cleanup,
+	.install =		fwtty_install,
+	.write =		fwtty_write,
+	.write_room =		fwtty_write_room,
+	.chars_in_buffer =	fwtty_chars_in_buffer,
+	.send_xchar =           fwtty_send_xchar,
+	.throttle =             fwtty_throttle,
+	.unthrottle =           fwtty_unthrottle,
+	.ioctl =		fwtty_ioctl,
+	.set_termios =		fwtty_set_termios,
+	.break_ctl =		fwtty_break_ctl,
+	.tiocmget =		fwtty_tiocmget,
+	.tiocmset =		fwtty_tiocmset,
+	.get_icount =		fwtty_get_icount,
+	.proc_fops =		&fwtty_proc_fops,
+};
+
+static inline int mgmt_pkt_expected_len(__be16 code)
+{
+	static const struct fwserial_mgmt_pkt pkt;
+
+	switch (be16_to_cpu(code)) {
+	case FWSC_VIRT_CABLE_PLUG:
+		return sizeof(pkt.hdr) + sizeof(pkt.plug_req);
+
+	case FWSC_VIRT_CABLE_PLUG_RSP:  /* | FWSC_RSP_OK */
+		return sizeof(pkt.hdr) + sizeof(pkt.plug_rsp);
+
+
+	case FWSC_VIRT_CABLE_UNPLUG:
+	case FWSC_VIRT_CABLE_UNPLUG_RSP:
+	case FWSC_VIRT_CABLE_PLUG_RSP | FWSC_RSP_NACK:
+	case FWSC_VIRT_CABLE_UNPLUG_RSP | FWSC_RSP_NACK:
+		return sizeof(pkt.hdr);
+
+	default:
+		return -1;
+	}
+}
+
+static inline void fill_plug_params(struct virt_plug_params *params,
+				    struct fwtty_port *port)
+{
+	u64 status_addr = port->rx_handler.offset;
+	u64 fifo_addr = port->rx_handler.offset + 4;
+	size_t fifo_len = port->rx_handler.length - 4;
+
+	params->status_hi = cpu_to_be32(status_addr >> 32);
+	params->status_lo = cpu_to_be32(status_addr);
+	params->fifo_hi = cpu_to_be32(fifo_addr >> 32);
+	params->fifo_lo = cpu_to_be32(fifo_addr);
+	params->fifo_len = cpu_to_be32(fifo_len);
+}
+
+static inline void fill_plug_req(struct fwserial_mgmt_pkt *pkt,
+				 struct fwtty_port *port)
+{
+	pkt->hdr.code = cpu_to_be16(FWSC_VIRT_CABLE_PLUG);
+	pkt->hdr.len = cpu_to_be16(mgmt_pkt_expected_len(pkt->hdr.code));
+	fill_plug_params(&pkt->plug_req, port);
+}
+
+static inline void fill_plug_rsp_ok(struct fwserial_mgmt_pkt *pkt,
+				    struct fwtty_port *port)
+{
+	pkt->hdr.code = cpu_to_be16(FWSC_VIRT_CABLE_PLUG_RSP);
+	pkt->hdr.len = cpu_to_be16(mgmt_pkt_expected_len(pkt->hdr.code));
+	fill_plug_params(&pkt->plug_rsp, port);
+}
+
+static inline void fill_plug_rsp_nack(struct fwserial_mgmt_pkt *pkt)
+{
+	pkt->hdr.code = cpu_to_be16(FWSC_VIRT_CABLE_PLUG_RSP | FWSC_RSP_NACK);
+	pkt->hdr.len = cpu_to_be16(mgmt_pkt_expected_len(pkt->hdr.code));
+}
+
+static inline void fill_unplug_req(struct fwserial_mgmt_pkt *pkt)
+{
+	pkt->hdr.code = cpu_to_be16(FWSC_VIRT_CABLE_UNPLUG);
+	pkt->hdr.len = cpu_to_be16(mgmt_pkt_expected_len(pkt->hdr.code));
+}
+
+static inline void fill_unplug_rsp_nack(struct fwserial_mgmt_pkt *pkt)
+{
+	pkt->hdr.code = cpu_to_be16(FWSC_VIRT_CABLE_UNPLUG_RSP | FWSC_RSP_NACK);
+	pkt->hdr.len = cpu_to_be16(mgmt_pkt_expected_len(pkt->hdr.code));
+}
+
+static inline void fill_unplug_rsp_ok(struct fwserial_mgmt_pkt *pkt)
+{
+	pkt->hdr.code = cpu_to_be16(FWSC_VIRT_CABLE_UNPLUG_RSP);
+	pkt->hdr.len = cpu_to_be16(mgmt_pkt_expected_len(pkt->hdr.code));
+}
+
+static void fwserial_virt_plug_complete(struct fwtty_peer *peer,
+					struct virt_plug_params *params)
+{
+	struct fwtty_port *port = peer->port;
+
+	peer->status_addr = be32_to_u64(params->status_hi, params->status_lo);
+	peer->fifo_addr = be32_to_u64(params->fifo_hi, params->fifo_lo);
+	peer->fifo_len = be32_to_cpu(params->fifo_len);
+	peer_set_state(peer, FWPS_ATTACHED);
+
+	/* reconfigure tx_fifo optimally for this peer */
+	spin_lock_bh(&port->lock);
+	port->max_payload = min3(peer->max_payload, peer->fifo_len,
+				 MAX_ASYNC_PAYLOAD);
+	dma_fifo_change_tx_limit(&port->tx_fifo, port->max_payload);
+	spin_unlock_bh(&peer->port->lock);
+
+	if (port->port.console && port->fwcon_ops->notify != NULL)
+		(*port->fwcon_ops->notify)(FWCON_NOTIFY_ATTACH, port->con_data);
+
+	fwtty_info(&peer->unit, "peer (guid:%016llx) connected on %s",
+		   (unsigned long long)peer->guid, dev_name(port->device));
+}
+
+static inline int fwserial_send_mgmt_sync(struct fwtty_peer *peer,
+					  struct fwserial_mgmt_pkt *pkt)
+{
+	int generation;
+	int rcode, tries = 5;
+
+	do {
+		generation = peer->generation;
+		smp_rmb();
+
+		rcode = fw_run_transaction(peer->serial->card,
+					   TCODE_WRITE_BLOCK_REQUEST,
+					   peer->node_id,
+					   generation, peer->speed,
+					   peer->mgmt_addr,
+					   pkt, be16_to_cpu(pkt->hdr.len));
+		if (rcode == RCODE_BUSY || rcode == RCODE_SEND_ERROR ||
+		    rcode == RCODE_GENERATION) {
+			fwtty_dbg(&peer->unit, "mgmt write error: %d", rcode);
+			continue;
+		} else
+			break;
+	} while (--tries > 0);
+	return rcode;
+}
+
+/**
+ * fwserial_claim_port - attempt to claim port @ index for peer
+ *
+ * Returns ptr to claimed port or error code (as ERR_PTR())
+ * Can sleep - must be called from process context
+ */
+static struct fwtty_port *fwserial_claim_port(struct fwtty_peer *peer,
+					      int index)
+{
+	struct fwtty_port *port;
+
+	if (index < 0 || index >= num_ports)
+		return ERR_PTR(-EINVAL);
+
+	/* must guarantee that previous port releases have completed */
+	synchronize_rcu();
+
+	port = peer->serial->ports[index];
+	spin_lock_bh(&port->lock);
+	if (!rcu_access_pointer(port->peer))
+		rcu_assign_pointer(port->peer, peer);
+	else
+		port = ERR_PTR(-EBUSY);
+	spin_unlock_bh(&port->lock);
+
+	return port;
+}
+
+/**
+ * fwserial_find_port - find avail port and claim for peer
+ *
+ * Returns ptr to claimed port or NULL if none avail
+ * Can sleep - must be called from process context
+ */
+static struct fwtty_port *fwserial_find_port(struct fwtty_peer *peer)
+{
+	struct fwtty_port **ports = peer->serial->ports;
+	int i;
+
+	/* must guarantee that previous port releases have completed */
+	synchronize_rcu();
+
+	/* TODO: implement optional GUID-to-specific port # matching */
+
+	/* find an unattached port (but not the loopback port, if present) */
+	for (i = 0; i < num_ttys; ++i) {
+		spin_lock_bh(&ports[i]->lock);
+		if (!ports[i]->peer) {
+			/* claim port */
+			rcu_assign_pointer(ports[i]->peer, peer);
+			spin_unlock_bh(&ports[i]->lock);
+			return ports[i];
+		}
+		spin_unlock_bh(&ports[i]->lock);
+	}
+	return NULL;
+}
+
+static void fwserial_release_port(struct fwtty_port *port)
+{
+	/* drop carrier (and all other line status) */
+	fwtty_update_port_status(port, 0);
+
+	spin_lock_bh(&port->lock);
+
+	/* reset dma fifo max transmission size back to S100 */
+	port->max_payload = link_speed_to_max_payload(SCODE_100);
+	dma_fifo_change_tx_limit(&port->tx_fifo, port->max_payload);
+
+	rcu_assign_pointer(port->peer, NULL);
+	spin_unlock_bh(&port->lock);
+
+	if (port->port.console && port->fwcon_ops->notify != NULL)
+		(*port->fwcon_ops->notify)(FWCON_NOTIFY_DETACH, port->con_data);
+}
+
+static void fwserial_plug_timeout(unsigned long data)
+{
+	struct fwtty_peer *peer = (struct fwtty_peer *) data;
+	struct fwtty_port *port;
+
+	spin_lock_bh(&peer->lock);
+	if (peer->state != FWPS_PLUG_PENDING) {
+		spin_unlock_bh(&peer->lock);
+		return;
+	}
+
+	port = peer_revert_state(peer);
+	spin_unlock_bh(&peer->lock);
+
+	if (port)
+		fwserial_release_port(port);
+}
+
+/**
+ * fwserial_connect_peer - initiate virtual cable with peer
+ *
+ * Returns 0 if VIRT_CABLE_PLUG request was successfully sent,
+ * otherwise error code.  Must be called from process context.
+ */
+static int fwserial_connect_peer(struct fwtty_peer *peer)
+{
+	struct fwtty_port *port;
+	struct fwserial_mgmt_pkt *pkt;
+	int err, rcode;
+
+	pkt = kmalloc(sizeof(*pkt), GFP_KERNEL);
+	if (!pkt)
+		return -ENOMEM;
+
+	port = fwserial_find_port(peer);
+	if (!port) {
+		fwtty_err(&peer->unit, "avail ports in use");
+		err = -EBUSY;
+		goto free_pkt;
+	}
+
+	spin_lock_bh(&peer->lock);
+
+	/* only initiate VIRT_CABLE_PLUG if peer is currently not attached */
+	if (peer->state != FWPS_NOT_ATTACHED) {
+		err = -EBUSY;
+		goto release_port;
+	}
+
+	peer->port = port;
+	peer_set_state(peer, FWPS_PLUG_PENDING);
+
+	fill_plug_req(pkt, peer->port);
+
+	setup_timer(&peer->timer, fwserial_plug_timeout, (unsigned long)peer);
+	mod_timer(&peer->timer, jiffies + VIRT_CABLE_PLUG_TIMEOUT);
+	spin_unlock_bh(&peer->lock);
+
+	rcode = fwserial_send_mgmt_sync(peer, pkt);
+
+	spin_lock_bh(&peer->lock);
+	if (peer->state == FWPS_PLUG_PENDING && rcode != RCODE_COMPLETE) {
+		if (rcode == RCODE_CONFLICT_ERROR)
+			err = -EAGAIN;
+		else
+			err = -EIO;
+		goto cancel_timer;
+	}
+	spin_unlock_bh(&peer->lock);
+
+	kfree(pkt);
+	return 0;
+
+cancel_timer:
+	del_timer(&peer->timer);
+	peer_revert_state(peer);
+release_port:
+	spin_unlock_bh(&peer->lock);
+	fwserial_release_port(port);
+free_pkt:
+	kfree(pkt);
+	return err;
+}
+
+/**
+ * fwserial_close_port -
+ * HUP the tty (if the tty exists) and unregister the tty device.
+ * Only used by the unit driver upon unit removal to disconnect and
+ * cleanup all attached ports
+ *
+ * The port reference is put by fwtty_cleanup (if a reference was
+ * ever taken).
+ */
+static void fwserial_close_port(struct fwtty_port *port)
+{
+	struct tty_struct *tty;
+
+	mutex_lock(&port->port.mutex);
+	tty = tty_port_tty_get(&port->port);
+	if (tty) {
+		tty_vhangup(tty);
+		tty_kref_put(tty);
+	}
+	mutex_unlock(&port->port.mutex);
+
+	tty_unregister_device(fwtty_driver, port->index);
+}
+
+/**
+ * fwserial_lookup - finds first fw_serial associated with card
+ * @card: fw_card to match
+ *
+ * NB: caller must be holding fwserial_list_mutex
+ */
+static struct fw_serial *fwserial_lookup(struct fw_card *card)
+{
+	struct fw_serial *serial;
+
+	list_for_each_entry(serial, &fwserial_list, list) {
+		if (card == serial->card)
+			return serial;
+	}
+
+	return NULL;
+}
+
+/**
+ * __fwserial_lookup_rcu - finds first fw_serial associated with card
+ * @card: fw_card to match
+ *
+ * NB: caller must be inside rcu_read_lock() section
+ */
+static struct fw_serial *__fwserial_lookup_rcu(struct fw_card *card)
+{
+	struct fw_serial *serial;
+
+	list_for_each_entry_rcu(serial, &fwserial_list, list) {
+		if (card == serial->card)
+			return serial;
+	}
+
+	return NULL;
+}
+
+/**
+ * __fwserial_peer_by_node_id - finds a peer matching the given generation + id
+ *
+ * If a matching peer could not be found for the specified generation/node id,
+ * this could be because:
+ * a) the generation has changed and one of the nodes hasn't updated yet
+ * b) the remote node has created its remote unit device before this
+ *    local node has created its corresponding remote unit device
+ * In either case, the remote node should retry
+ *
+ * Note: caller must be in rcu_read_lock() section
+ */
+static struct fwtty_peer *__fwserial_peer_by_node_id(struct fw_card *card,
+						     int generation, int id)
+{
+	struct fw_serial *serial;
+	struct fwtty_peer *peer;
+
+	serial = __fwserial_lookup_rcu(card);
+	if (!serial) {
+		/*
+		 * Something is very wrong - there should be a matching
+		 * fw_serial structure for every fw_card. Maybe the remote node
+		 * has created its remote unit device before this driver has
+		 * been probed for any unit devices...
+		 */
+		fwtty_err(card, "unknown card (guid %016llx)",
+			  (unsigned long long) card->guid);
+		return NULL;
+	}
+
+	list_for_each_entry_rcu(peer, &serial->peer_list, list) {
+		int g = peer->generation;
+		smp_rmb();
+		if (generation == g && id == peer->node_id)
+			return peer;
+	}
+
+	return NULL;
+}
+
+#ifdef DEBUG
+static void __dump_peer_list(struct fw_card *card)
+{
+	struct fw_serial *serial;
+	struct fwtty_peer *peer;
+
+	serial = __fwserial_lookup_rcu(card);
+	if (!serial)
+		return;
+
+	list_for_each_entry_rcu(peer, &serial->peer_list, list) {
+		int g = peer->generation;
+		smp_rmb();
+		fwtty_dbg(card, "peer(%d:%x) guid: %016llx\n", g,
+			  peer->node_id, (unsigned long long) peer->guid);
+	}
+}
+#else
+#define __dump_peer_list(s)
+#endif
+
+static void fwserial_auto_connect(struct work_struct *work)
+{
+	struct fwtty_peer *peer = to_peer(to_delayed_work(work), connect);
+	int err;
+
+	err = fwserial_connect_peer(peer);
+	if (err == -EAGAIN && ++peer->connect_retries < MAX_CONNECT_RETRIES)
+		schedule_delayed_work(&peer->connect, CONNECT_RETRY_DELAY);
+}
+
+/**
+ * fwserial_add_peer - add a newly probed 'serial' unit device as a 'peer'
+ * @serial: aggregate representing the specific fw_card to add the peer to
+ * @unit: 'peer' to create and add to peer_list of serial
+ *
+ * Adds a 'peer' (ie, a local or remote 'serial' unit device) to the list of
+ * peers for a specific fw_card. Optionally, auto-attach this peer to an
+ * available tty port. This function is called either directly or indirectly
+ * as a result of a 'serial' unit device being created & probed.
+ *
+ * Note: this function is serialized with fwserial_remove_peer() by the
+ * fwserial_list_mutex held in fwserial_probe().
+ *
+ * A 1:1 correspondence between an fw_unit and an fwtty_peer is maintained
+ * via the dev_set_drvdata() for the device of the fw_unit.
+ */
+static int fwserial_add_peer(struct fw_serial *serial, struct fw_unit *unit)
+{
+	struct device *dev = &unit->device;
+	struct fw_device  *parent = fw_parent_device(unit);
+	struct fwtty_peer *peer;
+	struct fw_csr_iterator ci;
+	int key, val;
+	int generation;
+
+	peer = kzalloc(sizeof(*peer), GFP_KERNEL);
+	if (!peer)
+		return -ENOMEM;
+
+	peer_set_state(peer, FWPS_NOT_ATTACHED);
+
+	dev_set_drvdata(dev, peer);
+	peer->unit = unit;
+	peer->guid = (u64)parent->config_rom[3] << 32 | parent->config_rom[4];
+	peer->speed = parent->max_speed;
+	peer->max_payload = min(device_max_receive(parent),
+				link_speed_to_max_payload(peer->speed));
+
+	generation = parent->generation;
+	smp_rmb();
+	peer->node_id = parent->node_id;
+	smp_wmb();
+	peer->generation = generation;
+
+	/* retrieve the mgmt bus addr from the unit directory */
+	fw_csr_iterator_init(&ci, unit->directory);
+	while (fw_csr_iterator_next(&ci, &key, &val)) {
+		if (key == (CSR_OFFSET | CSR_DEPENDENT_INFO)) {
+			peer->mgmt_addr = CSR_REGISTER_BASE + 4 * val;
+			break;
+		}
+	}
+	if (peer->mgmt_addr == 0ULL) {
+		/*
+		 * No mgmt address effectively disables VIRT_CABLE_PLUG -
+		 * this peer will not be able to attach to a remote
+		 */
+		peer_set_state(peer, FWPS_NO_MGMT_ADDR);
+	}
+
+	spin_lock_init(&peer->lock);
+	peer->port = NULL;
+
+	init_timer(&peer->timer);
+	INIT_WORK(&peer->work, NULL);
+	INIT_DELAYED_WORK(&peer->connect, fwserial_auto_connect);
+
+	/* associate peer with specific fw_card */
+	peer->serial = serial;
+	list_add_rcu(&peer->list, &serial->peer_list);
+
+	fwtty_info(&peer->unit, "peer added (guid:%016llx)",
+		   (unsigned long long)peer->guid);
+
+	/* identify the local unit & virt cable to loopback port */
+	if (parent->is_local) {
+		serial->self = peer;
+		if (create_loop_dev) {
+			struct fwtty_port *port;
+			port = fwserial_claim_port(peer, num_ttys);
+			if (!IS_ERR(port)) {
+				struct virt_plug_params params;
+
+				spin_lock_bh(&peer->lock);
+				peer->port = port;
+				fill_plug_params(&params, port);
+				fwserial_virt_plug_complete(peer, &params);
+				spin_unlock_bh(&peer->lock);
+
+				fwtty_write_port_status(port);
+			}
+		}
+
+	} else if (auto_connect) {
+		/* auto-attach to remote units only (if policy allows) */
+		schedule_delayed_work(&peer->connect, 1);
+	}
+
+	return 0;
+}
+
+/**
+ * fwserial_remove_peer - remove a 'serial' unit device as a 'peer'
+ *
+ * Remove a 'peer' from its list of peers. This function is only
+ * called by fwserial_remove() on bus removal of the unit device.
+ *
+ * Note: this function is serialized with fwserial_add_peer() by the
+ * fwserial_list_mutex held in fwserial_remove().
+ */
+static void fwserial_remove_peer(struct fwtty_peer *peer)
+{
+	struct fwtty_port *port;
+
+	spin_lock_bh(&peer->lock);
+	peer_set_state(peer, FWPS_GONE);
+	spin_unlock_bh(&peer->lock);
+
+	cancel_delayed_work_sync(&peer->connect);
+	cancel_work_sync(&peer->work);
+
+	spin_lock_bh(&peer->lock);
+	/* if this unit is the local unit, clear link */
+	if (peer == peer->serial->self)
+		peer->serial->self = NULL;
+
+	/* cancel the request timeout timer (if running) */
+	del_timer(&peer->timer);
+
+	port = peer->port;
+	peer->port = NULL;
+
+	list_del_rcu(&peer->list);
+
+	fwtty_info(&peer->unit, "peer removed (guid:%016llx)",
+		   (unsigned long long)peer->guid);
+
+	spin_unlock_bh(&peer->lock);
+
+	if (port)
+		fwserial_release_port(port);
+
+	synchronize_rcu();
+	kfree(peer);
+}
+
+/**
+ * create_loop_device - create a loopback tty device
+ * @tty_driver: tty_driver to own loopback device
+ * @prototype: ptr to already-assigned 'prototype' tty port
+ * @index: index to associate this device with the tty port
+ * @parent: device to child to
+ *
+ * HACK - this is basically tty_port_register_device() with an
+ * alternate naming scheme. Suggest tty_port_register_named_device()
+ * helper api.
+ *
+ * Creates a loopback tty device named 'fwloop<n>' which is attached to
+ * the local unit in fwserial_add_peer(). Note that <n> in the device
+ * name advances in increments of port allocation blocks, ie., for port
+ * indices 0..3, the device name will be 'fwloop0'; for 4..7, 'fwloop1',
+ * and so on.
+ *
+ * Only one loopback device should be created per fw_card.
+ */
+static void release_loop_device(struct device *dev)
+{
+	kfree(dev);
+}
+
+static struct device *create_loop_device(struct tty_driver *driver,
+					 struct fwtty_port *prototype,
+					 struct fwtty_port *port,
+					 struct device *parent)
+{
+	char name[64];
+	int index = port->index;
+	dev_t devt = MKDEV(driver->major, driver->minor_start) + index;
+	struct device *dev = NULL;
+	int err;
+
+	if (index >= fwtty_driver->num)
+		return ERR_PTR(-EINVAL);
+
+	snprintf(name, 64, "%s%d", loop_dev_name, index / num_ports);
+
+	tty_port_link_device(&port->port, driver, index);
+
+	cdev_init(&driver->cdevs[index], driver->cdevs[prototype->index].ops);
+	driver->cdevs[index].owner = driver->owner;
+	err = cdev_add(&driver->cdevs[index], devt, 1);
+	if (err)
+		return ERR_PTR(err);
+
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev) {
+		cdev_del(&driver->cdevs[index]);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	dev->devt = devt;
+	dev->class = prototype->device->class;
+	dev->parent = parent;
+	dev->release = release_loop_device;
+	dev_set_name(dev, "%s", name);
+	dev->groups = NULL;
+	dev_set_drvdata(dev, NULL);
+
+	err = device_register(dev);
+	if (err) {
+		put_device(dev);
+		cdev_del(&driver->cdevs[index]);
+		return ERR_PTR(err);
+	}
+
+	return dev;
+}
+
+/**
+ * fwserial_create - init everything to create TTYs for a specific fw_card
+ * @unit: fw_unit for first 'serial' unit device probed for this fw_card
+ *
+ * This function inits the aggregate structure (an fw_serial instance)
+ * used to manage the TTY ports registered by a specific fw_card. Also, the
+ * unit device is added as the first 'peer'.
+ *
+ * This unit device may represent a local unit device (as specified by the
+ * config ROM unit directory) or it may represent a remote unit device
+ * (as specified by the reading of the remote node's config ROM).
+ *
+ * Returns 0 to indicate "ownership" of the unit device, or a negative errno
+ * value to indicate which error.
+ */
+static int fwserial_create(struct fw_unit *unit)
+{
+	struct fw_device *parent = fw_parent_device(unit);
+	struct fw_card *card = parent->card;
+	struct fw_serial *serial;
+	struct fwtty_port *port;
+	struct device *tty_dev;
+	int i, j;
+	int err;
+
+	serial = kzalloc(sizeof(*serial), GFP_KERNEL);
+	if (!serial)
+		return -ENOMEM;
+
+	kref_init(&serial->kref);
+	serial->card = card;
+	INIT_LIST_HEAD(&serial->peer_list);
+
+	for (i = 0; i < num_ports; ++i) {
+		port = kzalloc(sizeof(*port), GFP_KERNEL);
+		if (!port) {
+			err = -ENOMEM;
+			goto free_ports;
+		}
+		tty_port_init(&port->port);
+		port->index = FWTTY_INVALID_INDEX;
+		port->port.ops = &fwtty_port_ops;
+		port->serial = serial;
+
+		spin_lock_init(&port->lock);
+		INIT_DELAYED_WORK(&port->drain, fwtty_drain_tx);
+		INIT_DELAYED_WORK(&port->emit_breaks, fwtty_emit_breaks);
+		INIT_WORK(&port->hangup, fwtty_do_hangup);
+		INIT_WORK(&port->push, fwtty_pushrx);
+		INIT_LIST_HEAD(&port->buf_list);
+		init_waitqueue_head(&port->wait_tx);
+		port->max_payload = link_speed_to_max_payload(SCODE_100);
+		dma_fifo_init(&port->tx_fifo);
+
+		rcu_assign_pointer(port->peer, NULL);
+		serial->ports[i] = port;
+
+		/* get unique bus addr region for port's status & recv fifo */
+		port->rx_handler.length = FWTTY_PORT_RXFIFO_LEN + 4;
+		port->rx_handler.address_callback = fwtty_port_handler;
+		port->rx_handler.callback_data = port;
+		/*
+		 * XXX: use custom memory region above cpu physical memory addrs
+		 * this will ease porting to 64-bit firewire adapters
+		 */
+		err = fw_core_add_address_handler(&port->rx_handler,
+						  &fw_high_memory_region);
+		if (err) {
+			kfree(port);
+			goto free_ports;
+		}
+	}
+	/* preserve i for error cleanup */
+
+	err = fwtty_ports_add(serial);
+	if (err) {
+		fwtty_err(&unit, "no space in port table");
+		goto free_ports;
+	}
+
+	for (j = 0; j < num_ttys; ++j) {
+		tty_dev = tty_port_register_device(&serial->ports[j]->port,
+						   fwtty_driver,
+						   serial->ports[j]->index,
+						   card->device);
+		if (IS_ERR(tty_dev)) {
+			err = PTR_ERR(tty_dev);
+			fwtty_err(&unit, "register tty device error (%d)", err);
+			goto unregister_ttys;
+		}
+
+		serial->ports[j]->device = tty_dev;
+	}
+	/* preserve j for error cleanup */
+
+	if (create_loop_dev) {
+		struct device *loop_dev;
+
+		loop_dev = create_loop_device(fwtty_driver,
+					      serial->ports[0],
+					      serial->ports[num_ttys],
+					      card->device);
+		if (IS_ERR(loop_dev)) {
+			err = PTR_ERR(loop_dev);
+			fwtty_err(&unit, "create loop device failed (%d)", err);
+			goto unregister_ttys;
+		}
+		serial->ports[num_ttys]->device = loop_dev;
+		serial->ports[num_ttys]->loopback = true;
+	}
+
+	list_add_rcu(&serial->list, &fwserial_list);
+
+	fwtty_notice(&unit, "TTY over FireWire on device %s (guid %016llx)",
+		     dev_name(card->device), (unsigned long long) card->guid);
+
+	err = fwserial_add_peer(serial, unit);
+	if (!err)
+		return 0;
+
+	fwtty_err(&unit, "unable to add peer unit device (%d)", err);
+
+	/* fall-through to error processing */
+	list_del_rcu(&serial->list);
+unregister_ttys:
+	for (--j; j >= 0; --j)
+		tty_unregister_device(fwtty_driver, serial->ports[j]->index);
+	kref_put(&serial->kref, fwserial_destroy);
+	return err;
+
+free_ports:
+	for (--i; i >= 0; --i)
+		kfree(serial->ports[i]);
+	kfree(serial);
+	return err;
+}
+
+/**
+ * fwserial_probe: bus probe function for firewire 'serial' unit devices
+ *
+ * A 'serial' unit device is created and probed as a result of:
+ * - declaring a ieee1394 bus id table for 'devices' matching a fabricated
+ *   'serial' unit specifier id
+ * - adding a unit directory to the config ROM(s) for a 'serial' unit
+ *
+ * The firewire core registers unit devices by enumerating unit directories
+ * of a node's config ROM after reading the config ROM when a new node is
+ * added to the bus topology after a bus reset.
+ *
+ * The practical implications of this are:
+ * - this probe is called for both local and remote nodes that have a 'serial'
+ *   unit directory in their config ROM (that matches the specifiers in
+ *   fwserial_id_table).
+ * - no specific order is enforced for local vs. remote unit devices
+ *
+ * This unit driver copes with the lack of specific order in the same way the
+ * firewire net driver does -- each probe, for either a local or remote unit
+ * device, is treated as a 'peer' (has a struct fwtty_peer instance) and the
+ * first peer created for a given fw_card (tracked by the global fwserial_list)
+ * creates the underlying TTYs (aggregated in a fw_serial instance).
+ *
+ * NB: an early attempt to differentiate local & remote unit devices by creating
+ *     peers only for remote units and fw_serial instances (with their
+ *     associated TTY devices) only for local units was discarded. Managing
+ *     the peer lifetimes on device removal proved too complicated.
+ *
+ * fwserial_probe/fwserial_remove are effectively serialized by the
+ * fwserial_list_mutex. This is necessary because the addition of the first peer
+ * for a given fw_card will trigger the creation of the fw_serial for that
+ * fw_card, which must not simultaneously contend with the removal of the
+ * last peer for a given fw_card triggering the destruction of the same
+ * fw_serial for the same fw_card.
+ */
+static int fwserial_probe(struct device *dev)
+{
+	struct fw_unit *unit = fw_unit(dev);
+	struct fw_serial *serial;
+	int err;
+
+	mutex_lock(&fwserial_list_mutex);
+	serial = fwserial_lookup(fw_parent_device(unit)->card);
+	if (!serial)
+		err = fwserial_create(unit);
+	else
+		err = fwserial_add_peer(serial, unit);
+	mutex_unlock(&fwserial_list_mutex);
+	return err;
+}
+
+/**
+ * fwserial_remove: bus removal function for firewire 'serial' unit devices
+ *
+ * The corresponding 'peer' for this unit device is removed from the list of
+ * peers for the associated fw_serial (which has a 1:1 correspondence with a
+ * specific fw_card). If this is the last peer being removed, then trigger
+ * the destruction of the underlying TTYs.
+ */
+static int fwserial_remove(struct device *dev)
+{
+	struct fwtty_peer *peer = dev_get_drvdata(dev);
+	struct fw_serial *serial = peer->serial;
+	int i;
+
+	mutex_lock(&fwserial_list_mutex);
+	fwserial_remove_peer(peer);
+
+	if (list_empty(&serial->peer_list)) {
+		/* unlink from the fwserial_list here */
+		list_del_rcu(&serial->list);
+
+		for (i = 0; i < num_ports; ++i)
+			fwserial_close_port(serial->ports[i]);
+		kref_put(&serial->kref, fwserial_destroy);
+	}
+	mutex_unlock(&fwserial_list_mutex);
+
+	return 0;
+}
+
+/**
+ * fwserial_update: bus update function for 'firewire' serial unit devices
+ *
+ * Updates the new node_id and bus generation for this peer. Note that locking
+ * is unnecessary; but careful memory barrier usage is important to enforce the
+ * load and store order of generation & node_id.
+ *
+ * The fw-core orders the write of node_id before generation in the parent
+ * fw_device to ensure that a stale node_id cannot be used with a current
+ * bus generation. So the generation value must be read before the node_id.
+ *
+ * In turn, this orders the write of node_id before generation in the peer to
+ * also ensure a stale node_id cannot be used with a current bus generation.
+ */
+static void fwserial_update(struct fw_unit *unit)
+{
+	struct fw_device *parent = fw_parent_device(unit);
+	struct fwtty_peer *peer = dev_get_drvdata(&unit->device);
+	int generation;
+
+	generation = parent->generation;
+	smp_rmb();
+	peer->node_id = parent->node_id;
+	smp_wmb();
+	peer->generation = generation;
+}
+
+static const struct ieee1394_device_id fwserial_id_table[] = {
+	{
+		.match_flags  = IEEE1394_MATCH_SPECIFIER_ID |
+				IEEE1394_MATCH_VERSION,
+		.specifier_id = LINUX_VENDOR_ID,
+		.version      = FWSERIAL_VERSION,
+	},
+	{ }
+};
+
+static struct fw_driver fwserial_driver = {
+	.driver = {
+		.owner  = THIS_MODULE,
+		.name   = KBUILD_MODNAME,
+		.bus    = &fw_bus_type,
+		.probe  = fwserial_probe,
+		.remove = fwserial_remove,
+	},
+	.update   = fwserial_update,
+	.id_table = fwserial_id_table,
+};
+
+#define FW_UNIT_SPECIFIER(id)	((CSR_SPECIFIER_ID << 24) | (id))
+#define FW_UNIT_VERSION(ver)	((CSR_VERSION << 24) | (ver))
+#define FW_UNIT_ADDRESS(ofs)	(((CSR_OFFSET | CSR_DEPENDENT_INFO) << 24)  \
+				 | (((ofs) - CSR_REGISTER_BASE) >> 2))
+/* XXX: config ROM definitons could be improved with semi-automated offset
+ * and length calculation
+ */
+#define FW_ROM_DESCRIPTOR(ofs)	(((CSR_LEAF | CSR_DESCRIPTOR) << 24) | (ofs))
+
+struct fwserial_unit_directory_data {
+	u16	crc;
+	u16	len;
+	u32	unit_specifier;
+	u32	unit_sw_version;
+	u32	unit_addr_offset;
+	u32	desc1_ofs;
+	u16	desc1_crc;
+	u16	desc1_len;
+	u32	desc1_data[5];
+} __packed;
+
+static struct fwserial_unit_directory_data fwserial_unit_directory_data = {
+	.len = 4,
+	.unit_specifier = FW_UNIT_SPECIFIER(LINUX_VENDOR_ID),
+	.unit_sw_version = FW_UNIT_VERSION(FWSERIAL_VERSION),
+	.desc1_ofs = FW_ROM_DESCRIPTOR(1),
+	.desc1_len = 5,
+	.desc1_data = {
+		0x00000000,			/*   type = text            */
+		0x00000000,			/*   enc = ASCII, lang EN   */
+		0x4c696e75,			/* 'Linux TTY'              */
+		0x78205454,
+		0x59000000,
+	},
+};
+
+static struct fw_descriptor fwserial_unit_directory = {
+	.length = sizeof(fwserial_unit_directory_data) / sizeof(u32),
+	.key    = (CSR_DIRECTORY | CSR_UNIT) << 24,
+	.data   = (u32 *)&fwserial_unit_directory_data,
+};
+
+/*
+ * The management address is in the unit space region but above other known
+ * address users (to keep wild writes from causing havoc)
+ */
+const struct fw_address_region fwserial_mgmt_addr_region = {
+	.start = CSR_REGISTER_BASE + 0x1e0000ULL,
+	.end = 0x1000000000000ULL,
+};
+
+static struct fw_address_handler fwserial_mgmt_addr_handler;
+
+/**
+ * fwserial_handle_plug_req - handle VIRT_CABLE_PLUG request work
+ * @work: ptr to peer->work
+ *
+ * Attempts to complete the VIRT_CABLE_PLUG handshake sequence for this peer.
+ *
+ * This checks for a collided request-- ie, that a VIRT_CABLE_PLUG request was
+ * already sent to this peer. If so, the collision is resolved by comparing
+ * guid values; the loser sends the plug response.
+ *
+ * Note: if an error prevents a response, don't do anything -- the
+ * remote will timeout its request.
+ */
+static void fwserial_handle_plug_req(struct work_struct *work)
+{
+	struct fwtty_peer *peer = to_peer(work, work);
+	struct virt_plug_params *plug_req = &peer->work_params.plug_req;
+	struct fwtty_port *port;
+	struct fwserial_mgmt_pkt *pkt;
+	int rcode;
+
+	pkt = kmalloc(sizeof(*pkt), GFP_KERNEL);
+	if (!pkt)
+		return;
+
+	port = fwserial_find_port(peer);
+
+	spin_lock_bh(&peer->lock);
+
+	switch (peer->state) {
+	case FWPS_NOT_ATTACHED:
+		if (!port) {
+			fwtty_err(&peer->unit, "no more ports avail");
+			fill_plug_rsp_nack(pkt);
+		} else {
+			peer->port = port;
+			fill_plug_rsp_ok(pkt, peer->port);
+			peer_set_state(peer, FWPS_PLUG_RESPONDING);
+			/* don't release claimed port */
+			port = NULL;
+		}
+		break;
+
+	case FWPS_PLUG_PENDING:
+		if (peer->serial->card->guid > peer->guid)
+			goto cleanup;
+
+		/* We lost - hijack the already-claimed port and send ok */
+		del_timer(&peer->timer);
+		fill_plug_rsp_ok(pkt, peer->port);
+		peer_set_state(peer, FWPS_PLUG_RESPONDING);
+		break;
+
+	default:
+		fill_plug_rsp_nack(pkt);
+	}
+
+	spin_unlock_bh(&peer->lock);
+	if (port)
+		fwserial_release_port(port);
+
+	rcode = fwserial_send_mgmt_sync(peer, pkt);
+
+	spin_lock_bh(&peer->lock);
+	if (peer->state == FWPS_PLUG_RESPONDING) {
+		if (rcode == RCODE_COMPLETE) {
+			struct fwtty_port *tmp = peer->port;
+
+			fwserial_virt_plug_complete(peer, plug_req);
+			spin_unlock_bh(&peer->lock);
+
+			fwtty_write_port_status(tmp);
+			spin_lock_bh(&peer->lock);
+		} else {
+			fwtty_err(&peer->unit, "PLUG_RSP error (%d)", rcode);
+			port = peer_revert_state(peer);
+		}
+	}
+cleanup:
+	spin_unlock_bh(&peer->lock);
+	if (port)
+		fwserial_release_port(port);
+	kfree(pkt);
+	return;
+}
+
+static void fwserial_handle_unplug_req(struct work_struct *work)
+{
+	struct fwtty_peer *peer = to_peer(work, work);
+	struct fwtty_port *port = NULL;
+	struct fwserial_mgmt_pkt *pkt;
+	int rcode;
+
+	pkt = kmalloc(sizeof(*pkt), GFP_KERNEL);
+	if (!pkt)
+		return;
+
+	spin_lock_bh(&peer->lock);
+
+	switch (peer->state) {
+	case FWPS_ATTACHED:
+		fill_unplug_rsp_ok(pkt);
+		peer_set_state(peer, FWPS_UNPLUG_RESPONDING);
+		break;
+
+	case FWPS_UNPLUG_PENDING:
+		if (peer->serial->card->guid > peer->guid)
+			goto cleanup;
+
+		/* We lost - send unplug rsp */
+		del_timer(&peer->timer);
+		fill_unplug_rsp_ok(pkt);
+		peer_set_state(peer, FWPS_UNPLUG_RESPONDING);
+		break;
+
+	default:
+		fill_unplug_rsp_nack(pkt);
+	}
+
+	spin_unlock_bh(&peer->lock);
+
+	rcode = fwserial_send_mgmt_sync(peer, pkt);
+
+	spin_lock_bh(&peer->lock);
+	if (peer->state == FWPS_UNPLUG_RESPONDING) {
+		if (rcode == RCODE_COMPLETE)
+			port = peer_revert_state(peer);
+		else
+			fwtty_err(&peer->unit, "UNPLUG_RSP error (%d)", rcode);
+	}
+cleanup:
+	spin_unlock_bh(&peer->lock);
+	if (port)
+		fwserial_release_port(port);
+	kfree(pkt);
+	return;
+}
+
+static int fwserial_parse_mgmt_write(struct fwtty_peer *peer,
+				     struct fwserial_mgmt_pkt *pkt,
+				     unsigned long long addr,
+				     size_t len)
+{
+	struct fwtty_port *port = NULL;
+	int rcode;
+
+	if (addr != fwserial_mgmt_addr_handler.offset || len < sizeof(pkt->hdr))
+		return RCODE_ADDRESS_ERROR;
+
+	if (len != be16_to_cpu(pkt->hdr.len) ||
+	    len != mgmt_pkt_expected_len(pkt->hdr.code))
+		return RCODE_DATA_ERROR;
+
+	spin_lock_bh(&peer->lock);
+	if (peer->state == FWPS_GONE) {
+		/*
+		 * This should never happen - it would mean that the
+		 * remote unit that just wrote this transaction was
+		 * already removed from the bus -- and the removal was
+		 * processed before we rec'd this transaction
+		 */
+		fwtty_err(&peer->unit, "peer already removed");
+		spin_unlock_bh(&peer->lock);
+		return RCODE_ADDRESS_ERROR;
+	}
+
+	rcode = RCODE_COMPLETE;
+
+	fwtty_dbg(&peer->unit, "mgmt: hdr.code: %04hx", pkt->hdr.code);
+
+	switch (be16_to_cpu(pkt->hdr.code) & FWSC_CODE_MASK) {
+	case FWSC_VIRT_CABLE_PLUG:
+		if (work_pending(&peer->work)) {
+			fwtty_err(&peer->unit, "plug req: busy");
+			rcode = RCODE_CONFLICT_ERROR;
+
+		} else {
+			peer->work_params.plug_req = pkt->plug_req;
+			PREPARE_WORK(&peer->work, fwserial_handle_plug_req);
+			queue_work(system_unbound_wq, &peer->work);
+		}
+		break;
+
+	case FWSC_VIRT_CABLE_PLUG_RSP:
+		if (peer->state != FWPS_PLUG_PENDING) {
+			rcode = RCODE_CONFLICT_ERROR;
+
+		} else if (be16_to_cpu(pkt->hdr.code) & FWSC_RSP_NACK) {
+			fwtty_notice(&peer->unit, "NACK plug rsp");
+			port = peer_revert_state(peer);
+
+		} else {
+			struct fwtty_port *tmp = peer->port;
+
+			fwserial_virt_plug_complete(peer, &pkt->plug_rsp);
+			spin_unlock_bh(&peer->lock);
+
+			fwtty_write_port_status(tmp);
+			spin_lock_bh(&peer->lock);
+		}
+		break;
+
+	case FWSC_VIRT_CABLE_UNPLUG:
+		if (work_pending(&peer->work)) {
+			fwtty_err(&peer->unit, "unplug req: busy");
+			rcode = RCODE_CONFLICT_ERROR;
+		} else {
+			PREPARE_WORK(&peer->work, fwserial_handle_unplug_req);
+			queue_work(system_unbound_wq, &peer->work);
+		}
+		break;
+
+	case FWSC_VIRT_CABLE_UNPLUG_RSP:
+		if (peer->state != FWPS_UNPLUG_PENDING)
+			rcode = RCODE_CONFLICT_ERROR;
+		else {
+			if (be16_to_cpu(pkt->hdr.code) & FWSC_RSP_NACK)
+				fwtty_notice(&peer->unit, "NACK unplug?");
+			port = peer_revert_state(peer);
+		}
+		break;
+
+	default:
+		fwtty_err(&peer->unit, "unknown mgmt code %d",
+			  be16_to_cpu(pkt->hdr.code));
+		rcode = RCODE_DATA_ERROR;
+	}
+	spin_unlock_bh(&peer->lock);
+
+	if (port)
+		fwserial_release_port(port);
+
+	return rcode;
+}
+
+/**
+ * fwserial_mgmt_handler: bus address handler for mgmt requests
+ * @parameters: fw_address_callback_t as specified by firewire core interface
+ *
+ * This handler is responsible for handling virtual cable requests from remotes
+ * for all cards.
+ */
+static void fwserial_mgmt_handler(struct fw_card *card,
+				  struct fw_request *request,
+				  int tcode, int destination, int source,
+				  int generation,
+				  unsigned long long addr,
+				  void *data, size_t len,
+				  void *callback_data)
+{
+	struct fwserial_mgmt_pkt *pkt = data;
+	struct fwtty_peer *peer;
+	int rcode;
+
+	rcu_read_lock();
+	peer = __fwserial_peer_by_node_id(card, generation, source);
+	if (!peer) {
+		fwtty_dbg(card, "peer(%d:%x) not found", generation, source);
+		__dump_peer_list(card);
+		rcode = RCODE_CONFLICT_ERROR;
+
+	} else {
+		switch (tcode) {
+		case TCODE_WRITE_BLOCK_REQUEST:
+			rcode = fwserial_parse_mgmt_write(peer, pkt, addr, len);
+			break;
+
+		default:
+			rcode = RCODE_TYPE_ERROR;
+		}
+	}
+
+	rcu_read_unlock();
+	fw_send_response(card, request, rcode);
+}
+
+static int __init fwserial_init(void)
+{
+	int err, num_loops = !!(create_loop_dev);
+
+	/* num_ttys/num_ports must not be set above the static alloc avail */
+	if (num_ttys + num_loops > MAX_CARD_PORTS)
+		num_ttys = MAX_CARD_PORTS - num_loops;
+	num_ports = num_ttys + num_loops;
+
+	fwtty_driver = alloc_tty_driver(MAX_TOTAL_PORTS);
+	if (!fwtty_driver) {
+		err = -ENOMEM;
+		return err;
+	}
+
+	fwtty_driver->driver_name	= KBUILD_MODNAME;
+	fwtty_driver->name		= tty_dev_name;
+	fwtty_driver->major		= 0;
+	fwtty_driver->minor_start	= 0;
+	fwtty_driver->type		= TTY_DRIVER_TYPE_SERIAL;
+	fwtty_driver->subtype		= SERIAL_TYPE_NORMAL;
+	fwtty_driver->flags		= TTY_DRIVER_REAL_RAW |
+						TTY_DRIVER_DYNAMIC_DEV;
+
+	fwtty_driver->init_termios	    = tty_std_termios;
+	fwtty_driver->init_termios.c_cflag  |= CLOCAL;
+	tty_set_operations(fwtty_driver, &fwtty_ops);
+
+	err = tty_register_driver(fwtty_driver);
+	if (err) {
+		driver_err("register tty driver failed (%d)", err);
+		goto put_tty;
+	}
+
+	fwtty_txn_cache = kmem_cache_create("fwtty_txn_cache",
+					    sizeof(struct fwtty_transaction),
+					    0, 0, fwtty_txn_constructor);
+	if (!fwtty_txn_cache) {
+		err = -ENOMEM;
+		goto unregister_driver;
+	}
+
+	/*
+	 * Ideally, this address handler would be registered per local node
+	 * (rather than the same handler for all local nodes). However,
+	 * since the firewire core requires the config rom descriptor *before*
+	 * the local unit device(s) are created, a single management handler
+	 * must suffice for all local serial units.
+	 */
+	fwserial_mgmt_addr_handler.length = sizeof(struct fwserial_mgmt_pkt);
+	fwserial_mgmt_addr_handler.address_callback = fwserial_mgmt_handler;
+
+	err = fw_core_add_address_handler(&fwserial_mgmt_addr_handler,
+					  &fwserial_mgmt_addr_region);
+	if (err) {
+		driver_err("add management handler failed (%d)", err);
+		goto destroy_cache;
+	}
+
+	fwserial_unit_directory_data.unit_addr_offset =
+		FW_UNIT_ADDRESS(fwserial_mgmt_addr_handler.offset);
+	err = fw_core_add_descriptor(&fwserial_unit_directory);
+	if (err) {
+		driver_err("add unit descriptor failed (%d)", err);
+		goto remove_handler;
+	}
+
+	err = driver_register(&fwserial_driver.driver);
+	if (err) {
+		driver_err("register fwserial driver failed (%d)", err);
+		goto remove_descriptor;
+	}
+
+	return 0;
+
+remove_descriptor:
+	fw_core_remove_descriptor(&fwserial_unit_directory);
+remove_handler:
+	fw_core_remove_address_handler(&fwserial_mgmt_addr_handler);
+destroy_cache:
+	kmem_cache_destroy(fwtty_txn_cache);
+unregister_driver:
+	tty_unregister_driver(fwtty_driver);
+put_tty:
+	put_tty_driver(fwtty_driver);
+	return err;
+}
+
+static void __exit fwserial_exit(void)
+{
+	driver_unregister(&fwserial_driver.driver);
+	fw_core_remove_descriptor(&fwserial_unit_directory);
+	fw_core_remove_address_handler(&fwserial_mgmt_addr_handler);
+	kmem_cache_destroy(fwtty_txn_cache);
+	tty_unregister_driver(fwtty_driver);
+	put_tty_driver(fwtty_driver);
+}
+
+module_init(fwserial_init);
+module_exit(fwserial_exit);
+
+MODULE_AUTHOR("Peter Hurley (peter@hurleysoftware.com)");
+MODULE_DESCRIPTION("FireWire Serial TTY Driver");
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(ieee1394, fwserial_id_table);
+MODULE_PARM_DESC(ttys, "Number of ttys to create for each local firewire node");
+MODULE_PARM_DESC(auto, "Auto-connect a tty to each firewire node discovered");
+MODULE_PARM_DESC(loop, "Create a loopback device, fwloop<n>, with ttys");
+MODULE_PARM_DESC(limit_bw, "Limit bandwidth utilization to 20%.");
diff --git a/drivers/staging/fwserial/fwserial.h b/drivers/staging/fwserial/fwserial.h
new file mode 100644
index 0000000..8b572ed
--- /dev/null
+++ b/drivers/staging/fwserial/fwserial.h
@@ -0,0 +1,387 @@
+#ifndef _FIREWIRE_FWSERIAL_H
+#define _FIREWIRE_FWSERIAL_H
+
+#include <linux/kernel.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/list.h>
+#include <linux/firewire.h>
+#include <linux/firewire-constants.h>
+#include <linux/spinlock.h>
+#include <linux/rcupdate.h>
+#include <linux/mutex.h>
+#include <linux/serial.h>
+#include <linux/serial_reg.h>
+#include <linux/module.h>
+#include <linux/seq_file.h>
+
+#include "dma_fifo.h"
+
+#ifdef FWTTY_PROFILING
+#define DISTRIBUTION_MAX_SIZE     8192
+#define DISTRIBUTION_MAX_INDEX    (ilog2(DISTRIBUTION_MAX_SIZE) + 1)
+static inline void profile_size_distrib(unsigned stat[], unsigned val)
+{
+	int n = (val) ? min(ilog2(val) + 1, DISTRIBUTION_MAX_INDEX) : 0;
+	++stat[n];
+}
+#else
+#define DISTRIBUTION_MAX_INDEX    0
+#define profile_size_distrib(st, n)
+#endif
+
+/* Parameters for both VIRT_CABLE_PLUG & VIRT_CABLE_PLUG_RSP mgmt codes */
+struct virt_plug_params {
+	__be32  status_hi;
+	__be32  status_lo;
+	__be32	fifo_hi;
+	__be32	fifo_lo;
+	__be32	fifo_len;
+};
+
+struct peer_work_params {
+	union {
+		struct virt_plug_params plug_req;
+	};
+};
+
+/**
+ * fwtty_peer: structure representing local & remote unit devices
+ * @unit: unit child device of fw_device node
+ * @serial: back pointer to associated fw_serial aggregate
+ * @guid: unique 64-bit guid for this unit device
+ * @generation: most recent bus generation
+ * @node_id: most recent node_id
+ * @speed: link speed of peer (0 = S100, 2 = S400, ... 5 = S3200)
+ * @mgmt_addr: bus addr region to write mgmt packets to
+ * @status_addr: bus addr register to write line status to
+ * @fifo_addr: bus addr region to write serial output to
+ * @fifo_len:  max length for single write to fifo_addr
+ * @list: link for insertion into fw_serial's peer_list
+ * @rcu: for deferring peer reclamation
+ * @lock: spinlock to synchonize changes to state & port fields
+ * @work: only one work item can be queued at any one time
+ *        Note: pending work is canceled prior to removal, so this
+ *        peer is valid for at least the lifetime of the work function
+ * @work_params: parameter block for work functions
+ * @timer: timer for resetting peer state if remote request times out
+ * @state: current state
+ * @connect: work item for auto-connecting
+ * @connect_retries: # of connections already attempted
+ * @port: associated tty_port (usable if state == FWSC_ATTACHED)
+ */
+struct fwtty_peer {
+	struct fw_unit		*unit;
+	struct fw_serial	*serial;
+	u64			guid;
+	int			generation;
+	int			node_id;
+	unsigned		speed;
+	int			max_payload;
+	u64			mgmt_addr;
+
+	/* these are usable only if state == FWSC_ATTACHED */
+	u64			status_addr;
+	u64			fifo_addr;
+	int			fifo_len;
+
+	struct list_head	list;
+	struct rcu_head		rcu;
+
+	spinlock_t		lock;
+	struct work_struct	work;
+	struct peer_work_params work_params;
+	struct timer_list	timer;
+	int			state;
+	struct delayed_work	connect;
+	int			connect_retries;
+
+	struct fwtty_port	*port;
+};
+
+#define to_peer(ptr, field)	(container_of(ptr, struct fwtty_peer, field))
+
+/* state values for fwtty_peer.state field */
+enum fwtty_peer_state {
+	FWPS_GONE,
+	FWPS_NOT_ATTACHED,
+	FWPS_ATTACHED,
+	FWPS_PLUG_PENDING,
+	FWPS_PLUG_RESPONDING,
+	FWPS_UNPLUG_PENDING,
+	FWPS_UNPLUG_RESPONDING,
+
+	FWPS_NO_MGMT_ADDR = -1,
+};
+
+#define CONNECT_RETRY_DELAY	HZ
+#define MAX_CONNECT_RETRIES	10
+
+/* must be holding peer lock for these state funclets */
+static inline void peer_set_state(struct fwtty_peer *peer, int new)
+{
+	peer->state = new;
+}
+
+static inline struct fwtty_port *peer_revert_state(struct fwtty_peer *peer)
+{
+	struct fwtty_port *port = peer->port;
+
+	peer->port = NULL;
+	peer_set_state(peer, FWPS_NOT_ATTACHED);
+	return port;
+}
+
+struct fwserial_mgmt_pkt {
+	struct {
+		__be16		len;
+		__be16		code;
+	} hdr;
+	union {
+		struct virt_plug_params plug_req;
+		struct virt_plug_params plug_rsp;
+	};
+} __packed;
+
+/* fwserial_mgmt_packet codes */
+#define FWSC_RSP_OK			0x0000
+#define FWSC_RSP_NACK			0x8000
+#define FWSC_CODE_MASK			0x0fff
+
+#define FWSC_VIRT_CABLE_PLUG		1
+#define FWSC_VIRT_CABLE_UNPLUG		2
+#define FWSC_VIRT_CABLE_PLUG_RSP	3
+#define FWSC_VIRT_CABLE_UNPLUG_RSP	4
+
+/* 1 min. plug timeout -- suitable for userland authorization */
+#define VIRT_CABLE_PLUG_TIMEOUT		(60 * HZ)
+
+struct stats {
+	unsigned	xchars;
+	unsigned	dropped;
+	unsigned	tx_stall;
+	unsigned	fifo_errs;
+	unsigned	sent;
+	unsigned	lost;
+	unsigned	throttled;
+	unsigned	watermark;
+	unsigned	reads[DISTRIBUTION_MAX_INDEX + 1];
+	unsigned	writes[DISTRIBUTION_MAX_INDEX + 1];
+	unsigned	txns[DISTRIBUTION_MAX_INDEX + 1];
+	unsigned	unthrottle[DISTRIBUTION_MAX_INDEX + 1];
+};
+
+struct fwconsole_ops {
+	void (*notify)(int code, void *data);
+	void (*stats)(struct stats *stats, void *data);
+	void (*proc_show)(struct seq_file *m, void *data);
+};
+
+/* codes for console ops notify */
+#define FWCON_NOTIFY_ATTACH		1
+#define FWCON_NOTIFY_DETACH		2
+
+struct buffered_rx {
+	struct list_head	list;
+	size_t			n;
+	unsigned char		data[0];
+};
+
+/**
+ * fwtty_port: structure used to track/represent underlying tty_port
+ * @port: underlying tty_port
+ * @device: tty device
+ * @index: index into port_table for this particular port
+ *    note: minor = index + FWSERIAL_TTY_START_MINOR
+ * @serial: back pointer to the containing fw_serial
+ * @rx_handler: bus address handler for unique addr region used by remotes
+ *              to communicate with this port. Every port uses
+ *		fwtty_port_handler() for per port transactions.
+ * @fwcon_ops: ops for attached fw_console (if any)
+ * @con_data: private data for fw_console
+ * @wait_tx: waitqueue for sleeping until writer/drain completes tx
+ * @emit_breaks: delayed work responsible for generating breaks when the
+ *               break line status is active
+ * @cps : characters per second computed from the termios settings
+ * @break_last: timestamp in jiffies from last emit_breaks
+ * @hangup: work responsible for HUPing when carrier is dropped/lost
+ * @mstatus: loose virtualization of LSR/MSR
+ *         bits 15..0  correspond to TIOCM_* bits
+ *         bits 19..16 reserved for mctrl
+ *         bit 20      OOB_TX_THROTTLE
+ *	   bits 23..21 reserved
+ *         bits 31..24 correspond to UART_LSR_* bits
+ * @lock: spinlock for protecting concurrent access to fields below it
+ * @mctrl: loose virtualization of MCR
+ *         bits 15..0  correspond to TIOCM_* bits
+ *         bit 16      OOB_RX_THROTTLE
+ *         bits 19..17 reserved
+ *	   bits 31..20 reserved for mstatus
+ * @drain: delayed work scheduled to ensure that writes are flushed.
+ *         The work can race with the writer but concurrent sending is
+ *         prevented with the IN_TX flag. Scheduled under lock to
+ *         limit scheduling when fifo has just been drained.
+ * @push: work responsible for pushing buffered rx to the ldisc.
+ *	  rx can become buffered if the tty buffer is filled before the
+ *	  ldisc throttles the sender.
+ * @buf_list: list of buffered rx yet to be sent to ldisc
+ * @buffered: byte count of buffered rx
+ * @tx_fifo: fifo used to store & block-up writes for dma to remote
+ * @max_payload: max bytes transmissable per dma (based on peer's max_payload)
+ * @status_mask: UART_LSR_* bitmask significant to rx (based on termios)
+ * @ignore_mask: UART_LSR_* bitmask of states to ignore (also based on termios)
+ * @break_ctl: if set, port is 'sending break' to remote
+ * @write_only: self-explanatory
+ * @overrun: previous rx was lost (partially or completely)
+ * @loopback: if set, port is in loopback mode
+ * @flags: atomic bit flags
+ *         bit 0: IN_TX - gate to allow only one cpu to send from the dma fifo
+ *                        at a time.
+ *         bit 1: STOP_TX - force tx to exit while sending
+ * @peer: rcu-pointer to associated fwtty_peer (if attached)
+ *        NULL if no peer attached
+ * @icount: predefined statistics reported by the TIOCGICOUNT ioctl
+ * @stats: additional statistics reported in /proc/tty/driver/firewire_serial
+ */
+struct fwtty_port {
+	struct tty_port		   port;
+	struct device		   *device;
+	unsigned		   index;
+	struct fw_serial	   *serial;
+	struct fw_address_handler  rx_handler;
+
+	struct fwconsole_ops	   *fwcon_ops;
+	void			   *con_data;
+
+	wait_queue_head_t	   wait_tx;
+	struct delayed_work	   emit_breaks;
+	unsigned		   cps;
+	unsigned long		   break_last;
+
+	struct work_struct	   hangup;
+
+	unsigned		   mstatus;
+
+	spinlock_t		   lock;
+	unsigned		   mctrl;
+	struct delayed_work	   drain;
+	struct work_struct	   push;
+	struct list_head	   buf_list;
+	int			   buffered;
+	struct dma_fifo		   tx_fifo;
+	int			   max_payload;
+	unsigned		   status_mask;
+	unsigned		   ignore_mask;
+	unsigned		   break_ctl:1,
+				   write_only:1,
+				   overrun:1,
+				   loopback:1;
+	unsigned long		   flags;
+
+	struct fwtty_peer	   *peer;
+
+	struct async_icount	   icount;
+	struct stats		   stats;
+};
+
+#define to_port(ptr, field)	(container_of(ptr, struct fwtty_port, field))
+
+/* bit #s for flags field */
+#define IN_TX                      0
+#define STOP_TX                    1
+#define BUFFERING_RX		   2
+
+/* bitmasks for special mctrl/mstatus bits */
+#define OOB_RX_THROTTLE   0x00010000
+#define MCTRL_RSRVD       0x000e0000
+#define OOB_TX_THROTTLE   0x00100000
+#define MSTATUS_RSRVD     0x00e00000
+
+#define MCTRL_MASK        (TIOCM_DTR | TIOCM_RTS | TIOCM_OUT1 | TIOCM_OUT2 | \
+			   TIOCM_LOOP | OOB_RX_THROTTLE | MCTRL_RSRVD)
+
+/* XXX even every 1/50th secs. may be unnecessarily accurate */
+/* delay in jiffies between brk emits */
+#define FREQ_BREAKS        (HZ / 50)
+
+/* Ports are allocated in blocks of num_ports for each fw_card */
+#define MAX_CARD_PORTS           32	/* max # of ports per card */
+#define MAX_TOTAL_PORTS          64	/* max # of ports total    */
+
+/* tuning parameters */
+#define FWTTY_PORT_TXFIFO_LEN	4096
+#define FWTTY_PORT_MAX_PEND_DMA    8    /* costs a cache line per pend */
+#define DRAIN_THRESHOLD         1024
+#define MAX_ASYNC_PAYLOAD       4096    /* ohci-defined limit          */
+#define WRITER_MINIMUM           128
+/* TODO: how to set watermark to AR context size? see fwtty_rx() */
+#define HIGH_WATERMARK         32768	/* AR context is 32K	       */
+
+/*
+ * Size of bus addr region above 4GB used per port as the recv addr
+ * - must be at least as big as the MAX_ASYNC_PAYLOAD
+ */
+#define FWTTY_PORT_RXFIFO_LEN	MAX_ASYNC_PAYLOAD
+
+/**
+ * fw_serial: aggregate used to associate tty ports with specific fw_card
+ * @card: fw_card associated with this fw_serial device (1:1 association)
+ * @kref: reference-counted multi-port management allows delayed destroy
+ * @self: local unit device as 'peer'. Not valid until local unit device
+ *         is enumerated.
+ * @list: link for insertion into fwserial_list
+ * @peer_list: list of local & remote unit devices attached to this card
+ * @ports: fixed array of tty_ports provided by this serial device
+ */
+struct fw_serial {
+	struct fw_card	  *card;
+	struct kref	  kref;
+
+	struct fwtty_peer *self;
+
+	struct list_head  list;
+	struct list_head  peer_list;
+
+	struct fwtty_port *ports[MAX_CARD_PORTS];
+};
+
+#define to_serial(ptr, field)	(container_of(ptr, struct fw_serial, field))
+
+#define TTY_DEV_NAME		    "fwtty"	/* ttyFW was taken           */
+static const char tty_dev_name[] =  TTY_DEV_NAME;
+static const char loop_dev_name[] = "fwloop";
+extern bool limit_bw;
+
+struct tty_driver *fwtty_driver;
+
+#define driver_err(s, v...)	pr_err(KBUILD_MODNAME ": " s, ##v)
+
+struct fwtty_port *fwtty_port_get(unsigned index);
+void fwtty_port_put(struct fwtty_port *port);
+
+static inline void fwtty_bind_console(struct fwtty_port *port,
+				      struct fwconsole_ops *fwcon_ops,
+				      void *data)
+{
+	port->con_data = data;
+	port->fwcon_ops = fwcon_ops;
+}
+
+/*
+ * Returns the max send async payload size in bytes based on the unit device
+ * link speed - if set to limit bandwidth to max 20%, use lookup table
+ */
+static inline int link_speed_to_max_payload(unsigned speed)
+{
+	static const int max_async[] = { 307, 614, 1229, 2458, 4916, 9832, };
+	BUILD_BUG_ON(ARRAY_SIZE(max_async) - 1 != SCODE_3200);
+
+	speed = clamp(speed, (unsigned) SCODE_100, (unsigned) SCODE_3200);
+	if (limit_bw)
+		return max_async[speed];
+	else
+		return 1 << (speed + 9);
+}
+
+#endif /* _FIREWIRE_FWSERIAL_H */
-- 
1.7.12.3


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 1/1] staging: fwserial: Add TTY-over-Firewire serial driver
  2012-11-02 12:16   ` [PATCH v2 1/1] staging: fwserial: Add TTY-over-Firewire serial driver Peter Hurley
@ 2012-11-12 23:33     ` Stefan Richter
  2012-11-12 23:51       ` Greg Kroah-Hartman
  2012-11-13 19:14       ` Peter Hurley
  0 siblings, 2 replies; 20+ messages in thread
From: Stefan Richter @ 2012-11-12 23:33 UTC (permalink / raw)
  To: Peter Hurley, Greg Kroah-Hartman
  Cc: Alan Cox, linux-kernel, devel, linux1394-devel, linux-serial

On Nov 02 Peter Hurley wrote:
> This patch provides the kernel driver for high-speed TTY
> communication over the IEEE 1394 bus.
> 
> Signed-off-by: Peter Hurley <peter@hurleysoftware.com>

Hi Greg,

you asked for an Ack.  Here it is:  I am OK with this getting added to the
staging tree.

(Sorry for the delay, I was ill and at the same time buried in work at the
day job.)  Note, I only quickly scrolled through v1 and v2.  I will do an
actual review when somebody asks to move this from staging into a proper
mainline place.

Peter,
below are some high-level comments to your TODOs.

> ---
> v2
[...]
> ---
>  drivers/staging/Kconfig             |    2 +
>  drivers/staging/Makefile            |    1 +
>  drivers/staging/fwserial/Kconfig    |    9 +
>  drivers/staging/fwserial/Makefile   |    2 +
>  drivers/staging/fwserial/TODO       |   37 +
>  drivers/staging/fwserial/dma_fifo.c |  310 ++++
>  drivers/staging/fwserial/dma_fifo.h |  130 ++
>  drivers/staging/fwserial/fwserial.c | 2946 +++++++++++++++++++++++++++++++++++
>  drivers/staging/fwserial/fwserial.h |  387 +++++
>  9 files changed, 3824 insertions(+)
>  create mode 100644 drivers/staging/fwserial/Kconfig
>  create mode 100644 drivers/staging/fwserial/Makefile
>  create mode 100644 drivers/staging/fwserial/TODO
>  create mode 100644 drivers/staging/fwserial/dma_fifo.c
>  create mode 100644 drivers/staging/fwserial/dma_fifo.h
>  create mode 100644 drivers/staging/fwserial/fwserial.c
>  create mode 100644 drivers/staging/fwserial/fwserial.h
[...]
> --- /dev/null
> +++ b/drivers/staging/fwserial/Kconfig
> @@ -0,0 +1,9 @@
> +config FIREWIRE_SERIAL
> +       tristate "TTY over Firewire"
> +       depends on FIREWIRE
> +       help
> +          This enables TTY over IEEE 1394, providing high-speed serial
> +	  connectivity to cabled peers.
> +
> +	  To compile this driver as a module, say M here:  the module will
> +	  be called firewire-serial.

This should mention that this is a Linux-only affair at this time:  It uses
an ad-hoc defined protocol which no other platform is known to support for
the time being.

[...]
> --- /dev/null
> +++ b/drivers/staging/fwserial/TODO
> @@ -0,0 +1,37 @@
> +TODOs
> +-----
> +1. Implement retries for RCODE_BUSY, RCODE_NO_ACK and RCODE_SEND_ERROR
> +   - I/O is handled asynchronously which presents some issues when error
> +     conditions occur.
> +2. Implement _robust_ console on top of this. The existing prototype console
> +   driver is not ready for the big leagues yet.
> +3. Expose means of controlling attach/detach of peers via sysfs. Include
> +   GUID-to-port matching/whitelist/blacklist.
> +
> +-- Issues with firewire stack --
> +1. This driver uses the same unregistered vendor id that the firewire core does
> +     (0xd00d1e). Perhaps this could be exposed as a define in
> +     firewire-constants.h?

I guess there is no better OUI for this purpose.

The constant could be added to linux/firewire.h, but IMO rather not to
uapi/linux/firewire-constants.h.  We don't want this definition to leak
out to userland; userland can hardwire its own hacks itself. :-)

This trivial move to a header should be deferred until the driver moves
out of staging.

> +2. MAX_ASYNC_PAYLOAD needs to be publicly exposed by core/ohci
> +   - otherwise how will this driver know the max size of address window to
> +     open for one packet write?

Hmm, don't firewire-sbp2 and firewire-net deal with this very problem
already on their own?  Firewire-sbp2 tells the target what maximum payload
the local node is ready to accept, and firewire-net figures out whether it
needs to fragment the datagrams in unicast TX depending on the remote
node's capabilities.

> +3. Maybe device_max_receive() and link_speed_to_max_payload() should be
> +     taken up by the firewire core?

Sounds like an extension of item 2, and is easier resolved after the
driver moves out of staging.

> +4. To avoid dropping rx data while still limiting the maximum buffering,
> +     the size of the AR context must be known. How to expose this to drivers?

I don't see a requirement to know the local or remote node's size of AR
DMA buffer.  Rather, keep the traffic throttled such that too frequent
ack-busy are avoided.  As far as I have learned from firewire-net, just
limiting the number or in-flight transactions should work reasonably.  Of
course, hardware ack-busy retries which the OHCI performs on its own won't
be noticed by software but take away from overall bus bandwidth.  IOW the
optimum queue depth is by a certain margin less than the depth at which
software starts to see ack-busy transaction terminations.

Furthermore:  In order to avoid dropping data, you don't need to know the
buffer size at all.  You need to implement proper software retries.  I.e.
either those which you noted yourself at the top of this TODO file, or you
let the userland on top of the TTY handle it if this is possible.  (I
don't know how the TTY subsystem and TTY based applications work.)

> +5. Explore if bigger AR context will reduce RCODE_BUSY responses
> +   (or auto-grow to certain max size -- but this would require major surgery
> +    as the current AR is contiguously mapped)

For the particular application "firewire-net", the present AR-Req DMA
buffer size has apparently been determined as suitable.  Of course if you
find a different optimum, that could certainly be changed.

I haven't looked closely, but I suppose your bandwidth requirement
concerns AR-Request DMA, not so much AR-Response DMA, right?
-- 
Stefan Richter
-=====-===-- =-== -==--
http://arcgraph.de/sr/

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 1/1] staging: fwserial: Add TTY-over-Firewire serial driver
  2012-11-12 23:33     ` Stefan Richter
@ 2012-11-12 23:51       ` Greg Kroah-Hartman
  2012-11-13 19:37         ` Peter Hurley
  2012-11-13 19:14       ` Peter Hurley
  1 sibling, 1 reply; 20+ messages in thread
From: Greg Kroah-Hartman @ 2012-11-12 23:51 UTC (permalink / raw)
  To: Stefan Richter
  Cc: Peter Hurley, devel, linux1394-devel, linux-kernel, Alan Cox,
	linux-serial

On Tue, Nov 13, 2012 at 12:33:38AM +0100, Stefan Richter wrote:
> On Nov 02 Peter Hurley wrote:
> > This patch provides the kernel driver for high-speed TTY
> > communication over the IEEE 1394 bus.
> > 
> > Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
> 
> Hi Greg,
> 
> you asked for an Ack.  Here it is:  I am OK with this getting added to the
> staging tree.

Thanks, I'll queue it up later this week when I return from my travels.

greg k-h

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 1/1] staging: fwserial: Add TTY-over-Firewire serial driver
  2012-11-12 23:33     ` Stefan Richter
  2012-11-12 23:51       ` Greg Kroah-Hartman
@ 2012-11-13 19:14       ` Peter Hurley
  2012-11-14  1:25         ` Stefan Richter
  1 sibling, 1 reply; 20+ messages in thread
From: Peter Hurley @ 2012-11-13 19:14 UTC (permalink / raw)
  To: Stefan Richter
  Cc: Greg Kroah-Hartman, Alan Cox, linux-kernel, devel,
	linux1394-devel, linux-serial

On Tue, 2012-11-13 at 00:33 +0100, Stefan Richter wrote:
> On Nov 02 Peter Hurley wrote:
> > This patch provides the kernel driver for high-speed TTY
> > communication over the IEEE 1394 bus.
> > 
> > Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
> 
> Hi Greg,
> 
> you asked for an Ack.  Here it is:  I am OK with this getting added to the
> staging tree.
> 
> (Sorry for the delay, I was ill and at the same time buried in work at the
> day job.)  Note, I only quickly scrolled through v1 and v2.  I will do an
> actual review when somebody asks to move this from staging into a proper
> mainline place.

Hi Stefan,

Thanks very much for looking over this submission. (I hope you're
feeling better.)

> Peter,
> below are some high-level comments to your TODOs.

> > --- /dev/null
> > +++ b/drivers/staging/fwserial/Kconfig
> > @@ -0,0 +1,9 @@
> > +config FIREWIRE_SERIAL
> > +       tristate "TTY over Firewire"
> > +       depends on FIREWIRE
> > +       help
> > +          This enables TTY over IEEE 1394, providing high-speed serial
> > +	  connectivity to cabled peers.
> > +
> > +	  To compile this driver as a module, say M here:  the module will
> > +	  be called firewire-serial.
> 
> This should mention that this is a Linux-only affair at this time:  It uses
> an ad-hoc defined protocol which no other platform is known to support for
> the time being.

Good point. I'll make this change.

> > --- /dev/null
> > +++ b/drivers/staging/fwserial/TODO
> > @@ -0,0 +1,37 @@
> > +TODOs
> > +-----
> > +1. Implement retries for RCODE_BUSY, RCODE_NO_ACK and RCODE_SEND_ERROR
> > +   - I/O is handled asynchronously which presents some issues when error
> > +     conditions occur.
> > +2. Implement _robust_ console on top of this. The existing prototype console
> > +   driver is not ready for the big leagues yet.
> > +3. Expose means of controlling attach/detach of peers via sysfs. Include
> > +   GUID-to-port matching/whitelist/blacklist.
> > +
> > +-- Issues with firewire stack --
> > +1. This driver uses the same unregistered vendor id that the firewire core does
> > +     (0xd00d1e). Perhaps this could be exposed as a define in
> > +     firewire-constants.h?
> 
> I guess there is no better OUI for this purpose.
> 
> The constant could be added to linux/firewire.h, but IMO rather not to
> uapi/linux/firewire-constants.h.  We don't want this definition to leak
> out to userland; userland can hardwire its own hacks itself. :-)
> 
> This trivial move to a header should be deferred until the driver moves
> out of staging.

You're right. I'll update the TODO.

And I should have noted in the TODO file itself that the issues noted
are for moving the driver out of staging.

> > +2. MAX_ASYNC_PAYLOAD needs to be publicly exposed by core/ohci
> > +   - otherwise how will this driver know the max size of address window to
> > +     open for one packet write?
> 
> Hmm, don't firewire-sbp2 and firewire-net deal with this very problem
> already on their own?  Firewire-sbp2 tells the target what maximum payload
> the local node is ready to accept, and firewire-net figures out whether it
> needs to fragment the datagrams in unicast TX depending on the remote
> node's capabilities.

I wasn't as clear as I should have been here. This is only about how big
to make the address handler window. Just like firewire-net, this driver
communicates with remotes at S100 speeds (and packet sizes) to
query the largest packet size that the remote supports.

Currently, firewire-net sets an arbitrary address handler length of
4096. This works because the largest AR packet size the current
firewire-ohci driver handles is 4096 (value of MAX_ASYNC_PAYLOAD) +
header/trailer. Note that firewire-ohci does not limit card->max_receive
to this value.

So if the ohci driver changes to handle 8K+ AR packets and the hardware
supports it, these address handler windows will be too small.

> > +3. Maybe device_max_receive() and link_speed_to_max_payload() should be
> > +     taken up by the firewire core?
> 
> Sounds like an extension of item 2, and is easier resolved after the
> driver moves out of staging.

Ok.

> > +4. To avoid dropping rx data while still limiting the maximum buffering,
> > +     the size of the AR context must be known. How to expose this to drivers?
> 
> I don't see a requirement to know the local or remote node's size of AR
> DMA buffer.  Rather, keep the traffic throttled such that too frequent
> ack-busy are avoided.  As far as I have learned from firewire-net, just
> limiting the number or in-flight transactions should work reasonably.  Of
> course, hardware ack-busy retries which the OHCI performs on its own won't
> be noticed by software but take away from overall bus bandwidth.  IOW the
> optimum queue depth is by a certain margin less than the depth at which
> software starts to see ack-busy transaction terminations.
> 
> Furthermore:  In order to avoid dropping data, you don't need to know the
> buffer size at all.  You need to implement proper software retries.  I.e.
> either those which you noted yourself at the top of this TODO file, or you
> let the userland on top of the TTY handle it if this is possible.  (I
> don't know how the TTY subsystem and TTY based applications work.)

I do need to implement retries but that will be of limited benefit to
this particular problem.

Data is written from the remote as a posted write into the AR buffer.
The remote receives hardware ack and continues writing more. Meanwhile,
the local node schedules the AR tasklet. The AR tasklet runs,
dispatching the packets to each appropriate handler. So, by the time
this driver's handler is delivered the first packet, many packets could
already have been acked and sitting in the AR buffer. At this point,
throttling the remote will only prevent the remote from sending even
more, and can't help with the data which has already been received.

The driver already limits the max in-flight transactions to 8. But since
they're unified transactions, that doesn't do much. Under heavy loads,
there may be many (small) completed packets in the AR buffer (NB: the
driver already does write aggregation but this isn't appropriate in the
console driver).

Besides the inefficiency of re-sending data that has already been
received, retrying may not be possible. Consider when a console driver
(work-in-progress) writes to the remote using the same interface. It
could be one of the last things the remote does. And the most crucial
information is that last write.

Despite my lengthy description, I agree that knowing the size of the AR
context is not a requirement, and we can drop this as an issue. As you
suggest, I can determine the appropriate buffer size experimentally (and
implement retries).

> > +5. Explore if bigger AR context will reduce RCODE_BUSY responses
> > +   (or auto-grow to certain max size -- but this would require major surgery
> > +    as the current AR is contiguously mapped)
> 
> For the particular application "firewire-net", the present AR-Req DMA
> buffer size has apparently been determined as suitable.  Of course if you
> find a different optimum, that could certainly be changed.
> 
> I haven't looked closely, but I suppose your bandwidth requirement
> concerns AR-Request DMA, not so much AR-Response DMA, right?

Exactly. And I was just thinking to myself there which unfortunately
slipped into a public document :)

Thanks again for your time, Stefan.

Regards,
Peter Hurley



^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 1/1] staging: fwserial: Add TTY-over-Firewire serial driver
  2012-11-12 23:51       ` Greg Kroah-Hartman
@ 2012-11-13 19:37         ` Peter Hurley
  2012-11-13 19:47           ` Greg Kroah-Hartman
  0 siblings, 1 reply; 20+ messages in thread
From: Peter Hurley @ 2012-11-13 19:37 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Stefan Richter, devel, linux1394-devel, linux-kernel, Alan Cox,
	linux-serial

On Mon, 2012-11-12 at 15:51 -0800, Greg Kroah-Hartman wrote:
> On Tue, Nov 13, 2012 at 12:33:38AM +0100, Stefan Richter wrote:
> > On Nov 02 Peter Hurley wrote:
> > > This patch provides the kernel driver for high-speed TTY
> > > communication over the IEEE 1394 bus.
> > > 
> > > Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
> > 
> > Hi Greg,
> > 
> > you asked for an Ack.  Here it is:  I am OK with this getting added to the
> > staging tree.
> 
> Thanks, I'll queue it up later this week when I return from my travels.

Thanks, Greg.

Except for your and Alan's earlier concerns, which I think I addressed,
I didn't get any other feedback on the TTY side of things. Are you
personally reviewing this submission?

Regards,
Peter Hurley



^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 1/1] staging: fwserial: Add TTY-over-Firewire serial driver
  2012-11-13 19:37         ` Peter Hurley
@ 2012-11-13 19:47           ` Greg Kroah-Hartman
  0 siblings, 0 replies; 20+ messages in thread
From: Greg Kroah-Hartman @ 2012-11-13 19:47 UTC (permalink / raw)
  To: Peter Hurley
  Cc: Stefan Richter, devel, linux1394-devel, linux-kernel, Alan Cox,
	linux-serial

On Tue, Nov 13, 2012 at 02:37:24PM -0500, Peter Hurley wrote:
> On Mon, 2012-11-12 at 15:51 -0800, Greg Kroah-Hartman wrote:
> > On Tue, Nov 13, 2012 at 12:33:38AM +0100, Stefan Richter wrote:
> > > On Nov 02 Peter Hurley wrote:
> > > > This patch provides the kernel driver for high-speed TTY
> > > > communication over the IEEE 1394 bus.
> > > > 
> > > > Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
> > > 
> > > Hi Greg,
> > > 
> > > you asked for an Ack.  Here it is:  I am OK with this getting added to the
> > > staging tree.
> > 
> > Thanks, I'll queue it up later this week when I return from my travels.
> 
> Thanks, Greg.
> 
> Except for your and Alan's earlier concerns, which I think I addressed,
> I didn't get any other feedback on the TTY side of things. Are you
> personally reviewing this submission?

I will poke at it, odds are it will need some changes as there has been
some tty changes in the tty.git tree that might need to also be done
here.  Give me a bit of time to catch up...

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 1/1] staging: fwserial: Add TTY-over-Firewire serial driver
  2012-11-13 19:14       ` Peter Hurley
@ 2012-11-14  1:25         ` Stefan Richter
  2012-11-27 18:33           ` Peter Hurley
  0 siblings, 1 reply; 20+ messages in thread
From: Stefan Richter @ 2012-11-14  1:25 UTC (permalink / raw)
  To: Peter Hurley
  Cc: Greg Kroah-Hartman, Alan Cox, linux-kernel, devel,
	linux1394-devel, linux-serial

On Nov 13 Peter Hurley wrote:
> On Tue, 2012-11-13 at 00:33 +0100, Stefan Richter wrote:
> > On Nov 02 Peter Hurley wrote:
> > > +2. MAX_ASYNC_PAYLOAD needs to be publicly exposed by core/ohci
> > > +   - otherwise how will this driver know the max size of address window to
> > > +     open for one packet write?
> > 
> > Hmm, don't firewire-sbp2 and firewire-net deal with this very problem
> > already on their own?  Firewire-sbp2 tells the target what maximum payload
> > the local node is ready to accept, and firewire-net figures out whether it
> > needs to fragment the datagrams in unicast TX depending on the remote
> > node's capabilities.
> 
> I wasn't as clear as I should have been here. This is only about how big
> to make the address handler window.

Isn't fw_card.max_receive what you were looking for?  But since the
current firewire-core API requires you to allocate one handler address and
size for all cards --- even for cards which will be hot-added later while
your driver is already running ---, you should surely just allocate
4096 bytes; see below.

> Just like firewire-net, this driver
> communicates with remotes at S100 speeds (and packet sizes) to
> query the largest packet size that the remote supports.

(RFC 2734 additionally keeps things sane for itself by requiring all 2734
compliant nodes to support at least max packet = 512 bytes.)

Could it be easier to take this information from fw_device.max_speed and
the max_rec in fw_device.config_rom?  RFC 2734 was designed to work
without a local node management layer which is capable to gather such
information, but we have this information on Linux (at the time when
core_device.c finished its node discovery).

> Currently, firewire-net sets an arbitrary address handler length of
> 4096. This works because the largest AR packet size the current
> firewire-ohci driver handles is 4096 (value of MAX_ASYNC_PAYLOAD) +
> header/trailer. Note that firewire-ohci does not limit card->max_receive
> to this value.
> 
> So if the ohci driver changes to handle 8K+ AR packets and the hardware
> supports it, these address handler windows will be too small.

While the IEEE 1394:2008 link layer specification (section 6) provides for
asynchronous packet payloads of up to 16384 bytes (table 6-4), the IEEE
1394 beta mode port specification (section 13) only allows up to 4096
bytes (table 16-18).  And alpha mode is of course limited to 2048 bytes.

So, asynchronous packet payloads greater than 4096 bytes are out of scope
of the current revision of IEEE 1394.

> > > +3. Maybe device_max_receive() and link_speed_to_max_payload() should be
> > > +     taken up by the firewire core?
> > 
> > Sounds like an extension of item 2, and is easier resolved after the
> > driver moves out of staging.
> 
> Ok.
> 
> > > +4. To avoid dropping rx data while still limiting the maximum buffering,
> > > +     the size of the AR context must be known. How to expose this to drivers?
> > 
> > I don't see a requirement to know the local or remote node's size of AR
> > DMA buffer.  Rather, keep the traffic throttled such that too frequent
> > ack-busy are avoided.
[...]
> I do need to implement retries but that will be of limited benefit to
> this particular problem.
> 
> Data is written from the remote as a posted write into the AR buffer.
[...]

Oh, I forgot about posted writes.  However, you only lose data with posted
writes if the OHCI somehow fails to access the host bus.  Merely getting
to the end of the DMA program before all of the PCI posting FIFO could be
emptied is not a reason to drop data.  See OHCI-1394 section 3.3 which
speaks about the possibility to drop selfID/ IR/ AR-resp data if out of
buffer, but not AR-req data.

Well, maybe there are bad OHCI implementations which drop posted AR-req
data...?  I don't know.

firewire-net uses posted writes too.  But with IP-over-1394, delivery
guarantee is handled further above if needed, not by the 1394
encapsulation.  firewire-sbp2 also uses posted writes, in two ways:  With
physical DMA to SCSI READ command data buffers, and for the SBP status
FIFO.  But physical DMA does not have a backing DMA program, and data loss
during a status FIFO write would likely be covered by a SCSI transaction
retry.

[...]
> Besides the inefficiency of re-sending data that has already been
> received, retrying may not be possible. Consider when a console driver
> (work-in-progress) writes to the remote using the same interface. It
> could be one of the last things the remote does. And the most crucial
> information is that last write.

As mentioned, I have no idea how TTY works or is supposed to work.  But
even if the sending application is allowed to quit before its last
outbound datagram was delivered, fwserial could still be made to deliver
this last datagram at its own pace, with as many software retries as
prudent.
-- 
Stefan Richter
-=====-===-- =-== -===-
http://arcgraph.de/sr/

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 1/1] staging: fwserial: Add TTY-over-Firewire serial driver
  2012-11-14  1:25         ` Stefan Richter
@ 2012-11-27 18:33           ` Peter Hurley
  2012-11-27 23:58             ` Stefan Richter
  0 siblings, 1 reply; 20+ messages in thread
From: Peter Hurley @ 2012-11-27 18:33 UTC (permalink / raw)
  To: Stefan Richter
  Cc: Greg Kroah-Hartman, Alan Cox, linux-kernel, devel,
	linux1394-devel, linux-serial

On Wed, 2012-11-14 at 02:25 +0100, Stefan Richter wrote:
> On Nov 13 Peter Hurley wrote:
> > On Tue, 2012-11-13 at 00:33 +0100, Stefan Richter wrote:
> > > On Nov 02 Peter Hurley wrote:
> > > > +2. MAX_ASYNC_PAYLOAD needs to be publicly exposed by core/ohci
> > > > +   - otherwise how will this driver know the max size of address window to
> > > > +     open for one packet write?
> > > 
> > > Hmm, don't firewire-sbp2 and firewire-net deal with this very problem
> > > already on their own?  Firewire-sbp2 tells the target what maximum payload
> > > the local node is ready to accept, and firewire-net figures out whether it
> > > needs to fragment the datagrams in unicast TX depending on the remote
> > > node's capabilities.
> > 
> > I wasn't as clear as I should have been here. This is only about how big
> > to make the address handler window.
> 
> Isn't fw_card.max_receive what you were looking for?

Yes, you're right, this is what I should do.

> But since the
> current firewire-core API requires you to allocate one handler address and
> size for all cards --- even for cards which will be hot-added later while
> your driver is already running ---, you should surely just allocate
> 4096 bytes; see below.

This driver uses a 2-stage negotiation scheme.

A small address window is allocated for all cards in the unit address
space. This address window is known to all peers because it is
advertised in the unit device config rom for all cards. The associated
handler handles requests for attach/detach from peers.

Then, when a local node is probed, the larger status+fifo address
windows are allocated (one address window for each tty port). Thus each
attached peer will get a dedicated address window.

The peer learns which status+fifo window it has been assigned
(corresponding to a particular tty port) while negotiating the attach.

Currently, the larger windows are allocated before knowledge of which
peer (or indeed any peer) might attempt to attach.

I will need to delay this allocation until during the attach
negotiation.

But if it's ok with you, maybe I can put this off for right now and just
note it in the TODO?

> > Just like firewire-net, this driver
> > communicates with remotes at S100 speeds (and packet sizes) to
> > query the largest packet size that the remote supports.
> 
> (RFC 2734 additionally keeps things sane for itself by requiring all 2734
> compliant nodes to support at least max packet = 512 bytes.)
> 
> Could it be easier to take this information from fw_device.max_speed and
> the max_rec in fw_device.config_rom?  RFC 2734 was designed to work
> without a local node management layer which is capable to gather such
> information, but we have this information on Linux (at the time when
> core_device.c finished its node discovery).

I see your point and I hadn't considered that. That said, the
attach/detach negiotiation doesn't need optimizing, so I'm comfortable
leaving this as is.

> > Currently, firewire-net sets an arbitrary address handler length of
> > 4096. This works because the largest AR packet size the current
> > firewire-ohci driver handles is 4096 (value of MAX_ASYNC_PAYLOAD) +
> > header/trailer. Note that firewire-ohci does not limit card->max_receive
> > to this value.
> > 
> > So if the ohci driver changes to handle 8K+ AR packets and the hardware
> > supports it, these address handler windows will be too small.
> 
> While the IEEE 1394:2008 link layer specification (section 6) provides for
> asynchronous packet payloads of up to 16384 bytes (table 6-4), the IEEE
> 1394 beta mode port specification (section 13) only allows up to 4096
> bytes (table 16-18).  And alpha mode is of course limited to 2048 bytes.
> 
> So, asynchronous packet payloads greater than 4096 bytes are out of scope
> of the current revision of IEEE 1394.

You should look at this 1394ta.org video
http://www.youtube.com/watch?v=xVXNvXHNQTY of DAP Technologies S1600
OHCI controllers running S1600 cameras using beta cables. (I apologize
for the youtube link but that's where they hosted it :\)

> > > > +4. To avoid dropping rx data while still limiting the maximum buffering,
> > > > +     the size of the AR context must be known. How to expose this to drivers?
> > > 
> > > I don't see a requirement to know the local or remote node's size of AR
> > > DMA buffer.  Rather, keep the traffic throttled such that too frequent
> > > ack-busy are avoided.
> [...]
> > I do need to implement retries but that will be of limited benefit to
> > this particular problem.
> > 
> > Data is written from the remote as a posted write into the AR buffer.
> [...]
> 
> Oh, I forgot about posted writes.  However, you only lose data with posted
> writes if the OHCI somehow fails to access the host bus.  Merely getting
> to the end of the DMA program before all of the PCI posting FIFO could be
> emptied is not a reason to drop data.  See OHCI-1394 section 3.3 which
> speaks about the possibility to drop selfID/ IR/ AR-resp data if out of
> buffer, but not AR-req data.
> 
> Well, maybe there are bad OHCI implementations which drop posted AR-req
> data...?  I don't know.

When I refer to dropping rx data here, I mean this sequence for
receiving a posted write:

handle_ar_packet
  fw_core_handle_request
    handle_exclusive_region_request
      handler->address_callback() => fwtty_port_handler(WRITE_BLOCK_REQUEST)
        ** tty buffer space is full so can't copy the data out of AR buffer **

That data will be dropped and need to be retransmitted. But see below.

> firewire-net uses posted writes too.  But with IP-over-1394, delivery
> guarantee is handled further above if needed, not by the 1394
> encapsulation.  firewire-sbp2 also uses posted writes, in two ways:  With
> physical DMA to SCSI READ command data buffers, and for the SBP status
> FIFO.  But physical DMA does not have a backing DMA program, and data loss
> during a status FIFO write would likely be covered by a SCSI transaction
> retry.

see below.

> [...]
> > Besides the inefficiency of re-sending data that has already been
> > received, retrying may not be possible. Consider when a console driver
> > (work-in-progress) writes to the remote using the same interface. It
> > could be one of the last things the remote does. And the most crucial
> > information is that last write.
> 
> As mentioned, I have no idea how TTY works or is supposed to work.  But
> even if the sending application is allowed to quit before its last
> outbound datagram was delivered, fwserial could still be made to deliver
> this last datagram at its own pace, with as many software retries as
> prudent.

Among the many uses for fast tty is to receive the output from a remote
console driver (where printk's go). The last printk coming from the
remote will be the most important as it may contain a vital clue as to
why the remote host died. (Note: most console drivers will busy-wait the
current cpu until the transmission completes or it times out).

That's why I'd like to only be forced to software retry if absolutely
necessary. To my mind, software retrying is partly avoidable if the
receiver can ensure enough tty buffer space is available at any given
time. To provide this guarantee, this driver reserves a buffer as large
as the AR-req buffer (initially I had wanted to simply use a watermark
within the existing tty buffers but that idea was a non-starter). This
way the receiver can throttle the remote but still receive all the data
the remote may already have sent that is already in the AR-req buffer.

Does that make sense?

Regards,
Peter Hurley


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 1/1] staging: fwserial: Add TTY-over-Firewire serial driver
  2012-11-27 18:33           ` Peter Hurley
@ 2012-11-27 23:58             ` Stefan Richter
  2012-11-28  1:00               ` Peter Hurley
  0 siblings, 1 reply; 20+ messages in thread
From: Stefan Richter @ 2012-11-27 23:58 UTC (permalink / raw)
  To: Peter Hurley
  Cc: Greg Kroah-Hartman, Alan Cox, linux-kernel, devel,
	linux1394-devel, linux-serial

On Nov 27 Peter Hurley wrote:
> > > Currently, firewire-net sets an arbitrary address handler length of
> > > 4096. This works because the largest AR packet size the current
> > > firewire-ohci driver handles is 4096 (value of MAX_ASYNC_PAYLOAD) +
> > > header/trailer. Note that firewire-ohci does not limit card->max_receive
> > > to this value.
> > > 
> > > So if the ohci driver changes to handle 8K+ AR packets and the hardware
> > > supports it, these address handler windows will be too small.  
> > 
> > While the IEEE 1394:2008 link layer specification (section 6) provides for
> > asynchronous packet payloads of up to 16384 bytes (table 6-4), the IEEE
> > 1394 beta mode port specification (section 13) only allows up to 4096
> > bytes (table 16-18).  And alpha mode is of course limited to 2048 bytes.
> > 
> > So, asynchronous packet payloads greater than 4096 bytes are out of scope
> > of the current revision of IEEE 1394.  
> 
> You should look at this 1394ta.org video
> http://www.youtube.com/watch?v=xVXNvXHNQTY of DAP Technologies S1600
> OHCI controllers running S1600 cameras using beta cables.

I don't know the details of their implementation, but I suppose they conform
with the 1394 beta mode port specification.  Which in turn means that their
S1600 solution (and by extrapolation, their S3200 prototypes) comply with a
maximum asynchronous packet payload of 4096 bytes.  Citing IEEE 1394-2008:

>>>
	Table 16-18———Maximum payload size for Beta data packets
 Data rate | Maximum asynchronous payload size | Maximum isochronous payload
           |              (bytes)              |           (bytes)
 ----------+-----------------------------------+----------------------------
    S100   |                 512               |             1024
    S200   |                1024               |             2048
    S400   |                2048               |             4096
    S800   |                4096               |             8192
    S1600  |                4096               |            16384
    S3200  |                4096               |            32768
<<<

(Alpha mode payload limits are the same as the S100...S400 subset of beta mode.
In IEEE 1394b-2002, the table number is 16-3.)

You can of course define registers (or better termed: buffers) which are larger
than what can be atomically read or written, or atomically compared-swapped;
IOW which are larger than what can be accessed in a single transaction, if such
registers or buffers are useful.  But if you particularly need a register which
is just large enough to accommodate the largest possible inbound block write
transaction which complies with IEEE 1394, and you don't know the peer's
capability and the speeds of all intermediary cable hops, then
fw_card.max_receive is the number that you need.  Or you ignore the cards actual
capability and just allocate 4096 bytes.

OHCIs that you can buy offer fw_card.max_receive of 1024, or 2048, or 4096 bytes.
1024 bytes is the limit of many but not all 1394a S400 CardBus cards.

[Issues of transaction retries and possible loss at session termination to be
left to another reply at another time.]
-- 
Stefan Richter
-=====-===-- =-== ==-==
http://arcgraph.de/sr/

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 1/1] staging: fwserial: Add TTY-over-Firewire serial driver
  2012-11-27 23:58             ` Stefan Richter
@ 2012-11-28  1:00               ` Peter Hurley
  0 siblings, 0 replies; 20+ messages in thread
From: Peter Hurley @ 2012-11-28  1:00 UTC (permalink / raw)
  To: Stefan Richter
  Cc: Greg Kroah-Hartman, Alan Cox, linux-kernel, devel,
	linux1394-devel, linux-serial

On Wed, 2012-11-28 at 00:58 +0100, Stefan Richter wrote:
> On Nov 27 Peter Hurley wrote:
> > > > Currently, firewire-net sets an arbitrary address handler length of
> > > > 4096. This works because the largest AR packet size the current
> > > > firewire-ohci driver handles is 4096 (value of MAX_ASYNC_PAYLOAD) +
> > > > header/trailer. Note that firewire-ohci does not limit card->max_receive
> > > > to this value.
> > > > 
> > > > So if the ohci driver changes to handle 8K+ AR packets and the hardware
> > > > supports it, these address handler windows will be too small.  
> > > 
> > > While the IEEE 1394:2008 link layer specification (section 6) provides for
> > > asynchronous packet payloads of up to 16384 bytes (table 6-4), the IEEE
> > > 1394 beta mode port specification (section 13) only allows up to 4096
> > > bytes (table 16-18).  And alpha mode is of course limited to 2048 bytes.
> > > 
> > > So, asynchronous packet payloads greater than 4096 bytes are out of scope
> > > of the current revision of IEEE 1394.  
> > 
> > You should look at this 1394ta.org video
> > http://www.youtube.com/watch?v=xVXNvXHNQTY of DAP Technologies S1600
> > OHCI controllers running S1600 cameras using beta cables.
> 
> I don't know the details of their implementation, but I suppose they conform
> with the 1394 beta mode port specification.  Which in turn means that their
> S1600 solution (and by extrapolation, their S3200 prototypes) comply with a
> maximum asynchronous packet payload of 4096 bytes.  Citing IEEE 1394-2008:
> 
> >>>
> 	Table 16-18———Maximum payload size for Beta data packets
>  Data rate | Maximum asynchronous payload size | Maximum isochronous payload
>            |              (bytes)              |           (bytes)
>  ----------+-----------------------------------+----------------------------
>     S100   |                 512               |             1024
>     S200   |                1024               |             2048
>     S400   |                2048               |             4096
>     S800   |                4096               |             8192
>     S1600  |                4096               |            16384
>     S3200  |                4096               |            32768
> <<<
> 
> (Alpha mode payload limits are the same as the S100...S400 subset of beta mode.
> In IEEE 1394b-2002, the table number is 16-3.)
> 
> You can of course define registers (or better termed: buffers) which are larger
> than what can be atomically read or written, or atomically compared-swapped;
> IOW which are larger than what can be accessed in a single transaction, if such
> registers or buffers are useful.  But if you particularly need a register which
> is just large enough to accommodate the largest possible inbound block write
> transaction which complies with IEEE 1394, and you don't know the peer's
> capability and the speeds of all intermediary cable hops, then
> fw_card.max_receive is the number that you need.  Or you ignore the cards actual
> capability and just allocate 4096 bytes.

Thanks for the clarification. I need to update
link_speed_to_max_payload() now. ;)

Plus I should just renew my IEEE membership so I can get the 1394-2008
spec without having to saw my arm off.


^ permalink raw reply	[flat|nested] 20+ messages in thread

end of thread, other threads:[~2012-11-28  1:00 UTC | newest]

Thread overview: 20+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2012-10-18 12:56 [PATCH 0/1] staging: Add firewire-serial driver Peter Hurley
2012-10-22 22:45 ` Greg Kroah-Hartman
2012-10-23  2:34   ` Peter Hurley
2012-10-23  3:15     ` Greg Kroah-Hartman
2012-10-23  9:51     ` Alan Cox
2012-10-23 16:30       ` Peter Hurley
2012-10-23 18:41         ` Stefan Richter
2012-10-24 13:41   ` Stefan Richter
2012-10-24 15:56     ` Peter Hurley
2012-11-02 12:16 ` [PATCH v2 " Peter Hurley
2012-11-02 12:16   ` [PATCH v2 1/1] staging: fwserial: Add TTY-over-Firewire serial driver Peter Hurley
2012-11-12 23:33     ` Stefan Richter
2012-11-12 23:51       ` Greg Kroah-Hartman
2012-11-13 19:37         ` Peter Hurley
2012-11-13 19:47           ` Greg Kroah-Hartman
2012-11-13 19:14       ` Peter Hurley
2012-11-14  1:25         ` Stefan Richter
2012-11-27 18:33           ` Peter Hurley
2012-11-27 23:58             ` Stefan Richter
2012-11-28  1:00               ` Peter Hurley

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).