diff mbox series

[net-next,2/2] appletalk: tashtalk: Add LocalTalk line discipline driver for AppleTalk using a TashTalk adapter

Message ID 20240817093316.9239-1-rwz@xhero.org
State New
Headers show
Series [net-next,1/2] tty: Add N_TASHTALK line discipline for TashTalk Localtalk serial driver | expand

Commit Message

Rodolfo Zitellini Aug. 17, 2024, 9:33 a.m. UTC
This is the TashTalk driver, it perits for a modern machine to
participate in a Apple LocalTalk network and is compatibile with
Netatalk.

Please see the included documentation for details:
Documentation/networking/device_drivers/appletalk/index.rst

Signed-off-by: Rodolfo Zitellini <rwz@xhero.org>

---
 .../device_drivers/appletalk/index.rst        |   18 +
 .../device_drivers/appletalk/tashtalk.rst     |  139 +++
 .../networking/device_drivers/index.rst       |    1 +
 MAINTAINERS                                   |    7 +
 drivers/net/Kconfig                           |    2 +
 drivers/net/Makefile                          |    1 +
 drivers/net/appletalk/Kconfig                 |   33 +
 drivers/net/appletalk/Makefile                |    6 +
 drivers/net/appletalk/tashtalk.c              | 1003 +++++++++++++++++
 9 files changed, 1210 insertions(+)
 create mode 100644 Documentation/networking/device_drivers/appletalk/index.rst
 create mode 100644 Documentation/networking/device_drivers/appletalk/tashtalk.rst
 create mode 100644 drivers/net/appletalk/Kconfig
 create mode 100644 drivers/net/appletalk/Makefile
 create mode 100644 drivers/net/appletalk/tashtalk.c

Comments

Arnd Bergmann Aug. 19, 2024, 9:44 a.m. UTC | #1
On Sat, Aug 17, 2024, at 11:33, Rodolfo Zitellini wrote:
> This is the TashTalk driver, it perits for a modern machine to
> participate in a Apple LocalTalk network and is compatibile with
> Netatalk.
>
> Please see the included documentation for details:
> Documentation/networking/device_drivers/appletalk/index.rst
>
> Signed-off-by: Rodolfo Zitellini <rwz@xhero.org>

Hi Rodolfo,

Nice to see you got this into a working state! I vaguely
remember discussing this in the past, and suggesting you
try a user space solution, so it would be good if you can
add in the patch description why you ended up with a kernel
driver after all.

My main concern at this point is the usage of .ndo_do_ioctl.
I had previously sent patches to completely remove that
from the kernel, but never got around to send a new version
after the previous review. I still have them in my tree
and should be able to send them again, but that will obviously
conflict with your added use.

> +static struct net_device **tashtalk_devs;
> +
> +static int tash_maxdev = TASH_MAX_CHAN;
> +module_param(tash_maxdev, int, 0);
> +MODULE_PARM_DESC(tash_maxdev, "Maximum number of tashtalk devices");

You should not need to keep a list of the devices
or a module parameter to limit the number. I'm fairly sure
the devices are already tracked by the network stack in a
way that lets you enumerate them later.

> +static void tashtalk_send_ctrl_packet(struct tashtalk *tt, unsigned 
> char dst,
> +				      unsigned char src, unsigned char type);
> +
> +static unsigned char tt_arbitrate_addr_blocking(struct tashtalk *tt, 
> unsigned char addr);

Please try to avoid forward declations and instead reorder the
functions to put the callers after the calles.

> +static void tash_setbits(struct tashtalk *tt, unsigned char addr)
> +{
> +	unsigned char bits[33];
> +	unsigned int byte, pos;
> +
> +	/* 0, 255 and anything else are invalid */
> +	if (addr == 0 || addr >= 255)
> +		return;
> +
> +	memset(bits, 0, sizeof(bits));
> +
> +	/* in theory we can respond to many addresses */
> +	byte = addr / 8 + 1; /* skip initial command byte */
> +	pos = (addr % 8);
> +	bits[byte] = (1 << pos);

This is basically set_bit_le(), so you could use that
for clarity and use an array of 'unsigned long' words.

> +	set_bit(TTY_DO_WRITE_WAKEUP, &tt->tty->flags);
> +	tt->tty->ops->write(tt->tty, bits, sizeof(bits));
> +}

> +
> +static u16 tt_crc_ccitt_update(u16 crc, u8 data)
> +{
> +	data ^= (u8)(crc) & (u8)(0xFF);
> +	data ^= data << 4;
> +	return ((((u16)data << 8) | ((crc & 0xFF00) >> 8)) ^ (u8)(data >> 4) ^
> +		((u16)data << 3));
> +}

Can you use the global crc_ccitt() function instead of implementing
your own?

