Commit e5711071 authored by Peter Hurley's avatar Peter Hurley Committed by Greg Kroah-Hartman

staging: fwserial: Add TTY-over-Firewire serial driver

This patch provides the kernel driver for high-speed TTY
communication over the IEEE 1394 bus.
Signed-off-by: default avatarPeter Hurley <peter@hurleysoftware.com>
Acked-by: default avatarStefan Richter <stefanr@s5r6.in-berlin.de>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 792660d5
...@@ -140,4 +140,6 @@ source "drivers/staging/imx-drm/Kconfig" ...@@ -140,4 +140,6 @@ source "drivers/staging/imx-drm/Kconfig"
source "drivers/staging/dgrp/Kconfig" source "drivers/staging/dgrp/Kconfig"
source "drivers/staging/fwserial/Kconfig"
endif # STAGING endif # STAGING
...@@ -62,3 +62,4 @@ obj-$(CONFIG_NET_VENDOR_SILICOM) += silicom/ ...@@ -62,3 +62,4 @@ obj-$(CONFIG_NET_VENDOR_SILICOM) += silicom/
obj-$(CONFIG_CED1401) += ced1401/ obj-$(CONFIG_CED1401) += ced1401/
obj-$(CONFIG_DRM_IMX) += imx-drm/ obj-$(CONFIG_DRM_IMX) += imx-drm/
obj-$(CONFIG_DGRP) += dgrp/ obj-$(CONFIG_DGRP) += dgrp/
obj-$(CONFIG_FIREWIRE_SERIAL) += fwserial/
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.
obj-$(CONFIG_FIREWIRE_SERIAL) += firewire-serial.o
firewire-serial-objs := fwserial.o dma_fifo.o
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().
/*
* 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;
}
/*
* 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_ */
/*
* 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%.");
#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 */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment