diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-30 09:22:00 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-30 09:22:00 -0700 |
| commit | 034dd340b08be1f2f0477ad16131d609f9dbd53c (patch) | |
| tree | 2536f4b2d7893ccd916b5abdf3c454ad6edc03c2 /net/lapb | |
| download | linux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.tar.gz linux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.zip | |
Merge tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-tracegrafted
Pull tracing fixes from Steven Rostedt:
- Fix error output of boot instance creation failure
Currently if a boot instance creation fails, instead of printing out
the name of the instance that failed, it prints "(null)". That is
because it prints "cur_str" that had already been processed by
strsep(). Print the saved name instead.
While at it, print the error code of the failure.
- Fix use-after-free for same named historgrams
Histograms can be named so that they can be used in multiple events.
But if the named histogram has a variable attached, the second event
that uses the named histogram which duplicates it and needs to free
the original after duplication leaves the old variable in place and
still visible. If another histogram uses than variable, it will use
the stale one which will try to reference the freed duplicate
histogram and crash the kernel.
Free the duplicate variables along with the duplicated histogram
data.
- Check return value of kthread_run() in event self test
The events self tests uses a kthread for testing but does not check
if it succeeded in creating a kthread. If the kthread creation were
to fail, the code will still try to call kthread_stop() on the error
returned.
- Fix race between reading trace_pipe and updating subbuffer size
If a user is reading the trace_pipe file at the same time they update
the ring buffer sub-buffer size, can cause the trace_pipe read to
read stale data. Add trace_access_lock() around updating the ring
buffer sub-buffer size.
- Fix eventfs_inode on failure path in creation of the events directory
In the creation of the "events" directory, if after allocating the
eventfs_inode a failure is detected, it calls cleanup_ei() which
calls free_ei(). The free_ei() will test if eventfs_inode being freed
has no children. It is a bug if it does. But on the failure case of
the creation of the "events" directory, the children lists have not
yet been initialized and the free will trigger a warning because
list_empty() on an uninitialized list returns false.
Move the initialization into init_ei() where it makes more sense and
makes sure that a created eventfs_inode has its lists initialized
upon creation.
- Check return value of kthread_run() in ftrace direct sample code
The sample code that shows how to use the ftrace direct calls does
not test the return of kthread_run() to see if it succeeds. Return a
failure if the kthread_run() doesn't succeed.
- Clear user events state on fork in case of alloc failure
On fork, the child gets a pointer to the parent's user events state.
It makes a copy of it then updates the child's pointer to it. But if
the allocation fails, the duplication function leaves the child with
a pointer to its parent's descriptor. When the child cleans up its
data, it will free the parent's descriptor while the parent is still
using it.
In the duplication function, set the child's user_event_mm to NULL
before testing if the allocation succeeded, and when it exits it will
not free the parent's descriptor.
- Fix retry exhaustion in simple ring buffer reader swap
simple_ring_buffer_swap_reader_page() starts with retry set to 8 and
post-decrements it only after a failed link replacement. On the final
attempt, a successful replacement leaves retry at zero, while a
failed replacement leaves it at -1.
But the check for success expects the retry value to be non-zero and
exits with an error on zero. This is the opposite result. Fix it.
- Fail nicely when the remote swap_reader_page() returns an error
Currently, if the swap_reader_page() of a remote buffer fails, it
triggers a WARN_ON_ONCE() and continues normally. Instead, have it
exit with an error and a pr_warn() print instead of a full WARNING.
* tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace:
ring-buffer: Stop remote reader update when page swap fails
tracing: Fix retry exhaustion in simple ring buffer reader swap
tracing/user_events: Clear copied tracing state before fork duplication
samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-multi-modify
samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-modify
eventfs: Initialize ei->children and ei->list in init_ei()
tracing: Fix use-after-free in trace_pipe read on sub-buffer order change
tracing: Fix crash passing ERR_PTR to kthread_stop()
tracing: Fix use-after-free with same-name named triggers
tracing: Fix logged instance name on creation failure
Diffstat (limited to 'net/lapb')
| -rw-r--r-- | net/lapb/Kconfig | 22 | ||||
| -rw-r--r-- | net/lapb/Makefile | 8 | ||||
| -rw-r--r-- | net/lapb/lapb_iface.c | 553 | ||||
| -rw-r--r-- | net/lapb/lapb_in.c | 556 | ||||
| -rw-r--r-- | net/lapb/lapb_out.c | 205 | ||||
| -rw-r--r-- | net/lapb/lapb_subr.c | 299 | ||||
| -rw-r--r-- | net/lapb/lapb_timer.c | 206 |
7 files changed, 1849 insertions, 0 deletions
diff --git a/net/lapb/Kconfig b/net/lapb/Kconfig new file mode 100644 index 000000000..da87b47f0 --- /dev/null +++ b/net/lapb/Kconfig @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# LAPB Data Link Drive +# + +config LAPB + tristate "LAPB Data Link Driver" + help + Link Access Procedure, Balanced (LAPB) is the data link layer (i.e. + the lower) part of the X.25 protocol. It offers a reliable + connection service to exchange data frames with one other host, and + it is used to transport higher level protocols (mostly X.25 Packet + Layer, the higher part of X.25, but others are possible as well). + Usually, LAPB is used with specialized X.21 network cards, but Linux + currently supports LAPB only over Ethernet connections. If you want + to use LAPB connections over Ethernet, say Y here and to "LAPB over + Ethernet driver" below. Read + <file:Documentation/networking/lapb-module.rst> for technical + details. + + To compile this driver as a module, choose M here: the + module will be called lapb. If unsure, say N. diff --git a/net/lapb/Makefile b/net/lapb/Makefile new file mode 100644 index 000000000..7be91b4c0 --- /dev/null +++ b/net/lapb/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for the Linux LAPB layer. +# + +obj-$(CONFIG_LAPB) += lapb.o + +lapb-y := lapb_in.o lapb_out.o lapb_subr.o lapb_timer.o lapb_iface.o diff --git a/net/lapb/lapb_iface.c b/net/lapb/lapb_iface.c new file mode 100644 index 000000000..670e0859a --- /dev/null +++ b/net/lapb/lapb_iface.c @@ -0,0 +1,553 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * LAPB release 002 + * + * This code REQUIRES 2.1.15 or higher/ NET3.038 + * + * History + * LAPB 001 Jonathan Naylor Started Coding + * LAPB 002 Jonathan Naylor New timer architecture. + * 2000-10-29 Henner Eisen lapb_data_indication() return status. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/socket.h> +#include <linux/in.h> +#include <linux/kernel.h> +#include <linux/jiffies.h> +#include <linux/timer.h> +#include <linux/string.h> +#include <linux/sockios.h> +#include <linux/net.h> +#include <linux/inet.h> +#include <linux/if_arp.h> +#include <linux/skbuff.h> +#include <linux/slab.h> +#include <net/sock.h> +#include <linux/uaccess.h> +#include <linux/fcntl.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/stat.h> +#include <linux/init.h> +#include <net/lapb.h> + +static LIST_HEAD(lapb_list); +static DEFINE_RWLOCK(lapb_list_lock); + +/* + * Free an allocated lapb control block. + */ +static void lapb_free_cb(struct lapb_cb *lapb) +{ + kfree(lapb); +} + +static __inline__ void lapb_hold(struct lapb_cb *lapb) +{ + refcount_inc(&lapb->refcnt); +} + +static __inline__ void lapb_put(struct lapb_cb *lapb) +{ + if (refcount_dec_and_test(&lapb->refcnt)) + lapb_free_cb(lapb); +} + +/* + * Socket removal during an interrupt is now safe. + */ +static void __lapb_remove_cb(struct lapb_cb *lapb) +{ + if (lapb->node.next) { + list_del(&lapb->node); + lapb_put(lapb); + } +} + +/* + * Add a socket to the bound sockets list. + */ +static void __lapb_insert_cb(struct lapb_cb *lapb) +{ + list_add(&lapb->node, &lapb_list); + lapb_hold(lapb); +} + +static struct lapb_cb *__lapb_devtostruct(struct net_device *dev) +{ + struct lapb_cb *lapb, *use = NULL; + + list_for_each_entry(lapb, &lapb_list, node) { + if (lapb->dev == dev) { + use = lapb; + break; + } + } + + if (use) + lapb_hold(use); + + return use; +} + +static struct lapb_cb *lapb_devtostruct(struct net_device *dev) +{ + struct lapb_cb *rc; + + read_lock_bh(&lapb_list_lock); + rc = __lapb_devtostruct(dev); + read_unlock_bh(&lapb_list_lock); + + return rc; +} +/* + * Create an empty LAPB control block. + */ +static struct lapb_cb *lapb_create_cb(void) +{ + struct lapb_cb *lapb = kzalloc_obj(*lapb, GFP_ATOMIC); + + if (!lapb) + goto out; + + skb_queue_head_init(&lapb->write_queue); + skb_queue_head_init(&lapb->ack_queue); + + timer_setup(&lapb->t1timer, NULL, 0); + timer_setup(&lapb->t2timer, NULL, 0); + lapb->t1timer_running = false; + lapb->t2timer_running = false; + + lapb->t1 = LAPB_DEFAULT_T1; + lapb->t2 = LAPB_DEFAULT_T2; + lapb->n2 = LAPB_DEFAULT_N2; + lapb->mode = LAPB_DEFAULT_MODE; + lapb->window = LAPB_DEFAULT_WINDOW; + lapb->state = LAPB_STATE_0; + + spin_lock_init(&lapb->lock); + refcount_set(&lapb->refcnt, 1); +out: + return lapb; +} + +int lapb_register(struct net_device *dev, + const struct lapb_register_struct *callbacks) +{ + struct lapb_cb *lapb; + int rc = LAPB_BADTOKEN; + + write_lock_bh(&lapb_list_lock); + + lapb = __lapb_devtostruct(dev); + if (lapb) { + lapb_put(lapb); + goto out; + } + + lapb = lapb_create_cb(); + rc = LAPB_NOMEM; + if (!lapb) + goto out; + + lapb->dev = dev; + lapb->callbacks = callbacks; + + __lapb_insert_cb(lapb); + + lapb_start_t1timer(lapb); + + rc = LAPB_OK; +out: + write_unlock_bh(&lapb_list_lock); + return rc; +} +EXPORT_SYMBOL(lapb_register); + +int lapb_unregister(struct net_device *dev) +{ + struct lapb_cb *lapb; + int rc = LAPB_BADTOKEN; + + write_lock_bh(&lapb_list_lock); + lapb = __lapb_devtostruct(dev); + if (!lapb) + goto out; + lapb_put(lapb); + + /* Wait for other refs to "lapb" to drop */ + while (refcount_read(&lapb->refcnt) > 2) + usleep_range(1, 10); + + spin_lock_bh(&lapb->lock); + + lapb_stop_t1timer(lapb); + lapb_stop_t2timer(lapb); + + lapb_clear_queues(lapb); + + spin_unlock_bh(&lapb->lock); + + /* Wait for running timers to stop */ + timer_delete_sync(&lapb->t1timer); + timer_delete_sync(&lapb->t2timer); + + __lapb_remove_cb(lapb); + + lapb_put(lapb); + rc = LAPB_OK; +out: + write_unlock_bh(&lapb_list_lock); + return rc; +} +EXPORT_SYMBOL(lapb_unregister); + +int lapb_getparms(struct net_device *dev, struct lapb_parms_struct *parms) +{ + int rc = LAPB_BADTOKEN; + struct lapb_cb *lapb = lapb_devtostruct(dev); + + if (!lapb) + goto out; + + spin_lock_bh(&lapb->lock); + + parms->t1 = lapb->t1 / HZ; + parms->t2 = lapb->t2 / HZ; + parms->n2 = lapb->n2; + parms->n2count = lapb->n2count; + parms->state = lapb->state; + parms->window = lapb->window; + parms->mode = lapb->mode; + + if (!timer_pending(&lapb->t1timer)) + parms->t1timer = 0; + else + parms->t1timer = (lapb->t1timer.expires - jiffies) / HZ; + + if (!timer_pending(&lapb->t2timer)) + parms->t2timer = 0; + else + parms->t2timer = (lapb->t2timer.expires - jiffies) / HZ; + + spin_unlock_bh(&lapb->lock); + lapb_put(lapb); + rc = LAPB_OK; +out: + return rc; +} +EXPORT_SYMBOL(lapb_getparms); + +int lapb_setparms(struct net_device *dev, struct lapb_parms_struct *parms) +{ + int rc = LAPB_BADTOKEN; + struct lapb_cb *lapb = lapb_devtostruct(dev); + + if (!lapb) + goto out; + + spin_lock_bh(&lapb->lock); + + rc = LAPB_INVALUE; + if (parms->t1 < 1 || parms->t2 < 1 || parms->n2 < 1) + goto out_put; + + if (lapb->state == LAPB_STATE_0) { + if (parms->mode & LAPB_EXTENDED) { + if (parms->window < 1 || parms->window > 127) + goto out_put; + } else { + if (parms->window < 1 || parms->window > 7) + goto out_put; + } + lapb->mode = parms->mode; + lapb->window = parms->window; + } + + lapb->t1 = parms->t1 * HZ; + lapb->t2 = parms->t2 * HZ; + lapb->n2 = parms->n2; + + rc = LAPB_OK; +out_put: + spin_unlock_bh(&lapb->lock); + lapb_put(lapb); +out: + return rc; +} +EXPORT_SYMBOL(lapb_setparms); + +int lapb_connect_request(struct net_device *dev) +{ + struct lapb_cb *lapb = lapb_devtostruct(dev); + int rc = LAPB_BADTOKEN; + + if (!lapb) + goto out; + + spin_lock_bh(&lapb->lock); + + rc = LAPB_OK; + if (lapb->state == LAPB_STATE_1) + goto out_put; + + rc = LAPB_CONNECTED; + if (lapb->state == LAPB_STATE_3 || lapb->state == LAPB_STATE_4) + goto out_put; + + lapb_establish_data_link(lapb); + + lapb_dbg(0, "(%p) S0 -> S1\n", lapb->dev); + lapb->state = LAPB_STATE_1; + + rc = LAPB_OK; +out_put: + spin_unlock_bh(&lapb->lock); + lapb_put(lapb); +out: + return rc; +} +EXPORT_SYMBOL(lapb_connect_request); + +static int __lapb_disconnect_request(struct lapb_cb *lapb) +{ + switch (lapb->state) { + case LAPB_STATE_0: + return LAPB_NOTCONNECTED; + + case LAPB_STATE_1: + lapb_dbg(1, "(%p) S1 TX DISC(1)\n", lapb->dev); + lapb_dbg(0, "(%p) S1 -> S0\n", lapb->dev); + lapb_send_control(lapb, LAPB_DISC, LAPB_POLLON, LAPB_COMMAND); + lapb->state = LAPB_STATE_0; + lapb_start_t1timer(lapb); + return LAPB_NOTCONNECTED; + + case LAPB_STATE_2: + return LAPB_OK; + } + + lapb_clear_queues(lapb); + lapb->n2count = 0; + lapb_send_control(lapb, LAPB_DISC, LAPB_POLLON, LAPB_COMMAND); + lapb_start_t1timer(lapb); + lapb_stop_t2timer(lapb); + lapb->state = LAPB_STATE_2; + + lapb_dbg(1, "(%p) S3 DISC(1)\n", lapb->dev); + lapb_dbg(0, "(%p) S3 -> S2\n", lapb->dev); + + return LAPB_OK; +} + +int lapb_disconnect_request(struct net_device *dev) +{ + struct lapb_cb *lapb = lapb_devtostruct(dev); + int rc = LAPB_BADTOKEN; + + if (!lapb) + goto out; + + spin_lock_bh(&lapb->lock); + + rc = __lapb_disconnect_request(lapb); + + spin_unlock_bh(&lapb->lock); + lapb_put(lapb); +out: + return rc; +} +EXPORT_SYMBOL(lapb_disconnect_request); + +int lapb_data_request(struct net_device *dev, struct sk_buff *skb) +{ + struct lapb_cb *lapb = lapb_devtostruct(dev); + int rc = LAPB_BADTOKEN; + + if (!lapb) + goto out; + + spin_lock_bh(&lapb->lock); + + rc = LAPB_NOTCONNECTED; + if (lapb->state != LAPB_STATE_3 && lapb->state != LAPB_STATE_4) + goto out_put; + + skb_queue_tail(&lapb->write_queue, skb); + lapb_kick(lapb); + rc = LAPB_OK; +out_put: + spin_unlock_bh(&lapb->lock); + lapb_put(lapb); +out: + return rc; +} +EXPORT_SYMBOL(lapb_data_request); + +int lapb_data_received(struct net_device *dev, struct sk_buff *skb) +{ + struct lapb_cb *lapb = lapb_devtostruct(dev); + int rc = LAPB_BADTOKEN; + + if (lapb) { + spin_lock_bh(&lapb->lock); + lapb_data_input(lapb, skb); + spin_unlock_bh(&lapb->lock); + lapb_put(lapb); + rc = LAPB_OK; + } + + return rc; +} +EXPORT_SYMBOL(lapb_data_received); + +void lapb_connect_confirmation(struct lapb_cb *lapb, int reason) +{ + if (lapb->callbacks->connect_confirmation) + lapb->callbacks->connect_confirmation(lapb->dev, reason); +} + +void lapb_connect_indication(struct lapb_cb *lapb, int reason) +{ + if (lapb->callbacks->connect_indication) + lapb->callbacks->connect_indication(lapb->dev, reason); +} + +void lapb_disconnect_confirmation(struct lapb_cb *lapb, int reason) +{ + if (lapb->callbacks->disconnect_confirmation) + lapb->callbacks->disconnect_confirmation(lapb->dev, reason); +} + +void lapb_disconnect_indication(struct lapb_cb *lapb, int reason) +{ + if (lapb->callbacks->disconnect_indication) + lapb->callbacks->disconnect_indication(lapb->dev, reason); +} + +int lapb_data_indication(struct lapb_cb *lapb, struct sk_buff *skb) +{ + if (lapb->callbacks->data_indication) + return lapb->callbacks->data_indication(lapb->dev, skb); + + kfree_skb(skb); + return NET_RX_SUCCESS; /* For now; must be != NET_RX_DROP */ +} + +int lapb_data_transmit(struct lapb_cb *lapb, struct sk_buff *skb) +{ + int used = 0; + + if (lapb->callbacks->data_transmit) { + lapb->callbacks->data_transmit(lapb->dev, skb); + used = 1; + } + + return used; +} + +/* Handle device status changes. */ +static int lapb_device_event(struct notifier_block *this, unsigned long event, + void *ptr) +{ + struct net_device *dev = netdev_notifier_info_to_dev(ptr); + struct lapb_cb *lapb; + + if (!net_eq(dev_net(dev), &init_net)) + return NOTIFY_DONE; + + if (dev->type != ARPHRD_X25) + return NOTIFY_DONE; + + lapb = lapb_devtostruct(dev); + if (!lapb) + return NOTIFY_DONE; + + spin_lock_bh(&lapb->lock); + + switch (event) { + case NETDEV_UP: + lapb_dbg(0, "(%p) Interface up: %s\n", dev, dev->name); + + if (netif_carrier_ok(dev)) { + lapb_dbg(0, "(%p): Carrier is already up: %s\n", dev, + dev->name); + if (lapb->mode & LAPB_DCE) { + lapb_start_t1timer(lapb); + } else { + if (lapb->state == LAPB_STATE_0) { + lapb->state = LAPB_STATE_1; + lapb_establish_data_link(lapb); + } + } + } + break; + case NETDEV_GOING_DOWN: + if (netif_carrier_ok(dev)) + __lapb_disconnect_request(lapb); + break; + case NETDEV_DOWN: + lapb_dbg(0, "(%p) Interface down: %s\n", dev, dev->name); + lapb_dbg(0, "(%p) S%d -> S0\n", dev, lapb->state); + lapb_clear_queues(lapb); + lapb->state = LAPB_STATE_0; + lapb->n2count = 0; + lapb_stop_t1timer(lapb); + lapb_stop_t2timer(lapb); + break; + case NETDEV_CHANGE: + if (netif_carrier_ok(dev)) { + lapb_dbg(0, "(%p): Carrier detected: %s\n", dev, + dev->name); + if (lapb->mode & LAPB_DCE) { + lapb_start_t1timer(lapb); + } else { + if (lapb->state == LAPB_STATE_0) { + lapb->state = LAPB_STATE_1; + lapb_establish_data_link(lapb); + } + } + } else { + lapb_dbg(0, "(%p) Carrier lost: %s\n", dev, dev->name); + lapb_dbg(0, "(%p) S%d -> S0\n", dev, lapb->state); + lapb_clear_queues(lapb); + lapb->state = LAPB_STATE_0; + lapb->n2count = 0; + lapb_stop_t1timer(lapb); + lapb_stop_t2timer(lapb); + } + break; + } + + spin_unlock_bh(&lapb->lock); + lapb_put(lapb); + return NOTIFY_DONE; +} + +static struct notifier_block lapb_dev_notifier = { + .notifier_call = lapb_device_event, +}; + +static int __init lapb_init(void) +{ + return register_netdevice_notifier(&lapb_dev_notifier); +} + +static void __exit lapb_exit(void) +{ + WARN_ON(!list_empty(&lapb_list)); + + unregister_netdevice_notifier(&lapb_dev_notifier); +} + +MODULE_AUTHOR("Jonathan Naylor <g4klx@g4klx.demon.co.uk>"); +MODULE_DESCRIPTION("The X.25 Link Access Procedure B link layer protocol"); +MODULE_LICENSE("GPL"); + +module_init(lapb_init); +module_exit(lapb_exit); diff --git a/net/lapb/lapb_in.c b/net/lapb/lapb_in.c new file mode 100644 index 000000000..38ae23c09 --- /dev/null +++ b/net/lapb/lapb_in.c @@ -0,0 +1,556 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * LAPB release 002 + * + * This code REQUIRES 2.1.15 or higher/ NET3.038 + * + * History + * LAPB 001 Jonathan Naulor Started Coding + * LAPB 002 Jonathan Naylor New timer architecture. + * 2000-10-29 Henner Eisen lapb_data_indication() return status. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/socket.h> +#include <linux/in.h> +#include <linux/kernel.h> +#include <linux/timer.h> +#include <linux/string.h> +#include <linux/sockios.h> +#include <linux/net.h> +#include <linux/inet.h> +#include <linux/netdevice.h> +#include <linux/skbuff.h> +#include <linux/slab.h> +#include <net/sock.h> +#include <linux/uaccess.h> +#include <linux/fcntl.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <net/lapb.h> + +/* + * State machine for state 0, Disconnected State. + * The handling of the timer(s) is in file lapb_timer.c. + */ +static void lapb_state0_machine(struct lapb_cb *lapb, struct sk_buff *skb, + struct lapb_frame *frame) +{ + switch (frame->type) { + case LAPB_SABM: + lapb_dbg(1, "(%p) S0 RX SABM(%d)\n", lapb->dev, frame->pf); + if (lapb->mode & LAPB_EXTENDED) { + lapb_dbg(1, "(%p) S0 TX DM(%d)\n", + lapb->dev, frame->pf); + lapb_send_control(lapb, LAPB_DM, frame->pf, + LAPB_RESPONSE); + } else { + lapb_dbg(1, "(%p) S0 TX UA(%d)\n", + lapb->dev, frame->pf); + lapb_dbg(0, "(%p) S0 -> S3\n", lapb->dev); + lapb_send_control(lapb, LAPB_UA, frame->pf, + LAPB_RESPONSE); + lapb_stop_t1timer(lapb); + lapb_stop_t2timer(lapb); + lapb->state = LAPB_STATE_3; + lapb->condition = 0x00; + lapb->n2count = 0; + lapb->vs = 0; + lapb->vr = 0; + lapb->va = 0; + lapb_connect_indication(lapb, LAPB_OK); + } + break; + + case LAPB_SABME: + lapb_dbg(1, "(%p) S0 RX SABME(%d)\n", lapb->dev, frame->pf); + if (lapb->mode & LAPB_EXTENDED) { + lapb_dbg(1, "(%p) S0 TX UA(%d)\n", + lapb->dev, frame->pf); + lapb_dbg(0, "(%p) S0 -> S3\n", lapb->dev); + lapb_send_control(lapb, LAPB_UA, frame->pf, + LAPB_RESPONSE); + lapb_stop_t1timer(lapb); + lapb_stop_t2timer(lapb); + lapb->state = LAPB_STATE_3; + lapb->condition = 0x00; + lapb->n2count = 0; + lapb->vs = 0; + lapb->vr = 0; + lapb->va = 0; + lapb_connect_indication(lapb, LAPB_OK); + } else { + lapb_dbg(1, "(%p) S0 TX DM(%d)\n", + lapb->dev, frame->pf); + lapb_send_control(lapb, LAPB_DM, frame->pf, + LAPB_RESPONSE); + } + break; + + case LAPB_DISC: + lapb_dbg(1, "(%p) S0 RX DISC(%d)\n", lapb->dev, frame->pf); + lapb_dbg(1, "(%p) S0 TX UA(%d)\n", lapb->dev, frame->pf); + lapb_send_control(lapb, LAPB_UA, frame->pf, LAPB_RESPONSE); + break; + + default: + break; + } + + kfree_skb(skb); +} + +/* + * State machine for state 1, Awaiting Connection State. + * The handling of the timer(s) is in file lapb_timer.c. + */ +static void lapb_state1_machine(struct lapb_cb *lapb, struct sk_buff *skb, + struct lapb_frame *frame) +{ + switch (frame->type) { + case LAPB_SABM: + lapb_dbg(1, "(%p) S1 RX SABM(%d)\n", lapb->dev, frame->pf); + if (lapb->mode & LAPB_EXTENDED) { + lapb_dbg(1, "(%p) S1 TX DM(%d)\n", + lapb->dev, frame->pf); + lapb_send_control(lapb, LAPB_DM, frame->pf, + LAPB_RESPONSE); + } else { + lapb_dbg(1, "(%p) S1 TX UA(%d)\n", + lapb->dev, frame->pf); + lapb_send_control(lapb, LAPB_UA, frame->pf, + LAPB_RESPONSE); + } + break; + + case LAPB_SABME: + lapb_dbg(1, "(%p) S1 RX SABME(%d)\n", lapb->dev, frame->pf); + if (lapb->mode & LAPB_EXTENDED) { + lapb_dbg(1, "(%p) S1 TX UA(%d)\n", + lapb->dev, frame->pf); + lapb_send_control(lapb, LAPB_UA, frame->pf, + LAPB_RESPONSE); + } else { + lapb_dbg(1, "(%p) S1 TX DM(%d)\n", + lapb->dev, frame->pf); + lapb_send_control(lapb, LAPB_DM, frame->pf, + LAPB_RESPONSE); + } + break; + + case LAPB_DISC: + lapb_dbg(1, "(%p) S1 RX DISC(%d)\n", lapb->dev, frame->pf); + lapb_dbg(1, "(%p) S1 TX DM(%d)\n", lapb->dev, frame->pf); + lapb_send_control(lapb, LAPB_DM, frame->pf, LAPB_RESPONSE); + break; + + case LAPB_UA: + lapb_dbg(1, "(%p) S1 RX UA(%d)\n", lapb->dev, frame->pf); + if (frame->pf) { + lapb_dbg(0, "(%p) S1 -> S3\n", lapb->dev); + lapb_stop_t1timer(lapb); + lapb_stop_t2timer(lapb); + lapb->state = LAPB_STATE_3; + lapb->condition = 0x00; + lapb->n2count = 0; + lapb->vs = 0; + lapb->vr = 0; + lapb->va = 0; + lapb_connect_confirmation(lapb, LAPB_OK); + } + break; + + case LAPB_DM: + lapb_dbg(1, "(%p) S1 RX DM(%d)\n", lapb->dev, frame->pf); + if (frame->pf) { + lapb_dbg(0, "(%p) S1 -> S0\n", lapb->dev); + lapb_clear_queues(lapb); + lapb->state = LAPB_STATE_0; + lapb_start_t1timer(lapb); + lapb_stop_t2timer(lapb); + lapb_disconnect_indication(lapb, LAPB_REFUSED); + } + break; + } + + kfree_skb(skb); +} + +/* + * State machine for state 2, Awaiting Release State. + * The handling of the timer(s) is in file lapb_timer.c + */ +static void lapb_state2_machine(struct lapb_cb *lapb, struct sk_buff *skb, + struct lapb_frame *frame) +{ + switch (frame->type) { + case LAPB_SABM: + case LAPB_SABME: + lapb_dbg(1, "(%p) S2 RX {SABM,SABME}(%d)\n", + lapb->dev, frame->pf); + lapb_dbg(1, "(%p) S2 TX DM(%d)\n", lapb->dev, frame->pf); + lapb_send_control(lapb, LAPB_DM, frame->pf, LAPB_RESPONSE); + break; + + case LAPB_DISC: + lapb_dbg(1, "(%p) S2 RX DISC(%d)\n", lapb->dev, frame->pf); + lapb_dbg(1, "(%p) S2 TX UA(%d)\n", lapb->dev, frame->pf); + lapb_send_control(lapb, LAPB_UA, frame->pf, LAPB_RESPONSE); + break; + + case LAPB_UA: + lapb_dbg(1, "(%p) S2 RX UA(%d)\n", lapb->dev, frame->pf); + if (frame->pf) { + lapb_dbg(0, "(%p) S2 -> S0\n", lapb->dev); + lapb->state = LAPB_STATE_0; + lapb_start_t1timer(lapb); + lapb_stop_t2timer(lapb); + lapb_disconnect_confirmation(lapb, LAPB_OK); + } + break; + + case LAPB_DM: + lapb_dbg(1, "(%p) S2 RX DM(%d)\n", lapb->dev, frame->pf); + if (frame->pf) { + lapb_dbg(0, "(%p) S2 -> S0\n", lapb->dev); + lapb->state = LAPB_STATE_0; + lapb_start_t1timer(lapb); + lapb_stop_t2timer(lapb); + lapb_disconnect_confirmation(lapb, LAPB_NOTCONNECTED); + } + break; + + case LAPB_I: + case LAPB_REJ: + case LAPB_RNR: + case LAPB_RR: + lapb_dbg(1, "(%p) S2 RX {I,REJ,RNR,RR}(%d)\n", + lapb->dev, frame->pf); + lapb_dbg(1, "(%p) S2 RX DM(%d)\n", lapb->dev, frame->pf); + if (frame->pf) + lapb_send_control(lapb, LAPB_DM, frame->pf, + LAPB_RESPONSE); + break; + } + + kfree_skb(skb); +} + +/* + * State machine for state 3, Connected State. + * The handling of the timer(s) is in file lapb_timer.c + */ +static void lapb_state3_machine(struct lapb_cb *lapb, struct sk_buff *skb, + struct lapb_frame *frame) +{ + int queued = 0; + int modulus = (lapb->mode & LAPB_EXTENDED) ? LAPB_EMODULUS : + LAPB_SMODULUS; + + switch (frame->type) { + case LAPB_SABM: + lapb_dbg(1, "(%p) S3 RX SABM(%d)\n", lapb->dev, frame->pf); + if (lapb->mode & LAPB_EXTENDED) { + lapb_dbg(1, "(%p) S3 TX DM(%d)\n", + lapb->dev, frame->pf); + lapb_send_control(lapb, LAPB_DM, frame->pf, + LAPB_RESPONSE); + } else { + lapb_dbg(1, "(%p) S3 TX UA(%d)\n", + lapb->dev, frame->pf); + lapb_send_control(lapb, LAPB_UA, frame->pf, + LAPB_RESPONSE); + lapb_stop_t1timer(lapb); + lapb_stop_t2timer(lapb); + lapb->condition = 0x00; + lapb->n2count = 0; + lapb->vs = 0; + lapb->vr = 0; + lapb->va = 0; + lapb_requeue_frames(lapb); + } + break; + + case LAPB_SABME: + lapb_dbg(1, "(%p) S3 RX SABME(%d)\n", lapb->dev, frame->pf); + if (lapb->mode & LAPB_EXTENDED) { + lapb_dbg(1, "(%p) S3 TX UA(%d)\n", + lapb->dev, frame->pf); + lapb_send_control(lapb, LAPB_UA, frame->pf, + LAPB_RESPONSE); + lapb_stop_t1timer(lapb); + lapb_stop_t2timer(lapb); + lapb->condition = 0x00; + lapb->n2count = 0; + lapb->vs = 0; + lapb->vr = 0; + lapb->va = 0; + lapb_requeue_frames(lapb); + } else { + lapb_dbg(1, "(%p) S3 TX DM(%d)\n", + lapb->dev, frame->pf); + lapb_send_control(lapb, LAPB_DM, frame->pf, + LAPB_RESPONSE); + } + break; + + case LAPB_DISC: + lapb_dbg(1, "(%p) S3 RX DISC(%d)\n", lapb->dev, frame->pf); + lapb_dbg(0, "(%p) S3 -> S0\n", lapb->dev); + lapb_clear_queues(lapb); + lapb_send_control(lapb, LAPB_UA, frame->pf, LAPB_RESPONSE); + lapb_start_t1timer(lapb); + lapb_stop_t2timer(lapb); + lapb->state = LAPB_STATE_0; + lapb_disconnect_indication(lapb, LAPB_OK); + break; + + case LAPB_DM: + lapb_dbg(1, "(%p) S3 RX DM(%d)\n", lapb->dev, frame->pf); + lapb_dbg(0, "(%p) S3 -> S0\n", lapb->dev); + lapb_clear_queues(lapb); + lapb->state = LAPB_STATE_0; + lapb_start_t1timer(lapb); + lapb_stop_t2timer(lapb); + lapb_disconnect_indication(lapb, LAPB_NOTCONNECTED); + break; + + case LAPB_RNR: + lapb_dbg(1, "(%p) S3 RX RNR(%d) R%d\n", + lapb->dev, frame->pf, frame->nr); + lapb->condition |= LAPB_PEER_RX_BUSY_CONDITION; + lapb_check_need_response(lapb, frame->cr, frame->pf); + if (lapb_validate_nr(lapb, frame->nr)) { + lapb_check_iframes_acked(lapb, frame->nr); + } else { + lapb->frmr_data = *frame; + lapb->frmr_type = LAPB_FRMR_Z; + lapb_transmit_frmr(lapb); + lapb_dbg(0, "(%p) S3 -> S4\n", lapb->dev); + lapb_start_t1timer(lapb); + lapb_stop_t2timer(lapb); + lapb->state = LAPB_STATE_4; + lapb->n2count = 0; + } + break; + + case LAPB_RR: + lapb_dbg(1, "(%p) S3 RX RR(%d) R%d\n", + lapb->dev, frame->pf, frame->nr); + lapb->condition &= ~LAPB_PEER_RX_BUSY_CONDITION; + lapb_check_need_response(lapb, frame->cr, frame->pf); + if (lapb_validate_nr(lapb, frame->nr)) { + lapb_check_iframes_acked(lapb, frame->nr); + } else { + lapb->frmr_data = *frame; + lapb->frmr_type = LAPB_FRMR_Z; + lapb_transmit_frmr(lapb); + lapb_dbg(0, "(%p) S3 -> S4\n", lapb->dev); + lapb_start_t1timer(lapb); + lapb_stop_t2timer(lapb); + lapb->state = LAPB_STATE_4; + lapb->n2count = 0; + } + break; + + case LAPB_REJ: + lapb_dbg(1, "(%p) S3 RX REJ(%d) R%d\n", + lapb->dev, frame->pf, frame->nr); + lapb->condition &= ~LAPB_PEER_RX_BUSY_CONDITION; + lapb_check_need_response(lapb, frame->cr, frame->pf); + if (lapb_validate_nr(lapb, frame->nr)) { + lapb_frames_acked(lapb, frame->nr); + lapb_stop_t1timer(lapb); + lapb->n2count = 0; + lapb_requeue_frames(lapb); + } else { + lapb->frmr_data = *frame; + lapb->frmr_type = LAPB_FRMR_Z; + lapb_transmit_frmr(lapb); + lapb_dbg(0, "(%p) S3 -> S4\n", lapb->dev); + lapb_start_t1timer(lapb); + lapb_stop_t2timer(lapb); + lapb->state = LAPB_STATE_4; + lapb->n2count = 0; + } + break; + + case LAPB_I: + lapb_dbg(1, "(%p) S3 RX I(%d) S%d R%d\n", + lapb->dev, frame->pf, frame->ns, frame->nr); + if (!lapb_validate_nr(lapb, frame->nr)) { + lapb->frmr_data = *frame; + lapb->frmr_type = LAPB_FRMR_Z; + lapb_transmit_frmr(lapb); + lapb_dbg(0, "(%p) S3 -> S4\n", lapb->dev); + lapb_start_t1timer(lapb); + lapb_stop_t2timer(lapb); + lapb->state = LAPB_STATE_4; + lapb->n2count = 0; + break; + } + if (lapb->condition & LAPB_PEER_RX_BUSY_CONDITION) + lapb_frames_acked(lapb, frame->nr); + else + lapb_check_iframes_acked(lapb, frame->nr); + + if (frame->ns == lapb->vr) { + int cn; + cn = lapb_data_indication(lapb, skb); + queued = 1; + /* + * If upper layer has dropped the frame, we + * basically ignore any further protocol + * processing. This will cause the peer + * to re-transmit the frame later like + * a frame lost on the wire. + */ + if (cn == NET_RX_DROP) { + pr_debug("rx congestion\n"); + break; + } + lapb->vr = (lapb->vr + 1) % modulus; + lapb->condition &= ~LAPB_REJECT_CONDITION; + if (frame->pf) + lapb_enquiry_response(lapb); + else { + if (!(lapb->condition & + LAPB_ACK_PENDING_CONDITION)) { + lapb->condition |= LAPB_ACK_PENDING_CONDITION; + lapb_start_t2timer(lapb); + } + } + } else { + if (lapb->condition & LAPB_REJECT_CONDITION) { + if (frame->pf) + lapb_enquiry_response(lapb); + } else { + lapb_dbg(1, "(%p) S3 TX REJ(%d) R%d\n", + lapb->dev, frame->pf, lapb->vr); + lapb->condition |= LAPB_REJECT_CONDITION; + lapb_send_control(lapb, LAPB_REJ, frame->pf, + LAPB_RESPONSE); + lapb->condition &= ~LAPB_ACK_PENDING_CONDITION; + } + } + break; + + case LAPB_FRMR: + lapb_dbg(1, "(%p) S3 RX FRMR(%d) %5ph\n", + lapb->dev, frame->pf, + skb->data); + lapb_establish_data_link(lapb); + lapb_dbg(0, "(%p) S3 -> S1\n", lapb->dev); + lapb_requeue_frames(lapb); + lapb->state = LAPB_STATE_1; + break; + + case LAPB_ILLEGAL: + lapb_dbg(1, "(%p) S3 RX ILLEGAL(%d)\n", lapb->dev, frame->pf); + lapb->frmr_data = *frame; + lapb->frmr_type = LAPB_FRMR_W; + lapb_transmit_frmr(lapb); + lapb_dbg(0, "(%p) S3 -> S4\n", lapb->dev); + lapb_start_t1timer(lapb); + lapb_stop_t2timer(lapb); + lapb->state = LAPB_STATE_4; + lapb->n2count = 0; + break; + } + + if (!queued) + kfree_skb(skb); +} + +/* + * State machine for state 4, Frame Reject State. + * The handling of the timer(s) is in file lapb_timer.c. + */ +static void lapb_state4_machine(struct lapb_cb *lapb, struct sk_buff *skb, + struct lapb_frame *frame) +{ + switch (frame->type) { + case LAPB_SABM: + lapb_dbg(1, "(%p) S4 RX SABM(%d)\n", lapb->dev, frame->pf); + if (lapb->mode & LAPB_EXTENDED) { + lapb_dbg(1, "(%p) S4 TX DM(%d)\n", + lapb->dev, frame->pf); + lapb_send_control(lapb, LAPB_DM, frame->pf, + LAPB_RESPONSE); + } else { + lapb_dbg(1, "(%p) S4 TX UA(%d)\n", + lapb->dev, frame->pf); + lapb_dbg(0, "(%p) S4 -> S3\n", lapb->dev); + lapb_send_control(lapb, LAPB_UA, frame->pf, + LAPB_RESPONSE); + lapb_stop_t1timer(lapb); + lapb_stop_t2timer(lapb); + lapb->state = LAPB_STATE_3; + lapb->condition = 0x00; + lapb->n2count = 0; + lapb->vs = 0; + lapb->vr = 0; + lapb->va = 0; + lapb_connect_indication(lapb, LAPB_OK); + } + break; + + case LAPB_SABME: + lapb_dbg(1, "(%p) S4 RX SABME(%d)\n", lapb->dev, frame->pf); + if (lapb->mode & LAPB_EXTENDED) { + lapb_dbg(1, "(%p) S4 TX UA(%d)\n", + lapb->dev, frame->pf); + lapb_dbg(0, "(%p) S4 -> S3\n", lapb->dev); + lapb_send_control(lapb, LAPB_UA, frame->pf, + LAPB_RESPONSE); + lapb_stop_t1timer(lapb); + lapb_stop_t2timer(lapb); + lapb->state = LAPB_STATE_3; + lapb->condition = 0x00; + lapb->n2count = 0; + lapb->vs = 0; + lapb->vr = 0; + lapb->va = 0; + lapb_connect_indication(lapb, LAPB_OK); + } else { + lapb_dbg(1, "(%p) S4 TX DM(%d)\n", + lapb->dev, frame->pf); + lapb_send_control(lapb, LAPB_DM, frame->pf, + LAPB_RESPONSE); + } + break; + } + + kfree_skb(skb); +} + +/* + * Process an incoming LAPB frame + */ +void lapb_data_input(struct lapb_cb *lapb, struct sk_buff *skb) +{ + struct lapb_frame frame; + + if (lapb_decode(lapb, skb, &frame) < 0) { + kfree_skb(skb); + return; + } + + switch (lapb->state) { + case LAPB_STATE_0: + lapb_state0_machine(lapb, skb, &frame); break; + case LAPB_STATE_1: + lapb_state1_machine(lapb, skb, &frame); break; + case LAPB_STATE_2: + lapb_state2_machine(lapb, skb, &frame); break; + case LAPB_STATE_3: + lapb_state3_machine(lapb, skb, &frame); break; + case LAPB_STATE_4: + lapb_state4_machine(lapb, skb, &frame); break; + } + + lapb_kick(lapb); +} diff --git a/net/lapb/lapb_out.c b/net/lapb/lapb_out.c new file mode 100644 index 000000000..a966d29c7 --- /dev/null +++ b/net/lapb/lapb_out.c @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * LAPB release 002 + * + * This code REQUIRES 2.1.15 or higher/ NET3.038 + * + * History + * LAPB 001 Jonathan Naylor Started Coding + * LAPB 002 Jonathan Naylor New timer architecture. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/socket.h> +#include <linux/in.h> +#include <linux/kernel.h> +#include <linux/timer.h> +#include <linux/string.h> +#include <linux/sockios.h> +#include <linux/net.h> +#include <linux/inet.h> +#include <linux/skbuff.h> +#include <linux/slab.h> +#include <net/sock.h> +#include <linux/uaccess.h> +#include <linux/fcntl.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <net/lapb.h> + +/* + * This procedure is passed a buffer descriptor for an iframe. It builds + * the rest of the control part of the frame and then writes it out. + */ +static void lapb_send_iframe(struct lapb_cb *lapb, struct sk_buff *skb, int poll_bit) +{ + unsigned char *frame; + + if (!skb) + return; + + if (lapb->mode & LAPB_EXTENDED) { + frame = skb_push(skb, 2); + + frame[0] = LAPB_I; + frame[0] |= lapb->vs << 1; + frame[1] = poll_bit ? LAPB_EPF : 0; + frame[1] |= lapb->vr << 1; + } else { + frame = skb_push(skb, 1); + + *frame = LAPB_I; + *frame |= poll_bit ? LAPB_SPF : 0; + *frame |= lapb->vr << 5; + *frame |= lapb->vs << 1; + } + + lapb_dbg(1, "(%p) S%d TX I(%d) S%d R%d\n", + lapb->dev, lapb->state, poll_bit, lapb->vs, lapb->vr); + + lapb_transmit_buffer(lapb, skb, LAPB_COMMAND); +} + +void lapb_kick(struct lapb_cb *lapb) +{ + struct sk_buff *skb, *skbn; + unsigned short modulus, start, end; + + modulus = (lapb->mode & LAPB_EXTENDED) ? LAPB_EMODULUS : LAPB_SMODULUS; + start = !skb_peek(&lapb->ack_queue) ? lapb->va : lapb->vs; + end = (lapb->va + lapb->window) % modulus; + + if (!(lapb->condition & LAPB_PEER_RX_BUSY_CONDITION) && + start != end && skb_peek(&lapb->write_queue)) { + lapb->vs = start; + + /* + * Dequeue the frame and copy it. + */ + skb = skb_dequeue(&lapb->write_queue); + + do { + skbn = skb_copy(skb, GFP_ATOMIC); + if (!skbn) { + skb_queue_head(&lapb->write_queue, skb); + break; + } + + if (skb->sk) + skb_set_owner_w(skbn, skb->sk); + + /* + * Transmit the frame copy. + */ + lapb_send_iframe(lapb, skbn, LAPB_POLLOFF); + + lapb->vs = (lapb->vs + 1) % modulus; + + /* + * Requeue the original data frame. + */ + skb_queue_tail(&lapb->ack_queue, skb); + + } while (lapb->vs != end && (skb = skb_dequeue(&lapb->write_queue)) != NULL); + + lapb->condition &= ~LAPB_ACK_PENDING_CONDITION; + + if (!lapb_t1timer_running(lapb)) + lapb_start_t1timer(lapb); + } +} + +void lapb_transmit_buffer(struct lapb_cb *lapb, struct sk_buff *skb, int type) +{ + unsigned char *ptr; + + ptr = skb_push(skb, 1); + + if (lapb->mode & LAPB_MLP) { + if (lapb->mode & LAPB_DCE) { + if (type == LAPB_COMMAND) + *ptr = LAPB_ADDR_C; + if (type == LAPB_RESPONSE) + *ptr = LAPB_ADDR_D; + } else { + if (type == LAPB_COMMAND) + *ptr = LAPB_ADDR_D; + if (type == LAPB_RESPONSE) + *ptr = LAPB_ADDR_C; + } + } else { + if (lapb->mode & LAPB_DCE) { + if (type == LAPB_COMMAND) + *ptr = LAPB_ADDR_A; + if (type == LAPB_RESPONSE) + *ptr = LAPB_ADDR_B; + } else { + if (type == LAPB_COMMAND) + *ptr = LAPB_ADDR_B; + if (type == LAPB_RESPONSE) + *ptr = LAPB_ADDR_A; + } + } + + lapb_dbg(2, "(%p) S%d TX %3ph\n", lapb->dev, lapb->state, skb->data); + + if (!lapb_data_transmit(lapb, skb)) + kfree_skb(skb); +} + +void lapb_establish_data_link(struct lapb_cb *lapb) +{ + lapb->condition = 0x00; + lapb->n2count = 0; + + if (lapb->mode & LAPB_EXTENDED) { + lapb_dbg(1, "(%p) S%d TX SABME(1)\n", lapb->dev, lapb->state); + lapb_send_control(lapb, LAPB_SABME, LAPB_POLLON, LAPB_COMMAND); + } else { + lapb_dbg(1, "(%p) S%d TX SABM(1)\n", lapb->dev, lapb->state); + lapb_send_control(lapb, LAPB_SABM, LAPB_POLLON, LAPB_COMMAND); + } + + lapb_start_t1timer(lapb); + lapb_stop_t2timer(lapb); +} + +void lapb_enquiry_response(struct lapb_cb *lapb) +{ + lapb_dbg(1, "(%p) S%d TX RR(1) R%d\n", + lapb->dev, lapb->state, lapb->vr); + + lapb_send_control(lapb, LAPB_RR, LAPB_POLLON, LAPB_RESPONSE); + + lapb->condition &= ~LAPB_ACK_PENDING_CONDITION; +} + +void lapb_timeout_response(struct lapb_cb *lapb) +{ + lapb_dbg(1, "(%p) S%d TX RR(0) R%d\n", + lapb->dev, lapb->state, lapb->vr); + lapb_send_control(lapb, LAPB_RR, LAPB_POLLOFF, LAPB_RESPONSE); + + lapb->condition &= ~LAPB_ACK_PENDING_CONDITION; +} + +void lapb_check_iframes_acked(struct lapb_cb *lapb, unsigned short nr) +{ + if (lapb->vs == nr) { + lapb_frames_acked(lapb, nr); + lapb_stop_t1timer(lapb); + lapb->n2count = 0; + } else if (lapb->va != nr) { + lapb_frames_acked(lapb, nr); + lapb_start_t1timer(lapb); + } +} + +void lapb_check_need_response(struct lapb_cb *lapb, int type, int pf) +{ + if (type == LAPB_COMMAND && pf) + lapb_enquiry_response(lapb); +} diff --git a/net/lapb/lapb_subr.c b/net/lapb/lapb_subr.c new file mode 100644 index 000000000..592a22d86 --- /dev/null +++ b/net/lapb/lapb_subr.c @@ -0,0 +1,299 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * LAPB release 002 + * + * This code REQUIRES 2.1.15 or higher/ NET3.038 + * + * History + * LAPB 001 Jonathan Naylor Started Coding + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/socket.h> +#include <linux/in.h> +#include <linux/kernel.h> +#include <linux/timer.h> +#include <linux/string.h> +#include <linux/sockios.h> +#include <linux/net.h> +#include <linux/inet.h> +#include <linux/skbuff.h> +#include <linux/slab.h> +#include <net/sock.h> +#include <linux/uaccess.h> +#include <linux/fcntl.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <net/lapb.h> + +/* + * This routine purges all the queues of frames. + */ +void lapb_clear_queues(struct lapb_cb *lapb) +{ + skb_queue_purge(&lapb->write_queue); + skb_queue_purge(&lapb->ack_queue); +} + +/* + * This routine purges the input queue of those frames that have been + * acknowledged. This replaces the boxes labelled "V(a) <- N(r)" on the + * SDL diagram. + */ +void lapb_frames_acked(struct lapb_cb *lapb, unsigned short nr) +{ + struct sk_buff *skb; + int modulus; + + modulus = (lapb->mode & LAPB_EXTENDED) ? LAPB_EMODULUS : LAPB_SMODULUS; + + /* + * Remove all the ack-ed frames from the ack queue. + */ + if (lapb->va != nr) + while (skb_peek(&lapb->ack_queue) && lapb->va != nr) { + skb = skb_dequeue(&lapb->ack_queue); + kfree_skb(skb); + lapb->va = (lapb->va + 1) % modulus; + } +} + +void lapb_requeue_frames(struct lapb_cb *lapb) +{ + struct sk_buff *skb, *skb_prev = NULL; + + /* + * Requeue all the un-ack-ed frames on the output queue to be picked + * up by lapb_kick called from the timer. This arrangement handles the + * possibility of an empty output queue. + */ + while ((skb = skb_dequeue(&lapb->ack_queue)) != NULL) { + if (!skb_prev) + skb_queue_head(&lapb->write_queue, skb); + else + skb_append(skb_prev, skb, &lapb->write_queue); + skb_prev = skb; + } +} + +/* + * Validate that the value of nr is between va and vs. Return true or + * false for testing. + */ +int lapb_validate_nr(struct lapb_cb *lapb, unsigned short nr) +{ + unsigned short vc = lapb->va; + int modulus; + + modulus = (lapb->mode & LAPB_EXTENDED) ? LAPB_EMODULUS : LAPB_SMODULUS; + + while (vc != lapb->vs) { + if (nr == vc) + return 1; + vc = (vc + 1) % modulus; + } + + return nr == lapb->vs; +} + +/* + * This routine is the centralised routine for parsing the control + * information for the different frame formats. + */ +int lapb_decode(struct lapb_cb *lapb, struct sk_buff *skb, + struct lapb_frame *frame) +{ + frame->type = LAPB_ILLEGAL; + + lapb_dbg(2, "(%p) S%d RX %3ph\n", lapb->dev, lapb->state, skb->data); + + /* We always need to look at 2 bytes, sometimes we need + * to look at 3 and those cases are handled below. + */ + if (!pskb_may_pull(skb, 2)) + return -1; + + if (lapb->mode & LAPB_MLP) { + if (lapb->mode & LAPB_DCE) { + if (skb->data[0] == LAPB_ADDR_D) + frame->cr = LAPB_COMMAND; + if (skb->data[0] == LAPB_ADDR_C) + frame->cr = LAPB_RESPONSE; + } else { + if (skb->data[0] == LAPB_ADDR_C) + frame->cr = LAPB_COMMAND; + if (skb->data[0] == LAPB_ADDR_D) + frame->cr = LAPB_RESPONSE; + } + } else { + if (lapb->mode & LAPB_DCE) { + if (skb->data[0] == LAPB_ADDR_B) + frame->cr = LAPB_COMMAND; + if (skb->data[0] == LAPB_ADDR_A) + frame->cr = LAPB_RESPONSE; + } else { + if (skb->data[0] == LAPB_ADDR_A) + frame->cr = LAPB_COMMAND; + if (skb->data[0] == LAPB_ADDR_B) + frame->cr = LAPB_RESPONSE; + } + } + + skb_pull(skb, 1); + + if (lapb->mode & LAPB_EXTENDED) { + if (!(skb->data[0] & LAPB_S)) { + if (!pskb_may_pull(skb, 2)) + return -1; + /* + * I frame - carries NR/NS/PF + */ + frame->type = LAPB_I; + frame->ns = (skb->data[0] >> 1) & 0x7F; + frame->nr = (skb->data[1] >> 1) & 0x7F; + frame->pf = skb->data[1] & LAPB_EPF; + frame->control[0] = skb->data[0]; + frame->control[1] = skb->data[1]; + skb_pull(skb, 2); + } else if ((skb->data[0] & LAPB_U) == 1) { + if (!pskb_may_pull(skb, 2)) + return -1; + /* + * S frame - take out PF/NR + */ + frame->type = skb->data[0] & 0x0F; + frame->nr = (skb->data[1] >> 1) & 0x7F; + frame->pf = skb->data[1] & LAPB_EPF; + frame->control[0] = skb->data[0]; + frame->control[1] = skb->data[1]; + skb_pull(skb, 2); + } else if ((skb->data[0] & LAPB_U) == 3) { + /* + * U frame - take out PF + */ + frame->type = skb->data[0] & ~LAPB_SPF; + frame->pf = skb->data[0] & LAPB_SPF; + frame->control[0] = skb->data[0]; + frame->control[1] = 0x00; + skb_pull(skb, 1); + } + } else { + if (!(skb->data[0] & LAPB_S)) { + /* + * I frame - carries NR/NS/PF + */ + frame->type = LAPB_I; + frame->ns = (skb->data[0] >> 1) & 0x07; + frame->nr = (skb->data[0] >> 5) & 0x07; + frame->pf = skb->data[0] & LAPB_SPF; + } else if ((skb->data[0] & LAPB_U) == 1) { + /* + * S frame - take out PF/NR + */ + frame->type = skb->data[0] & 0x0F; + frame->nr = (skb->data[0] >> 5) & 0x07; + frame->pf = skb->data[0] & LAPB_SPF; + } else if ((skb->data[0] & LAPB_U) == 3) { + /* + * U frame - take out PF + */ + frame->type = skb->data[0] & ~LAPB_SPF; + frame->pf = skb->data[0] & LAPB_SPF; + } + + frame->control[0] = skb->data[0]; + + skb_pull(skb, 1); + } + + return 0; +} + +/* + * This routine is called when the HDLC layer internally generates a + * command or response for the remote machine ( eg. RR, UA etc. ). + * Only supervisory or unnumbered frames are processed, FRMRs are handled + * by lapb_transmit_frmr below. + */ +void lapb_send_control(struct lapb_cb *lapb, int frametype, + int poll_bit, int type) +{ + struct sk_buff *skb; + unsigned char *dptr; + + if ((skb = alloc_skb(LAPB_HEADER_LEN + 3, GFP_ATOMIC)) == NULL) + return; + + skb_reserve(skb, LAPB_HEADER_LEN + 1); + + if (lapb->mode & LAPB_EXTENDED) { + if ((frametype & LAPB_U) == LAPB_U) { + dptr = skb_put(skb, 1); + *dptr = frametype; + *dptr |= poll_bit ? LAPB_SPF : 0; + } else { + dptr = skb_put(skb, 2); + dptr[0] = frametype; + dptr[1] = (lapb->vr << 1); + dptr[1] |= poll_bit ? LAPB_EPF : 0; + } + } else { + dptr = skb_put(skb, 1); + *dptr = frametype; + *dptr |= poll_bit ? LAPB_SPF : 0; + if ((frametype & LAPB_U) == LAPB_S) /* S frames carry NR */ + *dptr |= (lapb->vr << 5); + } + + lapb_transmit_buffer(lapb, skb, type); +} + +/* + * This routine generates FRMRs based on information previously stored in + * the LAPB control block. + */ +void lapb_transmit_frmr(struct lapb_cb *lapb) +{ + struct sk_buff *skb; + unsigned char *dptr; + + if ((skb = alloc_skb(LAPB_HEADER_LEN + 7, GFP_ATOMIC)) == NULL) + return; + + skb_reserve(skb, LAPB_HEADER_LEN + 1); + + if (lapb->mode & LAPB_EXTENDED) { + dptr = skb_put(skb, 6); + *dptr++ = LAPB_FRMR; + *dptr++ = lapb->frmr_data.control[0]; + *dptr++ = lapb->frmr_data.control[1]; + *dptr++ = (lapb->vs << 1) & 0xFE; + *dptr = (lapb->vr << 1) & 0xFE; + if (lapb->frmr_data.cr == LAPB_RESPONSE) + *dptr |= 0x01; + dptr++; + *dptr++ = lapb->frmr_type; + + lapb_dbg(1, "(%p) S%d TX FRMR %5ph\n", + lapb->dev, lapb->state, + &skb->data[1]); + } else { + dptr = skb_put(skb, 4); + *dptr++ = LAPB_FRMR; + *dptr++ = lapb->frmr_data.control[0]; + *dptr = (lapb->vs << 1) & 0x0E; + *dptr |= (lapb->vr << 5) & 0xE0; + if (lapb->frmr_data.cr == LAPB_RESPONSE) + *dptr |= 0x10; + dptr++; + *dptr++ = lapb->frmr_type; + + lapb_dbg(1, "(%p) S%d TX FRMR %3ph\n", + lapb->dev, lapb->state, &skb->data[1]); + } + + lapb_transmit_buffer(lapb, skb, LAPB_RESPONSE); +} diff --git a/net/lapb/lapb_timer.c b/net/lapb/lapb_timer.c new file mode 100644 index 000000000..9fde6cf20 --- /dev/null +++ b/net/lapb/lapb_timer.c @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * LAPB release 002 + * + * This code REQUIRES 2.1.15 or higher/ NET3.038 + * + * History + * LAPB 001 Jonathan Naylor Started Coding + * LAPB 002 Jonathan Naylor New timer architecture. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/socket.h> +#include <linux/in.h> +#include <linux/kernel.h> +#include <linux/jiffies.h> +#include <linux/timer.h> +#include <linux/string.h> +#include <linux/sockios.h> +#include <linux/net.h> +#include <linux/inet.h> +#include <linux/skbuff.h> +#include <net/sock.h> +#include <linux/uaccess.h> +#include <linux/fcntl.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <net/lapb.h> + +static void lapb_t1timer_expiry(struct timer_list *); +static void lapb_t2timer_expiry(struct timer_list *); + +void lapb_start_t1timer(struct lapb_cb *lapb) +{ + timer_delete(&lapb->t1timer); + + lapb->t1timer.function = lapb_t1timer_expiry; + lapb->t1timer.expires = jiffies + lapb->t1; + + lapb->t1timer_running = true; + add_timer(&lapb->t1timer); +} + +void lapb_start_t2timer(struct lapb_cb *lapb) +{ + timer_delete(&lapb->t2timer); + + lapb->t2timer.function = lapb_t2timer_expiry; + lapb->t2timer.expires = jiffies + lapb->t2; + + lapb->t2timer_running = true; + add_timer(&lapb->t2timer); +} + +void lapb_stop_t1timer(struct lapb_cb *lapb) +{ + lapb->t1timer_running = false; + timer_delete(&lapb->t1timer); +} + +void lapb_stop_t2timer(struct lapb_cb *lapb) +{ + lapb->t2timer_running = false; + timer_delete(&lapb->t2timer); +} + +int lapb_t1timer_running(struct lapb_cb *lapb) +{ + return lapb->t1timer_running; +} + +static void lapb_t2timer_expiry(struct timer_list *t) +{ + struct lapb_cb *lapb = timer_container_of(lapb, t, t2timer); + + spin_lock_bh(&lapb->lock); + if (timer_pending(&lapb->t2timer)) /* A new timer has been set up */ + goto out; + if (!lapb->t2timer_running) /* The timer has been stopped */ + goto out; + + if (lapb->condition & LAPB_ACK_PENDING_CONDITION) { + lapb->condition &= ~LAPB_ACK_PENDING_CONDITION; + lapb_timeout_response(lapb); + } + lapb->t2timer_running = false; + +out: + spin_unlock_bh(&lapb->lock); +} + +static void lapb_t1timer_expiry(struct timer_list *t) +{ + struct lapb_cb *lapb = timer_container_of(lapb, t, t1timer); + + spin_lock_bh(&lapb->lock); + if (timer_pending(&lapb->t1timer)) /* A new timer has been set up */ + goto out; + if (!lapb->t1timer_running) /* The timer has been stopped */ + goto out; + + switch (lapb->state) { + + /* + * If we are a DCE, send DM up to N2 times, then switch to + * STATE_1 and send SABM(E). + */ + case LAPB_STATE_0: + if (lapb->mode & LAPB_DCE && + lapb->n2count != lapb->n2) { + lapb->n2count++; + lapb_send_control(lapb, LAPB_DM, LAPB_POLLOFF, LAPB_RESPONSE); + } else { + lapb->state = LAPB_STATE_1; + lapb_establish_data_link(lapb); + } + break; + + /* + * Awaiting connection state, send SABM(E), up to N2 times. + */ + case LAPB_STATE_1: + if (lapb->n2count == lapb->n2) { + lapb_clear_queues(lapb); + lapb->state = LAPB_STATE_0; + lapb_disconnect_indication(lapb, LAPB_TIMEDOUT); + lapb_dbg(0, "(%p) S1 -> S0\n", lapb->dev); + lapb->t1timer_running = false; + goto out; + } else { + lapb->n2count++; + if (lapb->mode & LAPB_EXTENDED) { + lapb_dbg(1, "(%p) S1 TX SABME(1)\n", + lapb->dev); + lapb_send_control(lapb, LAPB_SABME, LAPB_POLLON, LAPB_COMMAND); + } else { + lapb_dbg(1, "(%p) S1 TX SABM(1)\n", + lapb->dev); + lapb_send_control(lapb, LAPB_SABM, LAPB_POLLON, LAPB_COMMAND); + } + } + break; + + /* + * Awaiting disconnection state, send DISC, up to N2 times. + */ + case LAPB_STATE_2: + if (lapb->n2count == lapb->n2) { + lapb_clear_queues(lapb); + lapb->state = LAPB_STATE_0; + lapb_disconnect_confirmation(lapb, LAPB_TIMEDOUT); + lapb_dbg(0, "(%p) S2 -> S0\n", lapb->dev); + lapb->t1timer_running = false; + goto out; + } else { + lapb->n2count++; + lapb_dbg(1, "(%p) S2 TX DISC(1)\n", lapb->dev); + lapb_send_control(lapb, LAPB_DISC, LAPB_POLLON, LAPB_COMMAND); + } + break; + + /* + * Data transfer state, restransmit I frames, up to N2 times. + */ + case LAPB_STATE_3: + if (lapb->n2count == lapb->n2) { + lapb_clear_queues(lapb); + lapb->state = LAPB_STATE_0; + lapb_stop_t2timer(lapb); + lapb_disconnect_indication(lapb, LAPB_TIMEDOUT); + lapb_dbg(0, "(%p) S3 -> S0\n", lapb->dev); + lapb->t1timer_running = false; + goto out; + } else { + lapb->n2count++; + lapb_requeue_frames(lapb); + lapb_kick(lapb); + } + break; + + /* + * Frame reject state, restransmit FRMR frames, up to N2 times. + */ + case LAPB_STATE_4: + if (lapb->n2count == lapb->n2) { + lapb_clear_queues(lapb); + lapb->state = LAPB_STATE_0; + lapb_disconnect_indication(lapb, LAPB_TIMEDOUT); + lapb_dbg(0, "(%p) S4 -> S0\n", lapb->dev); + lapb->t1timer_running = false; + goto out; + } else { + lapb->n2count++; + lapb_transmit_frmr(lapb); + } + break; + } + + lapb_start_t1timer(lapb); + +out: + spin_unlock_bh(&lapb->lock); +} |