> +static int tt_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
> +{
> +	struct sockaddr_at *sa = (struct sockaddr_at *)&ifr->ifr_addr;
> +	struct tashtalk *tt = netdev_priv(dev);
> +	struct atalk_addr *aa = &tt->node_addr;
> +
> +	switch (cmd) {
> +	case SIOCSIFADDR:
> +
> +		sa->sat_addr.s_node =
> +			tt_arbitrate_addr_blocking(tt, sa->sat_addr.s_node);
> +
> +		aa->s_net = sa->sat_addr.s_net;
> +		aa->s_node = sa->sat_addr.s_node;
> +
> +		/* Set broadcast address. */
> +		dev->broadcast[0] = 0xFF;
> +
> +		/* Set hardware address. */
> +		dev->addr_len = 1;
> +		dev_addr_set(dev, &aa->s_node);
> +
> +		/* Setup tashtalk to respond to that addr */
> +		tash_setbits(tt, aa->s_node);
> +
> +		return 0;
> +
> +	case SIOCGIFADDR:
> +		sa->sat_addr.s_net = aa->s_net;
> +		sa->sat_addr.s_node = aa->s_node;
> +
> +		return 0;

As we discussed in the past, I think this really should
not use ndo_do_ioctl(), which instead should just disappear.

Please change the caller to use some other method of
setting the address in the driver.

> +static int tashtalk_ioctl(struct tty_struct *tty, unsigned int cmd,
> +			  unsigned long arg)
> +{
> +	struct tashtalk *tt = tty->disc_data;
> +	int __user *p = (int __user *)arg;
> +	unsigned int tmp;
> +
> +	/* First make sure we're connected. */
> +	if (!tt || tt->magic != TASH_MAGIC)
> +		return -EINVAL;
> +
> +	switch (cmd) {
> +	case SIOCGIFNAME:
> +		tmp = strlen(tt->dev->name) + 1;
> +		if (copy_to_user((void __user *)arg, tt->dev->name, tmp))
> +			return -EFAULT;
> +		return 0;
> +
> +	case SIOCGIFENCAP:
> +		if (put_user(tt->mode, p))
> +			return -EFAULT;
> +		return 0;
> +
> +	case SIOCSIFENCAP:
> +		if (get_user(tmp, p))
> +			return -EFAULT;
> +		tt->mode = tmp;
> +		return 0;
> +
> +	case SIOCSIFHWADDR:
> +		return -EINVAL;
> +
> +	default:
> +		return tty_mode_ioctl(tty, cmd, arg);
> +	}

I'm also not a bit fan of using the SIOC* command codes
in a tty device with incompatible argument types. I do
see that slip and x25 do the same, but it would be nice
to find a better interface for these. I have not looked
at all the other line disciplines, but maybe you can
find a better example to copy.

     Arnd
Simon Horman Aug. 19, 2024, 9:47 a.m. UTC | #2
On Sat, Aug 17, 2024 at 11:33:16AM +0200, Rodolfo Zitellini wrote:
> This is the TashTalk driver, it perits for a modern machine to
> participate in a Apple LocalTalk network and is compatibile with

nit: compatible

     Flagged by checkpatch.pl --codespell

> Netatalk.
> 
> Please see the included documentation for details:
> Documentation/networking/device_drivers/appletalk/index.rst
> 
> Signed-off-by: Rodolfo Zitellini <rwz@xhero.org>

...

> diff --git a/drivers/net/appletalk/tashtalk.c b/drivers/net/appletalk/tashtalk.c

...

> +/* Called by the driver when there's room for more data.
> + * Schedule the transmit.
> + */
> +static void tashtalk_write_wakeup(struct tty_struct *tty)
> +{
> +	struct tashtalk *tt;

I think that tt needs an __rcu annotation. Sparse says:

.../tashtalk.c:290:14: error: incompatible types in comparison expression (different address spaces):
.../tashtalk.c:290:14:    void [noderef] __rcu *
.../tashtalk.c:290:14:    void *

> +
> +	rcu_read_lock();
> +	tt = rcu_dereference(tty->disc_data);
> +	if (tt)
> +		schedule_work(&tt->tx_work);
> +	rcu_read_unlock();
> +}

...

> +static void tashtalk_send_ctrl_packet(struct tashtalk *tt, unsigned char dst,
> +				      unsigned char src, unsigned char type)
> +{
> +	unsigned char cmd = TT_CMD_TX;
> +	unsigned char buf[5];
> +	int actual;
> +	u16 crc;
> +
> +	buf[LLAP_DST_POS] = dst;
> +	buf[LLAP_SRC_POS] = src;
> +	buf[LLAP_TYP_POS] = type;
> +
> +	crc = tash_crc(buf, 3);
> +	buf[3] = ~(crc & 0xFF);
> +	buf[4] = ~(crc >> 8);
> +
> +	actual = tt->tty->ops->write(tt->tty, &cmd, 1);
> +	actual += tt->tty->ops->write(tt->tty, buf, sizeof(buf));
> +}

actual is set but otherwise unused in this function.
Should it be used as part of checking, with an error code returned on error?
If not, it should probably be removed.

Flagged by W=1 builds.

...

> +static void tashtalk_close(struct tty_struct *tty)
> +{
> +	struct tashtalk *tt = tty->disc_data;
> +
> +	/* First make sure we're connected. */
> +	if (!tt || tt->magic != TASH_MAGIC || tt->tty != tty)
> +		return;
> +
> +	spin_lock_bh(&tt->lock);
> +	rcu_assign_pointer(tty->disc_data, NULL);

I think tty->disc_data also needs an __rcu annotation, which will
likely highlight the need for annotations elsewhere. Flagged by Sparse.

> +	tt->tty = NULL;
> +	spin_unlock_bh(&tt->lock);
> +
> +	synchronize_rcu();
> +	flush_work(&tt->tx_work);
> +
> +	/* Flush network side */
> +	unregister_netdev(tt->dev);
> +	/* This will complete via tt_free_netdev */
> +}

...
Jiri Slaby (SUSE) Aug. 19, 2024, 9:55 a.m. UTC | #3
On 17. 08. 24, 11:33, Rodolfo Zitellini wrote:
> --- /dev/null
> +++ b/drivers/net/appletalk/tashtalk.c
> @@ -0,0 +1,1003 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +/*      tashtalk.c: TashTalk LocalTalk driver for Linux.
> + *
> + *	Authors:
> + *      Rodolfo Zitellini (twelvetone12)
> + *
> + *      Derived from:
> + *      - slip.c: A network driver outline for linux.
> + *        written by Laurence Culhane and Fred N. van Kempen
> + *
> + *      This software may be used and distributed according to the terms
> + *      of the GNU General Public License, incorporated herein by reference.
> + *
> + */
> +
> +#include <linux/compat.h>

What is this used for?

> +#include <linux/module.h>
> +#include <linux/moduleparam.h>
> +#include <linux/version.h>
> +
> +#include <linux/uaccess.h>
> +#include <linux/bitops.h>
> +#include <linux/sched/signal.h>
> +#include <linux/string.h>
> +#include <linux/mm.h>
> +#include <linux/interrupt.h>
> +#include <linux/in.h>
> +#include <linux/tty.h>
> +#include <linux/errno.h>
> +#include <linux/netdevice.h>
> +#include <linux/etherdevice.h>
> +#include <linux/skbuff.h>
> +#include <linux/rtnetlink.h>
> +#include <linux/if_arp.h>
> +#include <linux/delay.h>
> +#include <linux/init.h>
> +#include <linux/slab.h>
> +#include <linux/workqueue.h>
> +#include <linux/if_ltalk.h>
> +#include <linux/atalk.h>

Are all those needed? I doubt that.

> +#ifndef TASH_DEBUG
> +#define TASH_DEBUG 0
> +#endif
> +static unsigned int tash_debug = TASH_DEBUG;

Can we use dyn dbg instead?

> +/* Max number of channels
> + * override with insmod -otash_maxdev=nnn
> + */
> +#define TASH_MAX_CHAN 32
> +#define TT_MTU 605
> +/* The buffer should be double since potentially
> + * all bytes inside are escaped.
> + */
> +#define BUF_LEN (TT_MTU * 2 + 4)
> +
> +struct tashtalk {
> +	int magic;

Where did you dig this driver from? Drop this.

> +	struct tty_struct *tty;        /* ptr to TTY structure		*/
> +	struct net_device *dev;        /* easy for intr handling	*/
> +	spinlock_t lock;
> +	wait_queue_head_t addr_wait;
> +	struct work_struct tx_work;    /* Flushes transmit buffer	*/
> +
> +	/* These are pointers to the malloc()ed frame buffers. */
> +	unsigned char *rbuff;          /* receiver buffer		*/
> +	int rcount;                    /* received chars counter       */

uint?

> +	unsigned char *xbuff;          /* transmitter buffer		*/
> +	unsigned char *xhead;          /* pointer to next byte to XMIT */

Switch to u8's.

> +	int xleft;                     /* bytes left in XMIT queue     */
> +	int mtu;
> +	int buffsize;                  /* Max buffers sizes            */

So uint?

> +	unsigned long flags;           /* Flag values/ mode etc	*/
> +	unsigned char mode;            /* really not used */
> +	pid_t pid;

Storing pid is usually wrong. So is here.

Many of the above is ancient material.

> +	struct atalk_addr node_addr;   /* Full node address */
> +};
...
> +static void tash_setbits(struct tashtalk *tt, unsigned char addr)
> +{
> +	unsigned char bits[33];

u8

> +	unsigned int byte, pos;
> +
> +	/* 0, 255 and anything else are invalid */
> +	if (addr == 0 || addr >= 255)
> +		return;
> +
> +	memset(bits, 0, sizeof(bits));
> +
> +	/* in theory we can respond to many addresses */
> +	byte = addr / 8 + 1; /* skip initial command byte */
> +	pos = (addr % 8);
> +
> +	if (tash_debug)
> +		netdev_dbg(tt->dev,
> +			   "Setting address %i (byte %i bit %i) for you.",
> +			    addr, byte - 1, pos);
> +
> +	bits[0] = TT_CMD_SET_NIDS;
> +	bits[byte] = (1 << pos);

BIT()

> +
> +	set_bit(TTY_DO_WRITE_WAKEUP, &tt->tty->flags);
> +	tt->tty->ops->write(tt->tty, bits, sizeof(bits));
> +}
> +
> +static u16 tt_crc_ccitt_update(u16 crc, u8 data)
> +{
> +	data ^= (u8)(crc) & (u8)(0xFF);
> +	data ^= data << 4;
> +	return ((((u16)data << 8) | ((crc & 0xFF00) >> 8)) ^ (u8)(data >> 4) ^
> +		((u16)data << 3));

Is this real ccitt? Won't crc_ccitt_byte() work then?

> +}
> +
> +static u16 tash_crc(const unsigned char *data, int len)
> +{
> +	u16 crc = 0xFFFF;
> +
> +	for (int i = 0; i < len; i++)
> +		crc = tt_crc_ccitt_update(crc, data[i]);
> +
> +	return crc;

Or even crc_ccitt()?

> +}
...
> +/* Write out any remaining transmit buffer. Scheduled when tty is writable */
> +static void tash_transmit_worker(struct work_struct *work)
> +{
> +	struct tashtalk *tt = container_of(work, struct tashtalk, tx_work);
> +	int actual;
> +
> +	spin_lock_bh(&tt->lock);
> +	/* First make sure we're connected. */
> +	if (!tt->tty || tt->magic != TASH_MAGIC || !netif_running(tt->dev)) {

Can the former two happen?

> +		spin_unlock_bh(&tt->lock);
> +		return;
> +	}
> +
> +	/* We always get here after all transmissions
> +	 * No more data?
> +	 */
> +	if (tt->xleft <= 0) {
> +		/* reset the flags for transmission
> +		 * and re-wake the netif queue
> +		 */
> +		tt->dev->stats.tx_packets++;
> +		clear_bit(TTY_DO_WRITE_WAKEUP, &tt->tty->flags);
> +		spin_unlock_bh(&tt->lock);
> +		netif_wake_queue(tt->dev);
> +
> +		return;
> +	}
> +
> +	/* Send whatever is there to send
> +	 * This function will be called again if xleft <= 0
> +	 */
> +	actual = tt->tty->ops->write(tt->tty, tt->xhead, tt->xleft);

returns ssize_t

> +	tt->xleft -= actual;
> +	tt->xhead += actual;
> +
> +	spin_unlock_bh(&tt->lock);
> +}
> +
> +/* Called by the driver when there's room for more data.
> + * Schedule the transmit.

Is this a valid multiline comment in net/?

> + */
...

> +static void tt_tx_timeout(struct net_device *dev, unsigned int txqueue)
> +{
> +	struct tashtalk *tt = netdev_priv(dev);
> +
> +	spin_lock(&tt->lock);

guard()?

> +
> +	if (netif_queue_stopped(dev)) {
> +		if (!netif_running(dev) || !tt->tty)
> +			goto out;
> +	}
> +out:
> +	spin_unlock(&tt->lock);
> +}
...
> +/* Netdevice DOWN -> UP routine */
> +
> +static int tt_open(struct net_device *dev)
> +{
> +	struct tashtalk *tt = netdev_priv(dev);
> +
> +	if (!tt->tty) {

No lock?

> +		netdev_err(dev, "TTY not open");
> +		return -ENODEV;
> +	}
> +
> +	tt->flags &= (1 << TT_FLAG_INUSE);

so clear_bit()?

> +	netif_start_queue(dev);
> +	return 0;
> +}

> +static void tashtalk_send_ctrl_packet(struct tashtalk *tt, unsigned char dst,
> +				      unsigned char src, unsigned char type)
> +{
> +	unsigned char cmd = TT_CMD_TX;
> +	unsigned char buf[5];

u8

> +	int actual;
> +	u16 crc;
> +
> +	buf[LLAP_DST_POS] = dst;
> +	buf[LLAP_SRC_POS] = src;
> +	buf[LLAP_TYP_POS] = type;
> +
> +	crc = tash_crc(buf, 3);
> +	buf[3] = ~(crc & 0xFF);
> +	buf[4] = ~(crc >> 8);
> +
> +	actual = tt->tty->ops->write(tt->tty, &cmd, 1);
> +	actual += tt->tty->ops->write(tt->tty, buf, sizeof(buf));

What is actual used for? And why is this not a single write (using 
buf[6] instead).

> +}
...
> +static void tashtalk_receive_buf(struct tty_struct *tty,
> +				 const u8 *cp, const u8 *fp,
> +				 size_t count)
> +{
> +	struct tashtalk *tt = tty->disc_data;
> +	int i;
> +
> +	if (!tt || tt->magic != TASH_MAGIC || !netif_running(tt->dev))

How can that happen?

> +		return;
> +
> +	if (tash_debug)
> +		netdev_dbg(tt->dev, "(1) TashTalk read %li", count);
> +
> +	print_hex_dump_bytes("Tash read: ", DUMP_PREFIX_NONE, cp, count);
> +
> +	if (!test_bit(TT_FLAG_INFRAME, &tt->flags)) {
> +		tt->rcount = 0;
> +		if (tash_debug)
> +			netdev_dbg(tt->dev, "(2) TashTalk start new frame");
> +	} else if (tash_debug) {
> +		netdev_dbg(tt->dev, "(2) TashTalk continue frame");
> +	}
> +
> +	set_bit(TT_FLAG_INFRAME, &tt->flags);

So test_and_set_bit()?

> +
> +	for (i = 0; i < count; i++) {
> +		set_bit(TT_FLAG_INFRAME, &tt->flags);

Why again?

> +
> +		if (cp[i] == 0x00) {
> +			set_bit(TT_FLAG_ESCAPE, &tt->flags);
> +			continue;
> +		}
> +
> +		if (test_and_clear_bit(TT_FLAG_ESCAPE, &tt->flags)) {
> +			if (cp[i] == 0xFF) {
> +				tt->rbuff[tt->rcount] = 0x00;
> +				tt->rcount++;
> +			} else {
> +				tashtalk_manage_escape(tt, cp[i]);
> +			}
> +		} else {
> +			tt->rbuff[tt->rcount] = cp[i];
> +			tt->rcount++;
> +		}
> +	}
> +
> +	if (tash_debug)
> +		netdev_dbg(tt->dev, "(5) Done read, pending frame=%i",
> +			   test_bit(TT_FLAG_INFRAME, &tt->flags));
> +}
...

> +static void tashtalk_close(struct tty_struct *tty)
> +{
> +	struct tashtalk *tt = tty->disc_data;
> +
> +	/* First make sure we're connected. */
> +	if (!tt || tt->magic != TASH_MAGIC || tt->tty != tty)

How can these happen?

> +		return;
> +
> +	spin_lock_bh(&tt->lock);
> +	rcu_assign_pointer(tty->disc_data, NULL);
> +	tt->tty = NULL;
> +	spin_unlock_bh(&tt->lock);
> +
> +	synchronize_rcu();
> +	flush_work(&tt->tx_work);
> +
> +	/* Flush network side */
> +	unregister_netdev(tt->dev);
> +	/* This will complete via tt_free_netdev */
> +}
...
> +static int __init tashtalk_init(void)
> +{
> +	int status;
> +
> +	if (tash_maxdev < 1)
> +		tash_maxdev = 1;
> +
> +	pr_info("TashTalk Interface (dynamic channels, max=%d)",
> +		tash_maxdev);

No info messages, please.

> +	tashtalk_devs =
> +		kcalloc(tash_maxdev, sizeof(struct net_device *), GFP_KERNEL);
> +	if (!tashtalk_devs)
> +		return -ENOMEM;

Something more modern? Like idr or a list?

> +	/* Fill in our line protocol discipline, and register it */
> +	status = tty_register_ldisc(&tashtalk_ldisc);
> +	if (status != 0) {
> +		pr_err("TaskTalk: can't register line discipline (err = %d)\n",
> +		       status);
> +		kfree(tashtalk_devs);
> +	}
> +	return status;
> +}
> +
> +static void __exit tashtalk_exit(void)
> +{
> +	unsigned long timeout = jiffies + HZ;
> +	struct net_device *dev;
> +	struct tashtalk *tt;
> +	int busy = 0;
> +	int i;
> +
> +	if (!tashtalk_devs)
> +		return;
> +
> +	/* First of all: check for active disciplines and hangup them. */
> +	do {
> +		if (busy)
> +			msleep_interruptible(100);
> +
> +		busy = 0;
> +		for (i = 0; i < tash_maxdev; i++) {
> +			dev = tashtalk_devs[i];
> +			if (!dev)
> +				continue;
> +			tt = netdev_priv(dev);
> +			spin_lock_bh(&tt->lock);
> +			if (tt->tty) {
> +				busy++;
> +				tty_hangup(tt->tty);
> +			}
> +			spin_unlock_bh(&tt->lock);
> +		}
> +	} while (busy && time_before(jiffies, timeout));

Is this neeeded at all? You cannot unload the module while the ldisc is 
active, right? (Unlike NET, TTY increases module count.)

> +	for (i = 0; i < tash_maxdev; i++) {
> +		dev = tashtalk_devs[i];
> +		if (!dev)
> +			continue;
> +		tashtalk_devs[i] = NULL;
> +
> +		tt = netdev_priv(dev);
> +		if (tt->tty) {
> +			pr_err("%s: tty discipline still running\n",
> +			       dev->name);
> +		}
> +
> +		unregister_netdev(dev);

Those should be unregistered in tty ldisc closes, no?

> +	}
> +
> +	kfree(tashtalk_devs);
> +	tashtalk_devs = NULL;
> +
> +	tty_unregister_ldisc(&tashtalk_ldisc);
> +}

thanks,
Rodolfo Zitellini Aug. 19, 2024, 12:39 p.m. UTC | #4
> On August 19, 2024, at 11:44 AM, Arnd Bergmann <arnd@arndb.de <mailto:arnd@arndb.de>> wrote:
> Nice to see you got this into a working state! I vaguely
> remember discussing this in the past, and suggesting you
> try a user space solution,

Hi Arnd, Simon and Jiri,
First and foremost, thank you so much for taking the time to review my code
and for providing your comments.
I will do my best to address the issues and improve the code for the
next submission.

I will also add a longer description on why I went this route, it is mostly
due to compatibility with existing distributions of netatalk 2, which can
work without modification.

> As we discussed in the past, I think this really should
> not use ndo_do_ioctl(), which instead should just disappear.

I will fix this in the next revision, I was not sure if I should touch the
appletalk code for my first submission, but I can add a third patch that
takes care of this.

Kind Regards,
Rodolfo
Jeff Johnson Aug. 19, 2024, 2:28 p.m. UTC | #5
On 8/17/24 02:33, Rodolfo Zitellini wrote:
...
> +module_init(tashtalk_init);
> +module_exit(tashtalk_exit);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS_LDISC(N_TASHTALK);

missing MODULE_DESCRIPTION()

Since commit 1fffe7a34c89 ("script: modpost: emit a warning when the
description is missing"), a module without a MODULE_DESCRIPTION() will
result in a warning when built with make W=1. Recently, multiple
developers have been eradicating these warnings treewide, and very few
are left, so please don't introduce a new one :)

/jeff
diff mbox series

Patch

diff --git a/Documentation/networking/device_drivers/appletalk/index.rst b/Documentation/networking/device_drivers/appletalk/index.rst
new file mode 100644
index 000000000000..9d2d40bd8c8a
--- /dev/null
+++ b/Documentation/networking/device_drivers/appletalk/index.rst
@@ -0,0 +1,18 @@ 
+.. SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+
+AppleTalk Device Drivers
+========================
+
+Contents:
+
+.. toctree::
+   :maxdepth: 2
+
+   tashtalk
+
+.. only::  subproject and html
+
+   Indices
+   =======
+
+   * :ref:`genindex`
diff --git a/Documentation/networking/device_drivers/appletalk/tashtalk.rst b/Documentation/networking/device_drivers/appletalk/tashtalk.rst
new file mode 100644
index 000000000000..fdf7c58db339
--- /dev/null
+++ b/Documentation/networking/device_drivers/appletalk/tashtalk.rst
@@ -0,0 +1,139 @@ 
+.. SPDX-License-Identifier: GPL-2.0
+
+tashtalk.c: LocalTalk driver for Linux
+======================================
+
+Authors
+-------
+
+Rodolfo Zitellini <rwz@xhero.org>
+
+Motivation
+----------
+
+The Linux Kernel includes a complete implementation of AppleTalk,
+which can be used with the Netatalk package to share files with older
+classic Macintoshes. The Kernel also contained drivers for LocalTalk,
+the serial LAN found on many early Macs, which was based on older ISA
+cards implementing the same chipset found in Macs. These boards were
+historically very difficult to obtain, making LocalTalk on Linux
+impractical. In recent years, a vibrant community of enthusiasts has
+produced many tools to ease connecting older machines to the modern
+world. One such project is TashTalk, which implements LocalTalk on a
+PIC microcontroller (https://github.com/lampmerchant/tashtalk).
+
+This driver reintroduces LocalTalk support to the Kernel by providing
+an interface to TashTalk, which can be easily used over a serial port.
+It comes handy for use with older machines that have no thernet option,
+since all early Macintoshes had LocalTalk built-in.
+
+Introduction
+------------
+
+The LocalTalk network implemented one of the physical layers for AppleTalk,
+utilizing an RS422 bus with FM0 and SDLC encoding. On Macs, it was managed
+by the built-in Zilog SCC Z8530. In the modern context, this interface is
+provided by TashTalk, which communicates with a PC via a serial port or
+adapter, or through a specialized adapter (https://github.com/xhero/USB2LT)
+that directly connects a LocalTalk network via a USB port.
+
+Since LocalTalk support is still present in the Linux kernel, it is possible
+to use Netatalk 2 (or the upcoming version 4) directly. The interface is also
+compatible with macipgw (https://github.com/jasonking3/macipgw) to provide
+MacIP over LocalTalk.
+
+This driver implements a line discipline that must be attached, after which
+the LocalTalk interface can be brought up and used.
+
+Operation/loading of the driver
+-------------------------------
+
+If the driver is compiled as module, it can be loaded with
+
+    modprobe tashtalk
+
+By default, 32 TashTalk adapters are available, so this means it can use
+up to 32 serial ports. This number can be changed with the tash_maxdev
+parameter.
+
+Once the driver is loaded, the line discipline is used to attach a serial
+port to it:
+
+    sudo stty -F /dev/ttyUSB0 crtscts
+    sudo ldattach -s 1000000 31 /dev/ttyUSB0
+
+The line discipline ID for TashTalk is 31. Use of stty is required for
+hardware flow control (and has to be properly implemented in hardware!)
+Once the line disc is attached, the interface should be brought up:
+
+    sudo ip link set dev lt0 up
+
+or
+
+    sudo ifconfig lt0 up
+
+Any number (up to the specified max devices) of lt interfaces can be
+used, which will be numbered lt0-ltN
+
+Configuring Netatalk
+--------------------
+
+Netatalk natively supports Localtalk networks. Here is a simple
+configuration for one network:
+
+    lt0 -router -phase 2 -net 54321 -addr 54321.129 -zone LocalTalk
+
+This sets the node id to 129, but the node id will still be arbitrated
+on the network following the specifications. Starting Netatalk will then
+make shares and printers available on the Localtalk network.
+Multiple adapters can be used together:
+
+    lt0 -seed -phase 2 -net 1 -addr 1.129 -zone "AirTalk"
+    lt1 -seed -phase 2 -net 2 -addr 2.130 -zone "LocalTalk"
+
+And also different type of adapters (like Ethernet) can be mixed in
+the Netatalk routing.
+
+Addressing
+----------
+
+LocalTalk addresses are dynamically assigned by default. In the Linux
+implementation, a user program must request a preferred address, which
+the driver will attempt to allocate. If the preferred address is unavailable,
+the driver will suggest a new, randomly generated one, as specified by the
+LocalTalk protocol. The user program should then retrieve the assigned address.
+
+In the COPS LocalTalk implementation, this process was handled in a blocking
+manner, and Netatalk continues to expect this behavior. The same approach is
+implemented in this driver. When the user program issues a `SIOCSIFADDR` ioctl,
+it triggers the address arbitration algorithm. The ioctl call will only return
+once the arbitration is complete. Subsequently, a `SIOCGIFADDR` ioctl is required
+to obtain the actual assigned address.
+
+
+Debug
+-----
+
+Despite the name, tcpdump is able to understand DDP and basic AppleTalk packets:
+
+    sudo tcpdump -i lt0 -vvvX
+
+The driver can also be recompiled setting the TASH_DEBUG option, to have a more
+verbose log of what is going on.
+
+`print_hex_dump_bytes` is used to print incoming and outgoing packets
+
+    echo 'file tashtalk.c line 231 +p' > /sys/kernel/debug/dynamic_debug/control
+
+Please consult the current source for the exact line numbers.
+
+Credits
+-------
+
+Many thanks to Tashtari (https://github.com/lampmerchant) for his TashTalk
+implementation of LocalTalk, as well as his invaluable assistance in debugging this
+driver and his unwavering support throughout the project.
+
+Special thanks to Doug Brown for his invaluable help, patience, thorough reviews,
+and insightful comments on my code, as well as his support throughout the
+submission process.
\ No newline at end of file
diff --git a/Documentation/networking/device_drivers/index.rst b/Documentation/networking/device_drivers/index.rst
index 0dd30a84ce25..1ab70c94e1aa 100644
--- a/Documentation/networking/device_drivers/index.rst
+++ b/Documentation/networking/device_drivers/index.rst
@@ -8,6 +8,7 @@  Contents:
 .. toctree::
    :maxdepth: 2
 
+   appletalk/index
    atm/index
    cable/index
    can/index
diff --git a/MAINTAINERS b/MAINTAINERS
index 8766f3e5e87e..7fbde47d00b9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -22237,6 +22237,13 @@  F:	Documentation/filesystems/sysv-fs.rst
 F:	fs/sysv/
 F:	include/linux/sysv_fs.h
 
+TASHTALK APPLETALK DRIVER
+M:	Rodolfo Zitellini <rwz@xhero.org>
+L:	netdev@vger.kernel.org
+S:	Maintained
+F:	Documentation/networking/device_drivers/appletalk/tashtalk.rst
+F:	drivers/net/can/appletalk/tashtalk.c
+
 TASKSTATS STATISTICS INTERFACE
 M:	Balbir Singh <bsingharora@gmail.com>
 S:	Maintained
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 9920b3a68ed1..2cb47e93ce5a 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -668,4 +668,6 @@  config NETDEV_LEGACY_INIT
 	  Drivers that call netdev_boot_setup_check() should select this
 	  symbol, everything else no longer needs it.
 
+source "drivers/net/appletalk/Kconfig"
+
 endif # NETDEVICES
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index 13743d0e83b5..d232ee2bbd13 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -47,6 +47,7 @@  obj-$(CONFIG_MHI_NET) += mhi_net.o
 # Networking Drivers
 #
 obj-$(CONFIG_ARCNET) += arcnet/
+obj-$(CONFIG_DEV_APPLETALK) += appletalk/
 obj-$(CONFIG_CAIF) += caif/
 obj-$(CONFIG_CAN) += can/
 ifdef CONFIG_NET_DSA
diff --git a/drivers/net/appletalk/Kconfig b/drivers/net/appletalk/Kconfig
new file mode 100644
index 000000000000..96e9f7121de1
--- /dev/null
+++ b/drivers/net/appletalk/Kconfig
@@ -0,0 +1,33 @@ 
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Appletalk driver configuration
+#
+
+config DEV_APPLETALK
+	tristate "Appletalk interfaces support"
+	depends on ATALK
+	help
+	  AppleTalk is a network protocol developed by Apple that allows Apple
+	  computers to communicate with each other over a network. This protocol
+	  is versatile and can operate over various types of physical interfaces.
+	  For instance, if you have a specific physical interface available,
+	  such as a LocalTalk serial adapter, you can enable support for it by
+	  selecting "Y" here. It's important to note that this support is
+	  specifically for non-Ethernet devices, which are natively supported
+	  by the appletalk driver
+
+	  By enabling this option, you ensure that your system can utilize the
+	  AppleTalk protocol over these alternative interfaces, allowing legacy
+	  Apple devices to communicate with your moder machines.
+
+config TASHTALK
+	tristate "TashTalk LocalTalk Interface Support"
+	depends on ATALK && DEV_APPLETALK
+	depends on NETDEVICES
+	help
+	  TashTalk is a serial adapter for LocalTalk interfaces. It permits
+	  to natively connect to a LocalTalk bus via a serial port or USB
+	  adapter. It will then work natively with Netatalk, and it can be
+	  used to communicate with a network of classic Macintoshes or
+	  compatibile systems.
+	  <file:Documentation/networking/device_drivers/appletalk/tashtalk.rst>.
diff --git a/drivers/net/appletalk/Makefile b/drivers/net/appletalk/Makefile
new file mode 100644
index 000000000000..897ecbe65b29
--- /dev/null
+++ b/drivers/net/appletalk/Makefile
@@ -0,0 +1,6 @@ 
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for drivers/net/appletalk
+#
+
+obj-$(CONFIG_TASHTALK) += tashtalk.o
diff --git a/drivers/net/appletalk/tashtalk.c b/drivers/net/appletalk/tashtalk.c
new file mode 100644
index 000000000000..d7e6aae1e24d
--- /dev/null
+++ b/drivers/net/appletalk/tashtalk.c
@@ -0,0 +1,1003 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*      tashtalk.c: TashTalk LocalTalk driver for Linux.
+ *
+ *	Authors:
+ *      Rodolfo Zitellini (twelvetone12)
+ *
+ *      Derived from:
+ *      - slip.c: A network driver outline for linux.
+ *        written by Laurence Culhane and Fred N. van Kempen
+ *
+ *      This software may be used and distributed according to the terms
+ *      of the GNU General Public License, incorporated herein by reference.
+ *
+ */
+
+#include <linux/compat.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/version.h>
+
+#include <linux/uaccess.h>
+#include <linux/bitops.h>
+#include <linux/sched/signal.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/in.h>
+#include <linux/tty.h>
+#include <linux/errno.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/rtnetlink.h>
+#include <linux/if_arp.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/if_ltalk.h>
+#include <linux/atalk.h>
+
+#ifndef TASH_DEBUG
+#define TASH_DEBUG 0
+#endif
+static unsigned int tash_debug = TASH_DEBUG;
+
+/* Max number of channels
+ * override with insmod -otash_maxdev=nnn
+ */
+#define TASH_MAX_CHAN 32
+#define TT_MTU 605
+/* The buffer should be double since potentially
+ * all bytes inside are escaped.
+ */
+#define BUF_LEN (TT_MTU * 2 + 4)
+
+struct tashtalk {
+	int magic;
+
+	struct tty_struct *tty;        /* ptr to TTY structure		*/
+	struct net_device *dev;        /* easy for intr handling	*/
+	spinlock_t lock;
+	wait_queue_head_t addr_wait;
+	struct work_struct tx_work;    /* Flushes transmit buffer	*/
+
+	/* These are pointers to the malloc()ed frame buffers. */
+	unsigned char *rbuff;          /* receiver buffer		*/
+	int rcount;                    /* received chars counter       */
+	unsigned char *xbuff;          /* transmitter buffer		*/
+	unsigned char *xhead;          /* pointer to next byte to XMIT */
+	int xleft;                     /* bytes left in XMIT queue     */
+	int mtu;
+	int buffsize;                  /* Max buffers sizes            */
+
+	unsigned long flags;           /* Flag values/ mode etc	*/
+	unsigned char mode;            /* really not used */
+	pid_t pid;
+
+	struct atalk_addr node_addr;   /* Full node address */
+};
+
+#define TT_FLAG_INUSE       0 /* Channel in use                     */
+#define TT_FLAG_ESCAPE      1 /* ESC received                       */
+#define TT_FLAG_INFRAME     2 /* We did not finish decoding a frame */
+#define TT_FLAG_WAITADDR    3 /* We are waiting for an address      */
+#define TT_FLAG_GOTACK      4 /* Received an ACK for our ENQ        */
+
+#define TT_CMD_NOP	    0x00
+#define TT_CMD_TX	    0x01
+#define TT_CMD_SET_NIDS	    0x02
+#define TT_CMD_SET_FEAT	    0x03
+
+#define TASH_MAGIC          0xFDFA
+#define LLAP_CHECK          0xF0B8
+
+#define LLAP_ENQ            0x81
+#define LLAP_ACK            0x82
+#define LLAP_RTS            0x84
+#define LLAP_CTS            0x85
+
+#define LLAP_DST_POS        0
+#define LLAP_SRC_POS        1
+#define LLAP_TYP_POS        2
+
+static struct net_device **tashtalk_devs;
+
+static int tash_maxdev = TASH_MAX_CHAN;
+module_param(tash_maxdev, int, 0);
+MODULE_PARM_DESC(tash_maxdev, "Maximum number of tashtalk devices");
+
+static void tashtalk_send_ctrl_packet(struct tashtalk *tt, unsigned char dst,
+				      unsigned char src, unsigned char type);
+
+static unsigned char tt_arbitrate_addr_blocking(struct tashtalk *tt, unsigned char addr);
+
+static void tash_setbits(struct tashtalk *tt, unsigned char addr)
+{
+	unsigned char bits[33];
+	unsigned int byte, pos;
+
+	/* 0, 255 and anything else are invalid */
+	if (addr == 0 || addr >= 255)
+		return;
+
+	memset(bits, 0, sizeof(bits));
+
+	/* in theory we can respond to many addresses */
+	byte = addr / 8 + 1; /* skip initial command byte */
+	pos = (addr % 8);
+
+	if (tash_debug)
+		netdev_dbg(tt->dev,
+			   "Setting address %i (byte %i bit %i) for you.",
+			    addr, byte - 1, pos);
+
+	bits[0] = TT_CMD_SET_NIDS;
+	bits[byte] = (1 << pos);
+
+	set_bit(TTY_DO_WRITE_WAKEUP, &tt->tty->flags);
+	tt->tty->ops->write(tt->tty, bits, sizeof(bits));
+}
+
+static u16 tt_crc_ccitt_update(u16 crc, u8 data)
+{
+	data ^= (u8)(crc) & (u8)(0xFF);
+	data ^= data << 4;
+	return ((((u16)data << 8) | ((crc & 0xFF00) >> 8)) ^ (u8)(data >> 4) ^
+		((u16)data << 3));
+}
+
+static u16 tash_crc(const unsigned char *data, int len)
+{
+	u16 crc = 0xFFFF;
+
+	for (int i = 0; i < len; i++)
+		crc = tt_crc_ccitt_update(crc, data[i]);
+
+	return crc;
+}
+
+/* Send one completely decapsulated DDP datagram to the DDP layer. */
+static void tt_post_to_netif(struct tashtalk *tt)
+{
+	struct net_device *dev = tt->dev;
+	struct sk_buff *skb;
+
+	/* before doing stuff, we need to make sure it is not a control frame
+	 * Control frames are always 5 bytes long
+	 */
+	if (tt->rcount <= 5)
+		return;
+
+	/* 0xF0B8 is the polynomial used in LLAP */
+	if (tash_crc(tt->rbuff, tt->rcount) != LLAP_CHECK) {
+		netdev_warn(dev, "Invalid CRC, drop packet");
+		return;
+	}
+
+	tt->rcount -= 2; /* Strip away the CRC bytes */
+	dev->stats.rx_bytes += tt->rcount;
+
+	skb = netdev_alloc_skb(dev, tt->rcount);
+	if (!skb) {
+		dev->stats.rx_dropped++;
+		return;
+	}
+
+	/* skip the CRC bytes at the end */
+	skb_put_data(skb, tt->rbuff, tt->rcount);
+	skb->protocol = htons(ETH_P_LOCALTALK);
+
+	/* This is for compatibility with the phase1 to phase2 translation */
+	skb_reset_mac_header(skb); /* Point to entire packet. */
+	skb_pull(skb, 3);
+	skb_reset_transport_header(skb); /* Point to data (Skip header). */
+
+	netif_rx(skb);
+	dev->stats.rx_packets++;
+}
+
+/* Encapsulate one DDP datagram into a TTY queue. */
+static void tt_send_frame(struct tashtalk *tt, unsigned char *icp, int len)
+{
+	int actual;
+	u16 crc;
+
+	/* This should not happen as we check beforehand */
+	if (len + 3 > BUF_LEN) {
+		netdev_err(tt->dev, "Dropping oversized buffer\n");
+		return;
+	}
+
+	crc = tash_crc(icp, len);
+
+	tt->xbuff[0] = TT_CMD_TX; /* First byte is te Tash TRANSMIT command */
+	memcpy(&tt->xbuff[1], icp, len); /* followed by all the bytes */
+	/* Last two bytes are the CRC */
+	tt->xbuff[1 + len] = ~(crc & 0xFF);
+	tt->xbuff[2 + len] = ~(crc >> 8);
+
+	len += 3; /* Account for Tash CMD + CRC */
+	actual = tt->tty->ops->write(tt->tty, tt->xbuff, len);
+
+	tt->xleft = len - actual;
+	/* see you in tash_transmit_worker */
+	tt->xhead = tt->xbuff + actual;
+
+	print_hex_dump_bytes("TashTalk: LLAP OUT frame sans CRC: ",
+			     DUMP_PREFIX_NONE, icp, len);
+
+	if (tash_debug)
+		netdev_dbg(tt->dev, "Transmit actual %i, requested %i",
+			   actual, len);
+
+	if (actual == len) {
+		clear_bit(TTY_DO_WRITE_WAKEUP, &tt->tty->flags);
+		netif_wake_queue(tt->dev);
+	} else {
+		set_bit(TTY_DO_WRITE_WAKEUP, &tt->tty->flags);
+	}
+}
+
+/* Write out any remaining transmit buffer. Scheduled when tty is writable */
+static void tash_transmit_worker(struct work_struct *work)
+{
+	struct tashtalk *tt = container_of(work, struct tashtalk, tx_work);
+	int actual;
+
+	spin_lock_bh(&tt->lock);
+	/* First make sure we're connected. */
+	if (!tt->tty || tt->magic != TASH_MAGIC || !netif_running(tt->dev)) {
+		spin_unlock_bh(&tt->lock);
+		return;
+	}
+
+	/* We always get here after all transmissions
+	 * No more data?
+	 */
+	if (tt->xleft <= 0) {
+		/* reset the flags for transmission
+		 * and re-wake the netif queue
+		 */
+		tt->dev->stats.tx_packets++;
+		clear_bit(TTY_DO_WRITE_WAKEUP, &tt->tty->flags);
+		spin_unlock_bh(&tt->lock);
+		netif_wake_queue(tt->dev);
+
+		return;
+	}
+
+	/* Send whatever is there to send
+	 * This function will be called again if xleft <= 0
+	 */
+	actual = tt->tty->ops->write(tt->tty, tt->xhead, tt->xleft);
+	tt->xleft -= actual;
+	tt->xhead += actual;
+
+	spin_unlock_bh(&tt->lock);
+}
+
+/* Called by the driver when there's room for more data.
+ * Schedule the transmit.
+ */
+static void tashtalk_write_wakeup(struct tty_struct *tty)
+{
+	struct tashtalk *tt;
+
+	rcu_read_lock();
+	tt = rcu_dereference(tty->disc_data);
+	if (tt)
+		schedule_work(&tt->tx_work);
+	rcu_read_unlock();
+}
+
+static void tt_tx_timeout(struct net_device *dev, unsigned int txqueue)
+{
+	struct tashtalk *tt = netdev_priv(dev);
+
+	spin_lock(&tt->lock);
+
+	if (netif_queue_stopped(dev)) {
+		if (!netif_running(dev) || !tt->tty)
+			goto out;
+	}
+out:
+	spin_unlock(&tt->lock);
+}
+
+static netdev_tx_t tt_transmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct tashtalk *tt = netdev_priv(dev);
+
+	if (skb->len > tt->mtu) {
+		netdev_err(dev, "Dropping oversized transmit packet %i vs %i!\n",
+			   skb->len, tt->mtu);
+		dev_kfree_skb(skb);
+		return NETDEV_TX_OK;
+	}
+
+	spin_lock(&tt->lock);
+	if (!netif_running(dev)) {
+		spin_unlock(&tt->lock);
+		netdev_err(dev, "Transmit call when iface is down\n");
+		dev_kfree_skb(skb);
+		return NETDEV_TX_OK;
+	}
+	if (!tt->tty) {
+		spin_unlock(&tt->lock);
+		dev_kfree_skb(skb);
+		netdev_err(dev, "TTY not connected\n");
+		return NETDEV_TX_OK;
+	}
+
+	netif_stop_queue(tt->dev);
+	dev->stats.tx_bytes += skb->len;
+	tt_send_frame(tt, skb->data, skb->len);
+	spin_unlock(&tt->lock);
+
+	dev_kfree_skb(skb);
+	return NETDEV_TX_OK;
+}
+
+/******************************************
+ *   Routines looking at netdevice side.
+ ******************************************/
+
+/* Netdevice UP -> DOWN routine */
+
+static int tt_close(struct net_device *dev)
+{
+	struct tashtalk *tt = netdev_priv(dev);
+
+	spin_lock_bh(&tt->lock);
+	if (tt->tty)
+		/* TTY discipline is running. */
+		clear_bit(TTY_DO_WRITE_WAKEUP, &tt->tty->flags);
+	netif_stop_queue(dev);
+	tt->rcount = 0;
+	tt->xleft = 0;
+	spin_unlock_bh(&tt->lock);
+
+	return 0;
+}
+
+/* Netdevice DOWN -> UP routine */
+
+static int tt_open(struct net_device *dev)
+{
+	struct tashtalk *tt = netdev_priv(dev);
+
+	if (!tt->tty) {
+		netdev_err(dev, "TTY not open");
+		return -ENODEV;
+	}
+
+	tt->flags &= (1 << TT_FLAG_INUSE);
+	netif_start_queue(dev);
+	return 0;
+}
+
+/* Netdevice get statistics request */
+static void tt_get_stats64(struct net_device *dev,
+			   struct rtnl_link_stats64 *stats)
+{
+	struct net_device_stats *devstats = &dev->stats;
+
+	stats->rx_packets = devstats->rx_packets;
+	stats->tx_packets = devstats->tx_packets;
+	stats->rx_bytes = devstats->rx_bytes;
+	stats->tx_bytes = devstats->tx_bytes;
+	stats->rx_dropped = devstats->rx_dropped;
+	stats->tx_dropped = devstats->tx_dropped;
+	stats->tx_errors = devstats->tx_errors;
+	stats->rx_errors = devstats->rx_errors;
+	stats->rx_over_errors = devstats->rx_over_errors;
+}
+
+/* This has to be blocking for compatibility with netatalk */
+static unsigned char tt_arbitrate_addr_blocking(struct tashtalk *tt,
+						unsigned char addr)
+{
+	unsigned char min, max;
+	unsigned char rand;
+	int i;
+
+	/* Set the ranges, the new address should stay in the proper one
+	 * I.e. a server should be >= 129 and a client always < 129
+	 */
+	min = (addr < 129) ? 1 : 129;
+	max = (addr < 129) ? 128 : 254;
+
+	if (tash_debug)
+		netdev_dbg(tt->dev,
+			   "Start address arbitration, requested %i", addr);
+
+	/* This works a bit backwards, we send many ENQs
+	 * and are happy not to receive ACKs.
+	 * If we get ACK, we try another addr
+	 */
+
+	set_bit(TT_FLAG_WAITADDR, &tt->flags);
+
+	for (i = 0; i < 10; i++) {
+		clear_bit(TT_FLAG_GOTACK, &tt->flags);
+		tashtalk_send_ctrl_packet(tt, addr, addr, LLAP_ENQ);
+
+		/* Timeout == nobody reclaims our addr */
+		if (wait_event_timeout(tt->addr_wait,
+				       test_bit(TT_FLAG_GOTACK, &tt->flags),
+				       msecs_to_jiffies(1))) {
+			unsigned char newaddr;
+
+			/* Oops! somebody has the same addr as us
+			 * make up a new one and start over
+			 */
+			get_random_bytes(&rand, 1);
+			newaddr = min + rand % (max - min + 1);
+			if (tash_debug)
+				netdev_dbg(tt->dev, "Addr %i is in use, try %i",
+					   addr, newaddr);
+			addr = newaddr;
+		}
+	}
+
+	clear_bit(TT_FLAG_WAITADDR, &tt->flags);
+	clear_bit(TT_FLAG_GOTACK, &tt->flags);
+
+	netdev_info(tt->dev, "Arbitrated address is %i", addr);
+
+	return addr;
+}
+
+static int tt_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	struct sockaddr_at *sa = (struct sockaddr_at *)&ifr->ifr_addr;
+	struct tashtalk *tt = netdev_priv(dev);
+	struct atalk_addr *aa = &tt->node_addr;
+
+	switch (cmd) {
+	case SIOCSIFADDR:
+
+		sa->sat_addr.s_node =
+			tt_arbitrate_addr_blocking(tt, sa->sat_addr.s_node);
+
+		aa->s_net = sa->sat_addr.s_net;
+		aa->s_node = sa->sat_addr.s_node;
+
+		/* Set broadcast address. */
+		dev->broadcast[0] = 0xFF;
+
+		/* Set hardware address. */
+		dev->addr_len = 1;
+		dev_addr_set(dev, &aa->s_node);
+
+		/* Setup tashtalk to respond to that addr */
+		tash_setbits(tt, aa->s_node);
+
+		return 0;
+
+	case SIOCGIFADDR:
+		sa->sat_addr.s_net = aa->s_net;
+		sa->sat_addr.s_node = aa->s_node;
+
+		return 0;
+
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+/* The destructor */
+static void tt_free_netdev(struct net_device *dev)
+{
+	int i = dev->base_addr;
+
+	tashtalk_devs[i] = NULL;
+}
+
+/* Copied from cops.c, make appletalk happy */
+static void tt_set_multicast(struct net_device *dev)
+{
+	netdev_dbg(dev, "set_multicast_list executed\n");
+}
+
+static const struct net_device_ops tt_netdev_ops = {
+	.ndo_open = tt_open,
+	.ndo_stop = tt_close,
+	.ndo_start_xmit = tt_transmit,
+	.ndo_get_stats64 = tt_get_stats64,
+	.ndo_tx_timeout = tt_tx_timeout,
+	.ndo_do_ioctl = tt_ioctl,
+	.ndo_set_rx_mode = tt_set_multicast,
+};
+
+static void tashtalk_send_ctrl_packet(struct tashtalk *tt, unsigned char dst,
+				      unsigned char src, unsigned char type)
+{
+	unsigned char cmd = TT_CMD_TX;
+	unsigned char buf[5];
+	int actual;
+	u16 crc;
+
+	buf[LLAP_DST_POS] = dst;
+	buf[LLAP_SRC_POS] = src;
+	buf[LLAP_TYP_POS] = type;
+
+	crc = tash_crc(buf, 3);
+	buf[3] = ~(crc & 0xFF);
+	buf[4] = ~(crc >> 8);
+
+	actual = tt->tty->ops->write(tt->tty, &cmd, 1);
+	actual += tt->tty->ops->write(tt->tty, buf, sizeof(buf));
+}
+
+static void tashtalk_manage_control_frame(struct tashtalk *tt)
+{
+	switch (tt->rbuff[LLAP_TYP_POS]) {
+	case LLAP_ENQ:
+
+		if (tt->node_addr.s_node != 0 &&
+		    tt->rbuff[LLAP_SRC_POS] == tt->node_addr.s_node) {
+			if (tash_debug) {
+				netdev_dbg(tt->dev, "Reply ACK to ENQ from %i",
+					   tt->rbuff[LLAP_SRC_POS]);
+			}
+
+			tashtalk_send_ctrl_packet(tt, tt->rbuff[LLAP_SRC_POS],
+						  tt->node_addr.s_node,
+						  LLAP_ACK);
+		}
+
+		break;
+
+	case LLAP_ACK:
+		if (test_bit(TT_FLAG_WAITADDR, &tt->flags)) {
+			set_bit(TT_FLAG_GOTACK, &tt->flags);
+			wake_up(&tt->addr_wait);
+		}
+		break;
+	}
+}
+
+static int tashtalk_is_control_frame(unsigned char *frame)
+{
+	return (frame[LLAP_TYP_POS] >= LLAP_ENQ &&
+		frame[LLAP_TYP_POS] <= LLAP_CTS);
+}
+
+static void tashtalk_manage_valid_frame(struct tashtalk *tt)
+{
+	if (tash_debug)
+		netdev_dbg(tt->dev, "(3) TashTalk done frame, len=%i",
+			   tt->rcount);
+
+	print_hex_dump_bytes("(3a) LLAP IN frame: ", DUMP_PREFIX_NONE,
+			     tt->rbuff, tt->rcount);
+
+	/* Control frames are not sent to the netif */
+	if (tt->rcount == 5 && tashtalk_is_control_frame(tt->rbuff))
+		tashtalk_manage_control_frame(tt);
+	else
+		tt_post_to_netif(tt);
+
+	if (tash_debug)
+		netdev_dbg(tt->dev, "(4) TashTalk next frame");
+}
+
+static void tashtalk_manage_escape(struct tashtalk *tt, unsigned char seq)
+{
+	switch (seq) {
+	case 0xFD:
+		tashtalk_manage_valid_frame(tt);
+		break;
+	case 0xFE:
+		netdev_info(tt->dev, "Frame error");
+		break;
+	case 0xFA:
+		netdev_info(tt->dev, "Frame abort");
+		break;
+	case 0xFC:
+		netdev_info(tt->dev, "Frame crc error");
+		break;
+
+	default:
+		netdev_warn(tt->dev, "Unknown escape sequence %c", seq);
+		break;
+	}
+
+	tt->rcount = 0;
+	clear_bit(TT_FLAG_INFRAME, &tt->flags);
+}
+
+/*********************************************
+ * Routines looking at TTY talking to TashTalk
+ *********************************************/
+
+static void tashtalk_receive_buf(struct tty_struct *tty,
+				 const u8 *cp, const u8 *fp,
+				 size_t count)
+{
+	struct tashtalk *tt = tty->disc_data;
+	int i;
+
+	if (!tt || tt->magic != TASH_MAGIC || !netif_running(tt->dev))
+		return;
+
+	if (tash_debug)
+		netdev_dbg(tt->dev, "(1) TashTalk read %li", count);
+
+	print_hex_dump_bytes("Tash read: ", DUMP_PREFIX_NONE, cp, count);
+
+	if (!test_bit(TT_FLAG_INFRAME, &tt->flags)) {
+		tt->rcount = 0;
+		if (tash_debug)
+			netdev_dbg(tt->dev, "(2) TashTalk start new frame");
+	} else if (tash_debug) {
+		netdev_dbg(tt->dev, "(2) TashTalk continue frame");
+	}
+
+	set_bit(TT_FLAG_INFRAME, &tt->flags);
+
+	for (i = 0; i < count; i++) {
+		set_bit(TT_FLAG_INFRAME, &tt->flags);
+
+		if (cp[i] == 0x00) {
+			set_bit(TT_FLAG_ESCAPE, &tt->flags);
+			continue;
+		}
+
+		if (test_and_clear_bit(TT_FLAG_ESCAPE, &tt->flags)) {
+			if (cp[i] == 0xFF) {
+				tt->rbuff[tt->rcount] = 0x00;
+				tt->rcount++;
+			} else {
+				tashtalk_manage_escape(tt, cp[i]);
+			}
+		} else {
+			tt->rbuff[tt->rcount] = cp[i];
+			tt->rcount++;
+		}
+	}
+
+	if (tash_debug)
+		netdev_dbg(tt->dev, "(5) Done read, pending frame=%i",
+			   test_bit(TT_FLAG_INFRAME, &tt->flags));
+}
+
+/* Free a channel buffers. */
+static void tt_free_bufs(struct tashtalk *tt)
+{
+	kfree(xchg(&tt->rbuff, NULL));
+	kfree(xchg(&tt->xbuff, NULL));
+}
+
+static int tt_alloc_bufs(struct tashtalk *tt, int buf_len)
+{
+	int err = -ENOBUFS;
+	char *rbuff = NULL;
+	char *xbuff = NULL;
+	unsigned long len;
+
+	rbuff = kmalloc(buf_len, GFP_KERNEL);
+	if (!rbuff)
+		goto err_exit;
+
+	xbuff = kmalloc(buf_len, GFP_KERNEL);
+	if (!xbuff)
+		goto err_exit;
+
+	spin_lock_bh(&tt->lock);
+	if (!tt->tty) {
+		spin_unlock_bh(&tt->lock);
+		err = -ENODEV;
+		goto err_exit;
+	}
+
+	tt->buffsize = len;
+	tt->rcount = 0;
+	tt->xleft = 0;
+
+	rbuff = xchg(&tt->rbuff, rbuff);
+	xbuff = xchg(&tt->xbuff, xbuff);
+
+	spin_unlock_bh(&tt->lock);
+	err = 0;
+
+	/* Cleanup */
+err_exit:
+
+	kfree(xbuff);
+	kfree(rbuff);
+	return err;
+}
+
+/* Find a free channel, and link in this `tty' line. */
+static struct tashtalk *tt_alloc(void)
+{
+	struct net_device *dev = NULL;
+	struct tashtalk *tt;
+	int i;
+
+	for (i = 0; i < tash_maxdev; i++) {
+		dev = tashtalk_devs[i];
+		if (!dev)
+			break;
+	}
+
+	if (i >= tash_maxdev) {
+		pr_err("TashTalk: all slots in use");
+		return NULL;
+	}
+
+	/* Also assigns the default lt* name */
+	dev = alloc_ltalkdev(sizeof(*tt));
+
+	if (!dev) {
+		pr_err("TashTalk: could not allocate ltalkdev");
+		return NULL;
+	}
+
+	dev->base_addr = i;
+	tt = netdev_priv(dev);
+
+	/* Initialize channel control data */
+	tt->magic = TASH_MAGIC;
+	tt->dev = dev;
+	tt->mtu = TT_MTU;
+	tt->mode = 0; /*Maybe useful in the future? */
+
+	tt->dev->netdev_ops = &tt_netdev_ops;
+	tt->dev->type = ARPHRD_LOCALTLK;
+	tt->dev->priv_destructor = tt_free_netdev;
+
+	/* Initially we have no address */
+	/* so we do not reply to ENQs */
+	tt->node_addr.s_node = 0;
+	tt->node_addr.s_net = 0;
+
+	spin_lock_init(&tt->lock);
+	init_waitqueue_head(&tt->addr_wait);
+	INIT_WORK(&tt->tx_work, tash_transmit_worker);
+
+	tashtalk_devs[i] = dev;
+	return tt;
+}
+
+/* Open the high-level part of the TashTalk channel.
+ * Generally used with a userspace program:
+ * sudo ldattach -d -s 1000000 PPP /dev/ttyUSB0
+ */
+
+static int tashtalk_open(struct tty_struct *tty)
+{
+	struct tashtalk *tt;
+	int err;
+
+	if (!capable(CAP_NET_ADMIN))
+		return -EPERM;
+
+	if (!tty->ops->write)
+		return -EOPNOTSUPP;
+
+	rtnl_lock();
+
+	tt = tty->disc_data;
+
+	err = -EEXIST;
+	/* First make sure we're not already connected. */
+	if (tt && tt->magic == TASH_MAGIC)
+		goto err_exit;
+
+	err = -ENFILE;
+
+	tt = tt_alloc();
+	if (!tt)
+		goto err_exit;
+
+	tt->tty = tty;
+	tty->disc_data = tt;
+	tt->pid = current->pid;
+
+	if (!test_bit(TT_FLAG_INUSE, &tt->flags)) {
+		set_bit(TT_FLAG_INUSE, &tt->flags);
+
+		err = tt_alloc_bufs(tt, BUF_LEN);
+		if (err)
+			goto err_free_chan;
+
+		err = register_netdevice(tt->dev);
+		if (err)
+			goto err_free_bufs;
+
+	} else {
+		pr_err("TashTalk: Channel is already in use");
+	}
+
+	/* Done.  We have linked the TTY line to a channel. */
+	rtnl_unlock();
+	tty->receive_room = 65536; /* We don't flow control */
+
+	/* TTY layer expects 0 on success */
+	pr_info("TashTalk is on port %s", tty->name);
+	return 0;
+
+err_free_bufs:
+	tt_free_bufs(tt);
+
+err_free_chan:
+	pr_err("TashTalk: could not open device");
+	tt->tty = NULL;
+	tty->disc_data = NULL;
+	clear_bit(TT_FLAG_INUSE, &tt->flags);
+
+	/* do not call free_netdev before rtnl_unlock */
+	rtnl_unlock();
+	free_netdev(tt->dev);
+	return err;
+
+err_exit:
+	rtnl_unlock();
+
+	/* Count references from TTY module */
+	return err;
+}
+
+static void tashtalk_close(struct tty_struct *tty)
+{
+	struct tashtalk *tt = tty->disc_data;
+
+	/* First make sure we're connected. */
+	if (!tt || tt->magic != TASH_MAGIC || tt->tty != tty)
+		return;
+
+	spin_lock_bh(&tt->lock);
+	rcu_assign_pointer(tty->disc_data, NULL);
+	tt->tty = NULL;
+	spin_unlock_bh(&tt->lock);
+
+	synchronize_rcu();
+	flush_work(&tt->tx_work);
+
+	/* Flush network side */
+	unregister_netdev(tt->dev);
+	/* This will complete via tt_free_netdev */
+}
+
+static void tashtalk_hangup(struct tty_struct *tty)
+{
+	tashtalk_close(tty);
+}
+
+static int tashtalk_ioctl(struct tty_struct *tty, unsigned int cmd,
+			  unsigned long arg)
+{
+	struct tashtalk *tt = tty->disc_data;
+	int __user *p = (int __user *)arg;
+	unsigned int tmp;
+
+	/* First make sure we're connected. */
+	if (!tt || tt->magic != TASH_MAGIC)
+		return -EINVAL;
+
+	switch (cmd) {
+	case SIOCGIFNAME:
+		tmp = strlen(tt->dev->name) + 1;
+		if (copy_to_user((void __user *)arg, tt->dev->name, tmp))
+			return -EFAULT;
+		return 0;
+
+	case SIOCGIFENCAP:
+		if (put_user(tt->mode, p))
+			return -EFAULT;
+		return 0;
+
+	case SIOCSIFENCAP:
+		if (get_user(tmp, p))
+			return -EFAULT;
+		tt->mode = tmp;
+		return 0;
+
+	case SIOCSIFHWADDR:
+		return -EINVAL;
+
+	default:
+		return tty_mode_ioctl(tty, cmd, arg);
+	}
+}
+
+static struct tty_ldisc_ops tashtalk_ldisc = {
+	.owner = THIS_MODULE,
+	.num = N_TASHTALK,
+	.name = "tasktalk",
+	.open = tashtalk_open,
+	.close = tashtalk_close,
+	.hangup = tashtalk_hangup,
+	.ioctl = tashtalk_ioctl,
+	.receive_buf = tashtalk_receive_buf,
+	.write_wakeup = tashtalk_write_wakeup,
+};
+
+static int __init tashtalk_init(void)
+{
+	int status;
+
+	if (tash_maxdev < 1)
+		tash_maxdev = 1;
+
+	pr_info("TashTalk Interface (dynamic channels, max=%d)",
+		tash_maxdev);
+
+	tashtalk_devs =
+		kcalloc(tash_maxdev, sizeof(struct net_device *), GFP_KERNEL);
+	if (!tashtalk_devs)
+		return -ENOMEM;
+
+	/* Fill in our line protocol discipline, and register it */
+	status = tty_register_ldisc(&tashtalk_ldisc);
+	if (status != 0) {
+		pr_err("TaskTalk: can't register line discipline (err = %d)\n",
+		       status);
+		kfree(tashtalk_devs);
+	}
+	return status;
+}
+
+static void __exit tashtalk_exit(void)
+{
+	unsigned long timeout = jiffies + HZ;
+	struct net_device *dev;
+	struct tashtalk *tt;
+	int busy = 0;
+	int i;
+
+	if (!tashtalk_devs)
+		return;
+
+	/* First of all: check for active disciplines and hangup them. */
+	do {
+		if (busy)
+			msleep_interruptible(100);
+
+		busy = 0;
+		for (i = 0; i < tash_maxdev; i++) {
+			dev = tashtalk_devs[i];
+			if (!dev)
+				continue;
+			tt = netdev_priv(dev);
+			spin_lock_bh(&tt->lock);
+			if (tt->tty) {
+				busy++;
+				tty_hangup(tt->tty);
+			}
+			spin_unlock_bh(&tt->lock);
+		}
+	} while (busy && time_before(jiffies, timeout));
+
+	for (i = 0; i < tash_maxdev; i++) {
+		dev = tashtalk_devs[i];
+		if (!dev)
+			continue;
+		tashtalk_devs[i] = NULL;
+
+		tt = netdev_priv(dev);
+		if (tt->tty) {
+			pr_err("%s: tty discipline still running\n",
+			       dev->name);
+		}
+
+		unregister_netdev(dev);
+	}
+
+	kfree(tashtalk_devs);
+	tashtalk_devs = NULL;
+
+	tty_unregister_ldisc(&tashtalk_ldisc);
+}
+
+module_init(tashtalk_init);
+module_exit(tashtalk_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_LDISC(N_TASHTALK);