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

Rodolfo Zitellini
2024-08-17T09:33:21Z

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

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);
--
2.34.1