162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * PPP synchronous tty channel driver for Linux. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * This is a ppp channel driver that can be used with tty device drivers 662306a36Sopenharmony_ci * that are frame oriented, such as synchronous HDLC devices. 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * Complete PPP frames without encoding/decoding are exchanged between 962306a36Sopenharmony_ci * the channel driver and the device driver. 1062306a36Sopenharmony_ci * 1162306a36Sopenharmony_ci * The async map IOCTL codes are implemented to keep the user mode 1262306a36Sopenharmony_ci * applications happy if they call them. Synchronous PPP does not use 1362306a36Sopenharmony_ci * the async maps. 1462306a36Sopenharmony_ci * 1562306a36Sopenharmony_ci * Copyright 1999 Paul Mackerras. 1662306a36Sopenharmony_ci * 1762306a36Sopenharmony_ci * Also touched by the grubby hands of Paul Fulghum paulkf@microgate.com 1862306a36Sopenharmony_ci * 1962306a36Sopenharmony_ci * This driver provides the encapsulation and framing for sending 2062306a36Sopenharmony_ci * and receiving PPP frames over sync serial lines. It relies on 2162306a36Sopenharmony_ci * the generic PPP layer to give it frames to send and to process 2262306a36Sopenharmony_ci * received frames. It implements the PPP line discipline. 2362306a36Sopenharmony_ci * 2462306a36Sopenharmony_ci * Part of the code in this driver was inspired by the old async-only 2562306a36Sopenharmony_ci * PPP driver, written by Michael Callahan and Al Longyear, and 2662306a36Sopenharmony_ci * subsequently hacked by Paul Mackerras. 2762306a36Sopenharmony_ci * 2862306a36Sopenharmony_ci * ==FILEVERSION 20040616== 2962306a36Sopenharmony_ci */ 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci#include <linux/module.h> 3262306a36Sopenharmony_ci#include <linux/kernel.h> 3362306a36Sopenharmony_ci#include <linux/skbuff.h> 3462306a36Sopenharmony_ci#include <linux/tty.h> 3562306a36Sopenharmony_ci#include <linux/netdevice.h> 3662306a36Sopenharmony_ci#include <linux/poll.h> 3762306a36Sopenharmony_ci#include <linux/ppp_defs.h> 3862306a36Sopenharmony_ci#include <linux/ppp-ioctl.h> 3962306a36Sopenharmony_ci#include <linux/ppp_channel.h> 4062306a36Sopenharmony_ci#include <linux/spinlock.h> 4162306a36Sopenharmony_ci#include <linux/completion.h> 4262306a36Sopenharmony_ci#include <linux/init.h> 4362306a36Sopenharmony_ci#include <linux/interrupt.h> 4462306a36Sopenharmony_ci#include <linux/slab.h> 4562306a36Sopenharmony_ci#include <linux/refcount.h> 4662306a36Sopenharmony_ci#include <asm/unaligned.h> 4762306a36Sopenharmony_ci#include <linux/uaccess.h> 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci#define PPP_VERSION "2.4.2" 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci/* Structure for storing local state. */ 5262306a36Sopenharmony_cistruct syncppp { 5362306a36Sopenharmony_ci struct tty_struct *tty; 5462306a36Sopenharmony_ci unsigned int flags; 5562306a36Sopenharmony_ci unsigned int rbits; 5662306a36Sopenharmony_ci int mru; 5762306a36Sopenharmony_ci spinlock_t xmit_lock; 5862306a36Sopenharmony_ci spinlock_t recv_lock; 5962306a36Sopenharmony_ci unsigned long xmit_flags; 6062306a36Sopenharmony_ci u32 xaccm[8]; 6162306a36Sopenharmony_ci u32 raccm; 6262306a36Sopenharmony_ci unsigned int bytes_sent; 6362306a36Sopenharmony_ci unsigned int bytes_rcvd; 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci struct sk_buff *tpkt; 6662306a36Sopenharmony_ci unsigned long last_xmit; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci struct sk_buff_head rqueue; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci struct tasklet_struct tsk; 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci refcount_t refcnt; 7362306a36Sopenharmony_ci struct completion dead_cmp; 7462306a36Sopenharmony_ci struct ppp_channel chan; /* interface to generic ppp layer */ 7562306a36Sopenharmony_ci}; 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci/* Bit numbers in xmit_flags */ 7862306a36Sopenharmony_ci#define XMIT_WAKEUP 0 7962306a36Sopenharmony_ci#define XMIT_FULL 1 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci/* Bits in rbits */ 8262306a36Sopenharmony_ci#define SC_RCV_BITS (SC_RCV_B7_1|SC_RCV_B7_0|SC_RCV_ODDP|SC_RCV_EVNP) 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci#define PPPSYNC_MAX_RQLEN 32 /* arbitrary */ 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci/* 8762306a36Sopenharmony_ci * Prototypes. 8862306a36Sopenharmony_ci */ 8962306a36Sopenharmony_cistatic struct sk_buff* ppp_sync_txmunge(struct syncppp *ap, struct sk_buff *); 9062306a36Sopenharmony_cistatic int ppp_sync_send(struct ppp_channel *chan, struct sk_buff *skb); 9162306a36Sopenharmony_cistatic int ppp_sync_ioctl(struct ppp_channel *chan, unsigned int cmd, 9262306a36Sopenharmony_ci unsigned long arg); 9362306a36Sopenharmony_cistatic void ppp_sync_process(struct tasklet_struct *t); 9462306a36Sopenharmony_cistatic int ppp_sync_push(struct syncppp *ap); 9562306a36Sopenharmony_cistatic void ppp_sync_flush_output(struct syncppp *ap); 9662306a36Sopenharmony_cistatic void ppp_sync_input(struct syncppp *ap, const u8 *buf, const u8 *flags, 9762306a36Sopenharmony_ci int count); 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_cistatic const struct ppp_channel_ops sync_ops = { 10062306a36Sopenharmony_ci .start_xmit = ppp_sync_send, 10162306a36Sopenharmony_ci .ioctl = ppp_sync_ioctl, 10262306a36Sopenharmony_ci}; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci/* 10562306a36Sopenharmony_ci * Utility procedure to print a buffer in hex/ascii 10662306a36Sopenharmony_ci */ 10762306a36Sopenharmony_cistatic void 10862306a36Sopenharmony_cippp_print_buffer (const char *name, const __u8 *buf, int count) 10962306a36Sopenharmony_ci{ 11062306a36Sopenharmony_ci if (name != NULL) 11162306a36Sopenharmony_ci printk(KERN_DEBUG "ppp_synctty: %s, count = %d\n", name, count); 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci print_hex_dump_bytes("", DUMP_PREFIX_NONE, buf, count); 11462306a36Sopenharmony_ci} 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci/* 11862306a36Sopenharmony_ci * Routines implementing the synchronous PPP line discipline. 11962306a36Sopenharmony_ci */ 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci/* 12262306a36Sopenharmony_ci * We have a potential race on dereferencing tty->disc_data, 12362306a36Sopenharmony_ci * because the tty layer provides no locking at all - thus one 12462306a36Sopenharmony_ci * cpu could be running ppp_synctty_receive while another 12562306a36Sopenharmony_ci * calls ppp_synctty_close, which zeroes tty->disc_data and 12662306a36Sopenharmony_ci * frees the memory that ppp_synctty_receive is using. The best 12762306a36Sopenharmony_ci * way to fix this is to use a rwlock in the tty struct, but for now 12862306a36Sopenharmony_ci * we use a single global rwlock for all ttys in ppp line discipline. 12962306a36Sopenharmony_ci * 13062306a36Sopenharmony_ci * FIXME: Fixed in tty_io nowadays. 13162306a36Sopenharmony_ci */ 13262306a36Sopenharmony_cistatic DEFINE_RWLOCK(disc_data_lock); 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_cistatic struct syncppp *sp_get(struct tty_struct *tty) 13562306a36Sopenharmony_ci{ 13662306a36Sopenharmony_ci struct syncppp *ap; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci read_lock(&disc_data_lock); 13962306a36Sopenharmony_ci ap = tty->disc_data; 14062306a36Sopenharmony_ci if (ap != NULL) 14162306a36Sopenharmony_ci refcount_inc(&ap->refcnt); 14262306a36Sopenharmony_ci read_unlock(&disc_data_lock); 14362306a36Sopenharmony_ci return ap; 14462306a36Sopenharmony_ci} 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_cistatic void sp_put(struct syncppp *ap) 14762306a36Sopenharmony_ci{ 14862306a36Sopenharmony_ci if (refcount_dec_and_test(&ap->refcnt)) 14962306a36Sopenharmony_ci complete(&ap->dead_cmp); 15062306a36Sopenharmony_ci} 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci/* 15362306a36Sopenharmony_ci * Called when a tty is put into sync-PPP line discipline. 15462306a36Sopenharmony_ci */ 15562306a36Sopenharmony_cistatic int 15662306a36Sopenharmony_cippp_sync_open(struct tty_struct *tty) 15762306a36Sopenharmony_ci{ 15862306a36Sopenharmony_ci struct syncppp *ap; 15962306a36Sopenharmony_ci int err; 16062306a36Sopenharmony_ci int speed; 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci if (tty->ops->write == NULL) 16362306a36Sopenharmony_ci return -EOPNOTSUPP; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci ap = kzalloc(sizeof(*ap), GFP_KERNEL); 16662306a36Sopenharmony_ci err = -ENOMEM; 16762306a36Sopenharmony_ci if (!ap) 16862306a36Sopenharmony_ci goto out; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci /* initialize the syncppp structure */ 17162306a36Sopenharmony_ci ap->tty = tty; 17262306a36Sopenharmony_ci ap->mru = PPP_MRU; 17362306a36Sopenharmony_ci spin_lock_init(&ap->xmit_lock); 17462306a36Sopenharmony_ci spin_lock_init(&ap->recv_lock); 17562306a36Sopenharmony_ci ap->xaccm[0] = ~0U; 17662306a36Sopenharmony_ci ap->xaccm[3] = 0x60000000U; 17762306a36Sopenharmony_ci ap->raccm = ~0U; 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci skb_queue_head_init(&ap->rqueue); 18062306a36Sopenharmony_ci tasklet_setup(&ap->tsk, ppp_sync_process); 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci refcount_set(&ap->refcnt, 1); 18362306a36Sopenharmony_ci init_completion(&ap->dead_cmp); 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci ap->chan.private = ap; 18662306a36Sopenharmony_ci ap->chan.ops = &sync_ops; 18762306a36Sopenharmony_ci ap->chan.mtu = PPP_MRU; 18862306a36Sopenharmony_ci ap->chan.hdrlen = 2; /* for A/C bytes */ 18962306a36Sopenharmony_ci speed = tty_get_baud_rate(tty); 19062306a36Sopenharmony_ci ap->chan.speed = speed; 19162306a36Sopenharmony_ci err = ppp_register_channel(&ap->chan); 19262306a36Sopenharmony_ci if (err) 19362306a36Sopenharmony_ci goto out_free; 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci tty->disc_data = ap; 19662306a36Sopenharmony_ci tty->receive_room = 65536; 19762306a36Sopenharmony_ci return 0; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci out_free: 20062306a36Sopenharmony_ci kfree(ap); 20162306a36Sopenharmony_ci out: 20262306a36Sopenharmony_ci return err; 20362306a36Sopenharmony_ci} 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci/* 20662306a36Sopenharmony_ci * Called when the tty is put into another line discipline 20762306a36Sopenharmony_ci * or it hangs up. We have to wait for any cpu currently 20862306a36Sopenharmony_ci * executing in any of the other ppp_synctty_* routines to 20962306a36Sopenharmony_ci * finish before we can call ppp_unregister_channel and free 21062306a36Sopenharmony_ci * the syncppp struct. This routine must be called from 21162306a36Sopenharmony_ci * process context, not interrupt or softirq context. 21262306a36Sopenharmony_ci */ 21362306a36Sopenharmony_cistatic void 21462306a36Sopenharmony_cippp_sync_close(struct tty_struct *tty) 21562306a36Sopenharmony_ci{ 21662306a36Sopenharmony_ci struct syncppp *ap; 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci write_lock_irq(&disc_data_lock); 21962306a36Sopenharmony_ci ap = tty->disc_data; 22062306a36Sopenharmony_ci tty->disc_data = NULL; 22162306a36Sopenharmony_ci write_unlock_irq(&disc_data_lock); 22262306a36Sopenharmony_ci if (!ap) 22362306a36Sopenharmony_ci return; 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci /* 22662306a36Sopenharmony_ci * We have now ensured that nobody can start using ap from now 22762306a36Sopenharmony_ci * on, but we have to wait for all existing users to finish. 22862306a36Sopenharmony_ci * Note that ppp_unregister_channel ensures that no calls to 22962306a36Sopenharmony_ci * our channel ops (i.e. ppp_sync_send/ioctl) are in progress 23062306a36Sopenharmony_ci * by the time it returns. 23162306a36Sopenharmony_ci */ 23262306a36Sopenharmony_ci if (!refcount_dec_and_test(&ap->refcnt)) 23362306a36Sopenharmony_ci wait_for_completion(&ap->dead_cmp); 23462306a36Sopenharmony_ci tasklet_kill(&ap->tsk); 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci ppp_unregister_channel(&ap->chan); 23762306a36Sopenharmony_ci skb_queue_purge(&ap->rqueue); 23862306a36Sopenharmony_ci kfree_skb(ap->tpkt); 23962306a36Sopenharmony_ci kfree(ap); 24062306a36Sopenharmony_ci} 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci/* 24362306a36Sopenharmony_ci * Called on tty hangup in process context. 24462306a36Sopenharmony_ci * 24562306a36Sopenharmony_ci * Wait for I/O to driver to complete and unregister PPP channel. 24662306a36Sopenharmony_ci * This is already done by the close routine, so just call that. 24762306a36Sopenharmony_ci */ 24862306a36Sopenharmony_cistatic void ppp_sync_hangup(struct tty_struct *tty) 24962306a36Sopenharmony_ci{ 25062306a36Sopenharmony_ci ppp_sync_close(tty); 25162306a36Sopenharmony_ci} 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_ci/* 25462306a36Sopenharmony_ci * Read does nothing - no data is ever available this way. 25562306a36Sopenharmony_ci * Pppd reads and writes packets via /dev/ppp instead. 25662306a36Sopenharmony_ci */ 25762306a36Sopenharmony_cistatic ssize_t 25862306a36Sopenharmony_cippp_sync_read(struct tty_struct *tty, struct file *file, u8 *buf, size_t count, 25962306a36Sopenharmony_ci void **cookie, unsigned long offset) 26062306a36Sopenharmony_ci{ 26162306a36Sopenharmony_ci return -EAGAIN; 26262306a36Sopenharmony_ci} 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci/* 26562306a36Sopenharmony_ci * Write on the tty does nothing, the packets all come in 26662306a36Sopenharmony_ci * from the ppp generic stuff. 26762306a36Sopenharmony_ci */ 26862306a36Sopenharmony_cistatic ssize_t 26962306a36Sopenharmony_cippp_sync_write(struct tty_struct *tty, struct file *file, const u8 *buf, 27062306a36Sopenharmony_ci size_t count) 27162306a36Sopenharmony_ci{ 27262306a36Sopenharmony_ci return -EAGAIN; 27362306a36Sopenharmony_ci} 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_cistatic int 27662306a36Sopenharmony_cippp_synctty_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg) 27762306a36Sopenharmony_ci{ 27862306a36Sopenharmony_ci struct syncppp *ap = sp_get(tty); 27962306a36Sopenharmony_ci int __user *p = (int __user *)arg; 28062306a36Sopenharmony_ci int err, val; 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci if (!ap) 28362306a36Sopenharmony_ci return -ENXIO; 28462306a36Sopenharmony_ci err = -EFAULT; 28562306a36Sopenharmony_ci switch (cmd) { 28662306a36Sopenharmony_ci case PPPIOCGCHAN: 28762306a36Sopenharmony_ci err = -EFAULT; 28862306a36Sopenharmony_ci if (put_user(ppp_channel_index(&ap->chan), p)) 28962306a36Sopenharmony_ci break; 29062306a36Sopenharmony_ci err = 0; 29162306a36Sopenharmony_ci break; 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci case PPPIOCGUNIT: 29462306a36Sopenharmony_ci err = -EFAULT; 29562306a36Sopenharmony_ci if (put_user(ppp_unit_number(&ap->chan), p)) 29662306a36Sopenharmony_ci break; 29762306a36Sopenharmony_ci err = 0; 29862306a36Sopenharmony_ci break; 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci case TCFLSH: 30162306a36Sopenharmony_ci /* flush our buffers and the serial port's buffer */ 30262306a36Sopenharmony_ci if (arg == TCIOFLUSH || arg == TCOFLUSH) 30362306a36Sopenharmony_ci ppp_sync_flush_output(ap); 30462306a36Sopenharmony_ci err = n_tty_ioctl_helper(tty, cmd, arg); 30562306a36Sopenharmony_ci break; 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_ci case FIONREAD: 30862306a36Sopenharmony_ci val = 0; 30962306a36Sopenharmony_ci if (put_user(val, p)) 31062306a36Sopenharmony_ci break; 31162306a36Sopenharmony_ci err = 0; 31262306a36Sopenharmony_ci break; 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_ci default: 31562306a36Sopenharmony_ci err = tty_mode_ioctl(tty, cmd, arg); 31662306a36Sopenharmony_ci break; 31762306a36Sopenharmony_ci } 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci sp_put(ap); 32062306a36Sopenharmony_ci return err; 32162306a36Sopenharmony_ci} 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ci/* May sleep, don't call from interrupt level or with interrupts disabled */ 32462306a36Sopenharmony_cistatic void 32562306a36Sopenharmony_cippp_sync_receive(struct tty_struct *tty, const u8 *buf, const u8 *cflags, 32662306a36Sopenharmony_ci size_t count) 32762306a36Sopenharmony_ci{ 32862306a36Sopenharmony_ci struct syncppp *ap = sp_get(tty); 32962306a36Sopenharmony_ci unsigned long flags; 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_ci if (!ap) 33262306a36Sopenharmony_ci return; 33362306a36Sopenharmony_ci spin_lock_irqsave(&ap->recv_lock, flags); 33462306a36Sopenharmony_ci ppp_sync_input(ap, buf, cflags, count); 33562306a36Sopenharmony_ci spin_unlock_irqrestore(&ap->recv_lock, flags); 33662306a36Sopenharmony_ci if (!skb_queue_empty(&ap->rqueue)) 33762306a36Sopenharmony_ci tasklet_schedule(&ap->tsk); 33862306a36Sopenharmony_ci sp_put(ap); 33962306a36Sopenharmony_ci tty_unthrottle(tty); 34062306a36Sopenharmony_ci} 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_cistatic void 34362306a36Sopenharmony_cippp_sync_wakeup(struct tty_struct *tty) 34462306a36Sopenharmony_ci{ 34562306a36Sopenharmony_ci struct syncppp *ap = sp_get(tty); 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ci clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); 34862306a36Sopenharmony_ci if (!ap) 34962306a36Sopenharmony_ci return; 35062306a36Sopenharmony_ci set_bit(XMIT_WAKEUP, &ap->xmit_flags); 35162306a36Sopenharmony_ci tasklet_schedule(&ap->tsk); 35262306a36Sopenharmony_ci sp_put(ap); 35362306a36Sopenharmony_ci} 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_cistatic struct tty_ldisc_ops ppp_sync_ldisc = { 35762306a36Sopenharmony_ci .owner = THIS_MODULE, 35862306a36Sopenharmony_ci .num = N_SYNC_PPP, 35962306a36Sopenharmony_ci .name = "pppsync", 36062306a36Sopenharmony_ci .open = ppp_sync_open, 36162306a36Sopenharmony_ci .close = ppp_sync_close, 36262306a36Sopenharmony_ci .hangup = ppp_sync_hangup, 36362306a36Sopenharmony_ci .read = ppp_sync_read, 36462306a36Sopenharmony_ci .write = ppp_sync_write, 36562306a36Sopenharmony_ci .ioctl = ppp_synctty_ioctl, 36662306a36Sopenharmony_ci .receive_buf = ppp_sync_receive, 36762306a36Sopenharmony_ci .write_wakeup = ppp_sync_wakeup, 36862306a36Sopenharmony_ci}; 36962306a36Sopenharmony_ci 37062306a36Sopenharmony_cistatic int __init 37162306a36Sopenharmony_cippp_sync_init(void) 37262306a36Sopenharmony_ci{ 37362306a36Sopenharmony_ci int err; 37462306a36Sopenharmony_ci 37562306a36Sopenharmony_ci err = tty_register_ldisc(&ppp_sync_ldisc); 37662306a36Sopenharmony_ci if (err != 0) 37762306a36Sopenharmony_ci printk(KERN_ERR "PPP_sync: error %d registering line disc.\n", 37862306a36Sopenharmony_ci err); 37962306a36Sopenharmony_ci return err; 38062306a36Sopenharmony_ci} 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_ci/* 38362306a36Sopenharmony_ci * The following routines provide the PPP channel interface. 38462306a36Sopenharmony_ci */ 38562306a36Sopenharmony_cistatic int 38662306a36Sopenharmony_cippp_sync_ioctl(struct ppp_channel *chan, unsigned int cmd, unsigned long arg) 38762306a36Sopenharmony_ci{ 38862306a36Sopenharmony_ci struct syncppp *ap = chan->private; 38962306a36Sopenharmony_ci int err, val; 39062306a36Sopenharmony_ci u32 accm[8]; 39162306a36Sopenharmony_ci void __user *argp = (void __user *)arg; 39262306a36Sopenharmony_ci u32 __user *p = argp; 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_ci err = -EFAULT; 39562306a36Sopenharmony_ci switch (cmd) { 39662306a36Sopenharmony_ci case PPPIOCGFLAGS: 39762306a36Sopenharmony_ci val = ap->flags | ap->rbits; 39862306a36Sopenharmony_ci if (put_user(val, (int __user *) argp)) 39962306a36Sopenharmony_ci break; 40062306a36Sopenharmony_ci err = 0; 40162306a36Sopenharmony_ci break; 40262306a36Sopenharmony_ci case PPPIOCSFLAGS: 40362306a36Sopenharmony_ci if (get_user(val, (int __user *) argp)) 40462306a36Sopenharmony_ci break; 40562306a36Sopenharmony_ci ap->flags = val & ~SC_RCV_BITS; 40662306a36Sopenharmony_ci spin_lock_irq(&ap->recv_lock); 40762306a36Sopenharmony_ci ap->rbits = val & SC_RCV_BITS; 40862306a36Sopenharmony_ci spin_unlock_irq(&ap->recv_lock); 40962306a36Sopenharmony_ci err = 0; 41062306a36Sopenharmony_ci break; 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_ci case PPPIOCGASYNCMAP: 41362306a36Sopenharmony_ci if (put_user(ap->xaccm[0], p)) 41462306a36Sopenharmony_ci break; 41562306a36Sopenharmony_ci err = 0; 41662306a36Sopenharmony_ci break; 41762306a36Sopenharmony_ci case PPPIOCSASYNCMAP: 41862306a36Sopenharmony_ci if (get_user(ap->xaccm[0], p)) 41962306a36Sopenharmony_ci break; 42062306a36Sopenharmony_ci err = 0; 42162306a36Sopenharmony_ci break; 42262306a36Sopenharmony_ci 42362306a36Sopenharmony_ci case PPPIOCGRASYNCMAP: 42462306a36Sopenharmony_ci if (put_user(ap->raccm, p)) 42562306a36Sopenharmony_ci break; 42662306a36Sopenharmony_ci err = 0; 42762306a36Sopenharmony_ci break; 42862306a36Sopenharmony_ci case PPPIOCSRASYNCMAP: 42962306a36Sopenharmony_ci if (get_user(ap->raccm, p)) 43062306a36Sopenharmony_ci break; 43162306a36Sopenharmony_ci err = 0; 43262306a36Sopenharmony_ci break; 43362306a36Sopenharmony_ci 43462306a36Sopenharmony_ci case PPPIOCGXASYNCMAP: 43562306a36Sopenharmony_ci if (copy_to_user(argp, ap->xaccm, sizeof(ap->xaccm))) 43662306a36Sopenharmony_ci break; 43762306a36Sopenharmony_ci err = 0; 43862306a36Sopenharmony_ci break; 43962306a36Sopenharmony_ci case PPPIOCSXASYNCMAP: 44062306a36Sopenharmony_ci if (copy_from_user(accm, argp, sizeof(accm))) 44162306a36Sopenharmony_ci break; 44262306a36Sopenharmony_ci accm[2] &= ~0x40000000U; /* can't escape 0x5e */ 44362306a36Sopenharmony_ci accm[3] |= 0x60000000U; /* must escape 0x7d, 0x7e */ 44462306a36Sopenharmony_ci memcpy(ap->xaccm, accm, sizeof(ap->xaccm)); 44562306a36Sopenharmony_ci err = 0; 44662306a36Sopenharmony_ci break; 44762306a36Sopenharmony_ci 44862306a36Sopenharmony_ci case PPPIOCGMRU: 44962306a36Sopenharmony_ci if (put_user(ap->mru, (int __user *) argp)) 45062306a36Sopenharmony_ci break; 45162306a36Sopenharmony_ci err = 0; 45262306a36Sopenharmony_ci break; 45362306a36Sopenharmony_ci case PPPIOCSMRU: 45462306a36Sopenharmony_ci if (get_user(val, (int __user *) argp)) 45562306a36Sopenharmony_ci break; 45662306a36Sopenharmony_ci if (val > U16_MAX) { 45762306a36Sopenharmony_ci err = -EINVAL; 45862306a36Sopenharmony_ci break; 45962306a36Sopenharmony_ci } 46062306a36Sopenharmony_ci if (val < PPP_MRU) 46162306a36Sopenharmony_ci val = PPP_MRU; 46262306a36Sopenharmony_ci ap->mru = val; 46362306a36Sopenharmony_ci err = 0; 46462306a36Sopenharmony_ci break; 46562306a36Sopenharmony_ci 46662306a36Sopenharmony_ci default: 46762306a36Sopenharmony_ci err = -ENOTTY; 46862306a36Sopenharmony_ci } 46962306a36Sopenharmony_ci return err; 47062306a36Sopenharmony_ci} 47162306a36Sopenharmony_ci 47262306a36Sopenharmony_ci/* 47362306a36Sopenharmony_ci * This is called at softirq level to deliver received packets 47462306a36Sopenharmony_ci * to the ppp_generic code, and to tell the ppp_generic code 47562306a36Sopenharmony_ci * if we can accept more output now. 47662306a36Sopenharmony_ci */ 47762306a36Sopenharmony_cistatic void ppp_sync_process(struct tasklet_struct *t) 47862306a36Sopenharmony_ci{ 47962306a36Sopenharmony_ci struct syncppp *ap = from_tasklet(ap, t, tsk); 48062306a36Sopenharmony_ci struct sk_buff *skb; 48162306a36Sopenharmony_ci 48262306a36Sopenharmony_ci /* process received packets */ 48362306a36Sopenharmony_ci while ((skb = skb_dequeue(&ap->rqueue)) != NULL) { 48462306a36Sopenharmony_ci if (skb->len == 0) { 48562306a36Sopenharmony_ci /* zero length buffers indicate error */ 48662306a36Sopenharmony_ci ppp_input_error(&ap->chan, 0); 48762306a36Sopenharmony_ci kfree_skb(skb); 48862306a36Sopenharmony_ci } 48962306a36Sopenharmony_ci else 49062306a36Sopenharmony_ci ppp_input(&ap->chan, skb); 49162306a36Sopenharmony_ci } 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_ci /* try to push more stuff out */ 49462306a36Sopenharmony_ci if (test_bit(XMIT_WAKEUP, &ap->xmit_flags) && ppp_sync_push(ap)) 49562306a36Sopenharmony_ci ppp_output_wakeup(&ap->chan); 49662306a36Sopenharmony_ci} 49762306a36Sopenharmony_ci 49862306a36Sopenharmony_ci/* 49962306a36Sopenharmony_ci * Procedures for encapsulation and framing. 50062306a36Sopenharmony_ci */ 50162306a36Sopenharmony_ci 50262306a36Sopenharmony_cistatic struct sk_buff* 50362306a36Sopenharmony_cippp_sync_txmunge(struct syncppp *ap, struct sk_buff *skb) 50462306a36Sopenharmony_ci{ 50562306a36Sopenharmony_ci int proto; 50662306a36Sopenharmony_ci unsigned char *data; 50762306a36Sopenharmony_ci int islcp; 50862306a36Sopenharmony_ci 50962306a36Sopenharmony_ci data = skb->data; 51062306a36Sopenharmony_ci proto = get_unaligned_be16(data); 51162306a36Sopenharmony_ci 51262306a36Sopenharmony_ci /* LCP packets with codes between 1 (configure-request) 51362306a36Sopenharmony_ci * and 7 (code-reject) must be sent as though no options 51462306a36Sopenharmony_ci * have been negotiated. 51562306a36Sopenharmony_ci */ 51662306a36Sopenharmony_ci islcp = proto == PPP_LCP && 1 <= data[2] && data[2] <= 7; 51762306a36Sopenharmony_ci 51862306a36Sopenharmony_ci /* compress protocol field if option enabled */ 51962306a36Sopenharmony_ci if (data[0] == 0 && (ap->flags & SC_COMP_PROT) && !islcp) 52062306a36Sopenharmony_ci skb_pull(skb,1); 52162306a36Sopenharmony_ci 52262306a36Sopenharmony_ci /* prepend address/control fields if necessary */ 52362306a36Sopenharmony_ci if ((ap->flags & SC_COMP_AC) == 0 || islcp) { 52462306a36Sopenharmony_ci if (skb_headroom(skb) < 2) { 52562306a36Sopenharmony_ci struct sk_buff *npkt = dev_alloc_skb(skb->len + 2); 52662306a36Sopenharmony_ci if (npkt == NULL) { 52762306a36Sopenharmony_ci kfree_skb(skb); 52862306a36Sopenharmony_ci return NULL; 52962306a36Sopenharmony_ci } 53062306a36Sopenharmony_ci skb_reserve(npkt,2); 53162306a36Sopenharmony_ci skb_copy_from_linear_data(skb, 53262306a36Sopenharmony_ci skb_put(npkt, skb->len), skb->len); 53362306a36Sopenharmony_ci consume_skb(skb); 53462306a36Sopenharmony_ci skb = npkt; 53562306a36Sopenharmony_ci } 53662306a36Sopenharmony_ci skb_push(skb,2); 53762306a36Sopenharmony_ci skb->data[0] = PPP_ALLSTATIONS; 53862306a36Sopenharmony_ci skb->data[1] = PPP_UI; 53962306a36Sopenharmony_ci } 54062306a36Sopenharmony_ci 54162306a36Sopenharmony_ci ap->last_xmit = jiffies; 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_ci if (skb && ap->flags & SC_LOG_OUTPKT) 54462306a36Sopenharmony_ci ppp_print_buffer ("send buffer", skb->data, skb->len); 54562306a36Sopenharmony_ci 54662306a36Sopenharmony_ci return skb; 54762306a36Sopenharmony_ci} 54862306a36Sopenharmony_ci 54962306a36Sopenharmony_ci/* 55062306a36Sopenharmony_ci * Transmit-side routines. 55162306a36Sopenharmony_ci */ 55262306a36Sopenharmony_ci 55362306a36Sopenharmony_ci/* 55462306a36Sopenharmony_ci * Send a packet to the peer over an sync tty line. 55562306a36Sopenharmony_ci * Returns 1 iff the packet was accepted. 55662306a36Sopenharmony_ci * If the packet was not accepted, we will call ppp_output_wakeup 55762306a36Sopenharmony_ci * at some later time. 55862306a36Sopenharmony_ci */ 55962306a36Sopenharmony_cistatic int 56062306a36Sopenharmony_cippp_sync_send(struct ppp_channel *chan, struct sk_buff *skb) 56162306a36Sopenharmony_ci{ 56262306a36Sopenharmony_ci struct syncppp *ap = chan->private; 56362306a36Sopenharmony_ci 56462306a36Sopenharmony_ci ppp_sync_push(ap); 56562306a36Sopenharmony_ci 56662306a36Sopenharmony_ci if (test_and_set_bit(XMIT_FULL, &ap->xmit_flags)) 56762306a36Sopenharmony_ci return 0; /* already full */ 56862306a36Sopenharmony_ci skb = ppp_sync_txmunge(ap, skb); 56962306a36Sopenharmony_ci if (skb != NULL) 57062306a36Sopenharmony_ci ap->tpkt = skb; 57162306a36Sopenharmony_ci else 57262306a36Sopenharmony_ci clear_bit(XMIT_FULL, &ap->xmit_flags); 57362306a36Sopenharmony_ci 57462306a36Sopenharmony_ci ppp_sync_push(ap); 57562306a36Sopenharmony_ci return 1; 57662306a36Sopenharmony_ci} 57762306a36Sopenharmony_ci 57862306a36Sopenharmony_ci/* 57962306a36Sopenharmony_ci * Push as much data as possible out to the tty. 58062306a36Sopenharmony_ci */ 58162306a36Sopenharmony_cistatic int 58262306a36Sopenharmony_cippp_sync_push(struct syncppp *ap) 58362306a36Sopenharmony_ci{ 58462306a36Sopenharmony_ci int sent, done = 0; 58562306a36Sopenharmony_ci struct tty_struct *tty = ap->tty; 58662306a36Sopenharmony_ci int tty_stuffed = 0; 58762306a36Sopenharmony_ci 58862306a36Sopenharmony_ci if (!spin_trylock_bh(&ap->xmit_lock)) 58962306a36Sopenharmony_ci return 0; 59062306a36Sopenharmony_ci for (;;) { 59162306a36Sopenharmony_ci if (test_and_clear_bit(XMIT_WAKEUP, &ap->xmit_flags)) 59262306a36Sopenharmony_ci tty_stuffed = 0; 59362306a36Sopenharmony_ci if (!tty_stuffed && ap->tpkt) { 59462306a36Sopenharmony_ci set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); 59562306a36Sopenharmony_ci sent = tty->ops->write(tty, ap->tpkt->data, ap->tpkt->len); 59662306a36Sopenharmony_ci if (sent < 0) 59762306a36Sopenharmony_ci goto flush; /* error, e.g. loss of CD */ 59862306a36Sopenharmony_ci if (sent < ap->tpkt->len) { 59962306a36Sopenharmony_ci tty_stuffed = 1; 60062306a36Sopenharmony_ci } else { 60162306a36Sopenharmony_ci consume_skb(ap->tpkt); 60262306a36Sopenharmony_ci ap->tpkt = NULL; 60362306a36Sopenharmony_ci clear_bit(XMIT_FULL, &ap->xmit_flags); 60462306a36Sopenharmony_ci done = 1; 60562306a36Sopenharmony_ci } 60662306a36Sopenharmony_ci continue; 60762306a36Sopenharmony_ci } 60862306a36Sopenharmony_ci /* haven't made any progress */ 60962306a36Sopenharmony_ci spin_unlock_bh(&ap->xmit_lock); 61062306a36Sopenharmony_ci if (!(test_bit(XMIT_WAKEUP, &ap->xmit_flags) || 61162306a36Sopenharmony_ci (!tty_stuffed && ap->tpkt))) 61262306a36Sopenharmony_ci break; 61362306a36Sopenharmony_ci if (!spin_trylock_bh(&ap->xmit_lock)) 61462306a36Sopenharmony_ci break; 61562306a36Sopenharmony_ci } 61662306a36Sopenharmony_ci return done; 61762306a36Sopenharmony_ci 61862306a36Sopenharmony_ciflush: 61962306a36Sopenharmony_ci if (ap->tpkt) { 62062306a36Sopenharmony_ci kfree_skb(ap->tpkt); 62162306a36Sopenharmony_ci ap->tpkt = NULL; 62262306a36Sopenharmony_ci clear_bit(XMIT_FULL, &ap->xmit_flags); 62362306a36Sopenharmony_ci done = 1; 62462306a36Sopenharmony_ci } 62562306a36Sopenharmony_ci spin_unlock_bh(&ap->xmit_lock); 62662306a36Sopenharmony_ci return done; 62762306a36Sopenharmony_ci} 62862306a36Sopenharmony_ci 62962306a36Sopenharmony_ci/* 63062306a36Sopenharmony_ci * Flush output from our internal buffers. 63162306a36Sopenharmony_ci * Called for the TCFLSH ioctl. 63262306a36Sopenharmony_ci */ 63362306a36Sopenharmony_cistatic void 63462306a36Sopenharmony_cippp_sync_flush_output(struct syncppp *ap) 63562306a36Sopenharmony_ci{ 63662306a36Sopenharmony_ci int done = 0; 63762306a36Sopenharmony_ci 63862306a36Sopenharmony_ci spin_lock_bh(&ap->xmit_lock); 63962306a36Sopenharmony_ci if (ap->tpkt != NULL) { 64062306a36Sopenharmony_ci kfree_skb(ap->tpkt); 64162306a36Sopenharmony_ci ap->tpkt = NULL; 64262306a36Sopenharmony_ci clear_bit(XMIT_FULL, &ap->xmit_flags); 64362306a36Sopenharmony_ci done = 1; 64462306a36Sopenharmony_ci } 64562306a36Sopenharmony_ci spin_unlock_bh(&ap->xmit_lock); 64662306a36Sopenharmony_ci if (done) 64762306a36Sopenharmony_ci ppp_output_wakeup(&ap->chan); 64862306a36Sopenharmony_ci} 64962306a36Sopenharmony_ci 65062306a36Sopenharmony_ci/* 65162306a36Sopenharmony_ci * Receive-side routines. 65262306a36Sopenharmony_ci */ 65362306a36Sopenharmony_ci 65462306a36Sopenharmony_ci/* called when the tty driver has data for us. 65562306a36Sopenharmony_ci * 65662306a36Sopenharmony_ci * Data is frame oriented: each call to ppp_sync_input is considered 65762306a36Sopenharmony_ci * a whole frame. If the 1st flag byte is non-zero then the whole 65862306a36Sopenharmony_ci * frame is considered to be in error and is tossed. 65962306a36Sopenharmony_ci */ 66062306a36Sopenharmony_cistatic void 66162306a36Sopenharmony_cippp_sync_input(struct syncppp *ap, const u8 *buf, const u8 *flags, int count) 66262306a36Sopenharmony_ci{ 66362306a36Sopenharmony_ci struct sk_buff *skb; 66462306a36Sopenharmony_ci unsigned char *p; 66562306a36Sopenharmony_ci 66662306a36Sopenharmony_ci if (count == 0) 66762306a36Sopenharmony_ci return; 66862306a36Sopenharmony_ci 66962306a36Sopenharmony_ci if (ap->flags & SC_LOG_INPKT) 67062306a36Sopenharmony_ci ppp_print_buffer ("receive buffer", buf, count); 67162306a36Sopenharmony_ci 67262306a36Sopenharmony_ci /* stuff the chars in the skb */ 67362306a36Sopenharmony_ci skb = dev_alloc_skb(ap->mru + PPP_HDRLEN + 2); 67462306a36Sopenharmony_ci if (!skb) { 67562306a36Sopenharmony_ci printk(KERN_ERR "PPPsync: no memory (input pkt)\n"); 67662306a36Sopenharmony_ci goto err; 67762306a36Sopenharmony_ci } 67862306a36Sopenharmony_ci /* Try to get the payload 4-byte aligned */ 67962306a36Sopenharmony_ci if (buf[0] != PPP_ALLSTATIONS) 68062306a36Sopenharmony_ci skb_reserve(skb, 2 + (buf[0] & 1)); 68162306a36Sopenharmony_ci 68262306a36Sopenharmony_ci if (flags && *flags) { 68362306a36Sopenharmony_ci /* error flag set, ignore frame */ 68462306a36Sopenharmony_ci goto err; 68562306a36Sopenharmony_ci } else if (count > skb_tailroom(skb)) { 68662306a36Sopenharmony_ci /* packet overflowed MRU */ 68762306a36Sopenharmony_ci goto err; 68862306a36Sopenharmony_ci } 68962306a36Sopenharmony_ci 69062306a36Sopenharmony_ci skb_put_data(skb, buf, count); 69162306a36Sopenharmony_ci 69262306a36Sopenharmony_ci /* strip address/control field if present */ 69362306a36Sopenharmony_ci p = skb->data; 69462306a36Sopenharmony_ci if (skb->len >= 2 && p[0] == PPP_ALLSTATIONS && p[1] == PPP_UI) { 69562306a36Sopenharmony_ci /* chop off address/control */ 69662306a36Sopenharmony_ci if (skb->len < 3) 69762306a36Sopenharmony_ci goto err; 69862306a36Sopenharmony_ci p = skb_pull(skb, 2); 69962306a36Sopenharmony_ci } 70062306a36Sopenharmony_ci 70162306a36Sopenharmony_ci /* PPP packet length should be >= 2 bytes when protocol field is not 70262306a36Sopenharmony_ci * compressed. 70362306a36Sopenharmony_ci */ 70462306a36Sopenharmony_ci if (!(p[0] & 0x01) && skb->len < 2) 70562306a36Sopenharmony_ci goto err; 70662306a36Sopenharmony_ci 70762306a36Sopenharmony_ci /* queue the frame to be processed */ 70862306a36Sopenharmony_ci skb_queue_tail(&ap->rqueue, skb); 70962306a36Sopenharmony_ci return; 71062306a36Sopenharmony_ci 71162306a36Sopenharmony_cierr: 71262306a36Sopenharmony_ci /* queue zero length packet as error indication */ 71362306a36Sopenharmony_ci if (skb || (skb = dev_alloc_skb(0))) { 71462306a36Sopenharmony_ci skb_trim(skb, 0); 71562306a36Sopenharmony_ci skb_queue_tail(&ap->rqueue, skb); 71662306a36Sopenharmony_ci } 71762306a36Sopenharmony_ci} 71862306a36Sopenharmony_ci 71962306a36Sopenharmony_cistatic void __exit 72062306a36Sopenharmony_cippp_sync_cleanup(void) 72162306a36Sopenharmony_ci{ 72262306a36Sopenharmony_ci tty_unregister_ldisc(&ppp_sync_ldisc); 72362306a36Sopenharmony_ci} 72462306a36Sopenharmony_ci 72562306a36Sopenharmony_cimodule_init(ppp_sync_init); 72662306a36Sopenharmony_cimodule_exit(ppp_sync_cleanup); 72762306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 72862306a36Sopenharmony_ciMODULE_ALIAS_LDISC(N_SYNC_PPP); 729