diff options
Diffstat (limited to 'drivers/tty/n_gsm.c')
-rw-r--r-- | drivers/tty/n_gsm.c | 231 |
1 files changed, 222 insertions, 9 deletions
diff --git a/drivers/tty/n_gsm.c b/drivers/tty/n_gsm.c index 7aa05ebed8e1..b411a26cc092 100644 --- a/drivers/tty/n_gsm.c +++ b/drivers/tty/n_gsm.c @@ -42,6 +42,7 @@ #include <linux/ctype.h> #include <linux/mm.h> #include <linux/math.h> +#include <linux/nospec.h> #include <linux/string.h> #include <linux/slab.h> #include <linux/poll.h> @@ -128,6 +129,7 @@ struct gsm_msg { enum gsm_dlci_state { DLCI_CLOSED, + DLCI_WAITING_CONFIG, /* Waiting for DLCI configuration from user */ DLCI_CONFIGURE, /* Sending PN (for adaption > 1) */ DLCI_OPENING, /* Sending SABM not seen UA */ DLCI_OPEN, /* SABM/UA complete */ @@ -330,6 +332,7 @@ struct gsm_mux { unsigned int t3; /* Power wake-up timer in seconds. */ int n2; /* Retry count */ u8 k; /* Window size */ + bool wait_config; /* Wait for configuration by ioctl before DLCI open */ u32 keep_alive; /* Control channel keep-alive in 10ms */ /* Statistics (not currently exposed) */ @@ -451,6 +454,7 @@ static int gsm_modem_update(struct gsm_dlci *dlci, u8 brk); static struct gsm_msg *gsm_data_alloc(struct gsm_mux *gsm, u8 addr, int len, u8 ctrl); static int gsm_send_packet(struct gsm_mux *gsm, struct gsm_msg *msg); +static struct gsm_dlci *gsm_dlci_alloc(struct gsm_mux *gsm, int addr); static void gsmld_write_trigger(struct gsm_mux *gsm); static void gsmld_write_task(struct work_struct *work); @@ -2280,6 +2284,7 @@ static void gsm_dlci_begin_open(struct gsm_dlci *dlci) switch (dlci->state) { case DLCI_CLOSED: + case DLCI_WAITING_CONFIG: case DLCI_CLOSING: dlci->retries = gsm->n2; if (!need_pn) { @@ -2311,6 +2316,7 @@ static void gsm_dlci_set_opening(struct gsm_dlci *dlci) { switch (dlci->state) { case DLCI_CLOSED: + case DLCI_WAITING_CONFIG: case DLCI_CLOSING: dlci->state = DLCI_OPENING; break; @@ -2320,6 +2326,24 @@ static void gsm_dlci_set_opening(struct gsm_dlci *dlci) } /** + * gsm_dlci_set_wait_config - wait for channel configuration + * @dlci: DLCI to configure + * + * Wait for a DLCI configuration from the application. + */ +static void gsm_dlci_set_wait_config(struct gsm_dlci *dlci) +{ + switch (dlci->state) { + case DLCI_CLOSED: + case DLCI_CLOSING: + dlci->state = DLCI_WAITING_CONFIG; + break; + default: + break; + } +} + +/** * gsm_dlci_begin_close - start channel open procedure * @dlci: DLCI to open * @@ -2453,6 +2477,128 @@ static void gsm_kick_timer(struct timer_list *t) pr_info("%s TX queue stalled\n", __func__); } +/** + * gsm_dlci_copy_config_values - copy DLCI configuration + * @dlci: source DLCI + * @dc: configuration structure to fill + */ +static void gsm_dlci_copy_config_values(struct gsm_dlci *dlci, struct gsm_dlci_config *dc) +{ + memset(dc, 0, sizeof(*dc)); + dc->channel = (u32)dlci->addr; + dc->adaption = (u32)dlci->adaption; + dc->mtu = (u32)dlci->mtu; + dc->priority = (u32)dlci->prio; + if (dlci->ftype == UIH) + dc->i = 1; + else + dc->i = 2; + dc->k = (u32)dlci->k; +} + +/** + * gsm_dlci_config - configure DLCI from configuration + * @dlci: DLCI to configure + * @dc: DLCI configuration + * @open: open DLCI after configuration? + */ +static int gsm_dlci_config(struct gsm_dlci *dlci, struct gsm_dlci_config *dc, int open) +{ + struct gsm_mux *gsm; + bool need_restart = false; + bool need_open = false; + unsigned int i; + + /* + * Check that userspace doesn't put stuff in here to prevent breakages + * in the future. + */ + for (i = 0; i < ARRAY_SIZE(dc->reserved); i++) + if (dc->reserved[i]) + return -EINVAL; + + if (!dlci) + return -EINVAL; + gsm = dlci->gsm; + + /* Stuff we don't support yet - I frame transport */ + if (dc->adaption != 1 && dc->adaption != 2) + return -EOPNOTSUPP; + if (dc->mtu > MAX_MTU || dc->mtu < MIN_MTU || dc->mtu > gsm->mru) + return -EINVAL; + if (dc->priority >= 64) + return -EINVAL; + if (dc->i == 0 || dc->i > 2) /* UIH and UI only */ + return -EINVAL; + if (dc->k > 7) + return -EINVAL; + + /* + * See what is needed for reconfiguration + */ + /* Framing fields */ + if (dc->adaption != dlci->adaption) + need_restart = true; + if (dc->mtu != dlci->mtu) + need_restart = true; + if (dc->i != dlci->ftype) + need_restart = true; + /* Requires care */ + if (dc->priority != dlci->prio) + need_restart = true; + + if ((open && gsm->wait_config) || need_restart) + need_open = true; + if (dlci->state == DLCI_WAITING_CONFIG) { + need_restart = false; + need_open = true; + } + + /* + * Close down what is needed, restart and initiate the new + * configuration. + */ + if (need_restart) { + gsm_dlci_begin_close(dlci); + wait_event_interruptible(gsm->event, dlci->state == DLCI_CLOSED); + if (signal_pending(current)) + return -EINTR; + } + /* + * Setup the new configuration values + */ + dlci->adaption = (int)dc->adaption; + + if (dc->mtu) + dlci->mtu = (unsigned int)dc->mtu; + else + dlci->mtu = gsm->mtu; + + if (dc->priority) + dlci->prio = (u8)dc->priority; + else + dlci->prio = roundup(dlci->addr + 1, 8) - 1; + + if (dc->i == 1) + dlci->ftype = UIH; + else if (dc->i == 2) + dlci->ftype = UI; + + if (dc->k) + dlci->k = (u8)dc->k; + else + dlci->k = gsm->k; + + if (need_open) { + if (gsm->initiator) + gsm_dlci_begin_open(dlci); + else + gsm_dlci_set_opening(dlci); + } + + return 0; +} + /* * Allocate/Free DLCI channels */ @@ -3078,6 +3224,7 @@ static struct gsm_mux *gsm_alloc_mux(void) gsm->mru = 64; /* Default to encoding 1 so these should be 64 */ gsm->mtu = 64; gsm->dead = true; /* Avoid early tty opens */ + gsm->wait_config = false; /* Disabled */ gsm->keep_alive = 0; /* Disabled */ /* Store the instance to the mux array or abort if no space is @@ -3130,8 +3277,8 @@ static int gsm_config(struct gsm_mux *gsm, struct gsm_config *c) int need_close = 0; int need_restart = 0; - /* Stuff we don't support yet - UI or I frame transport, windowing */ - if ((c->adaption != 1 && c->adaption != 2) || c->k) + /* Stuff we don't support yet - UI or I frame transport */ + if (c->adaption != 1 && c->adaption != 2) return -EOPNOTSUPP; /* Check the MRU/MTU range looks sane */ if (c->mru < MIN_MTU || c->mtu < MIN_MTU) @@ -3218,6 +3365,7 @@ static void gsm_copy_config_ext_values(struct gsm_mux *gsm, struct gsm_config_ext *ce) { memset(ce, 0, sizeof(*ce)); + ce->wait_config = gsm->wait_config ? 1 : 0; ce->keep_alive = gsm->keep_alive; } @@ -3233,7 +3381,12 @@ static int gsm_config_ext(struct gsm_mux *gsm, struct gsm_config_ext *ce) if (ce->reserved[i]) return -EINVAL; + /* + * Setup the new configuration values + */ + gsm->wait_config = ce->wait_config ? true : false; gsm->keep_alive = ce->keep_alive; + return 0; } @@ -3433,9 +3586,16 @@ static int gsmld_open(struct tty_struct *tty) tty->receive_room = 65536; /* Attach the initial passive connection */ - gsm->encoding = GSM_ADV_OPT; gsmld_attach_gsm(tty, gsm); + /* The mux will not be activated yet, we wait for correct + * configuration first. + */ + if (gsm->encoding == GSM_BASIC_OPT) + gsm->receive = gsm0_receive; + else + gsm->receive = gsm1_receive; + return 0; } @@ -3556,8 +3716,10 @@ static int gsmld_ioctl(struct tty_struct *tty, unsigned int cmd, { struct gsm_config c; struct gsm_config_ext ce; + struct gsm_dlci_config dc; struct gsm_mux *gsm = tty->disc_data; - unsigned int base; + unsigned int base, addr; + struct gsm_dlci *dlci; switch (cmd) { case GSMIOC_GETCONF: @@ -3581,6 +3743,35 @@ static int gsmld_ioctl(struct tty_struct *tty, unsigned int cmd, if (copy_from_user(&ce, (void __user *)arg, sizeof(ce))) return -EFAULT; return gsm_config_ext(gsm, &ce); + case GSMIOC_GETCONF_DLCI: + if (copy_from_user(&dc, (void __user *)arg, sizeof(dc))) + return -EFAULT; + if (dc.channel == 0 || dc.channel >= NUM_DLCI) + return -EINVAL; + addr = array_index_nospec(dc.channel, NUM_DLCI); + dlci = gsm->dlci[addr]; + if (!dlci) { + dlci = gsm_dlci_alloc(gsm, addr); + if (!dlci) + return -ENOMEM; + } + gsm_dlci_copy_config_values(dlci, &dc); + if (copy_to_user((void __user *)arg, &dc, sizeof(dc))) + return -EFAULT; + return 0; + case GSMIOC_SETCONF_DLCI: + if (copy_from_user(&dc, (void __user *)arg, sizeof(dc))) + return -EFAULT; + if (dc.channel == 0 || dc.channel >= NUM_DLCI) + return -EINVAL; + addr = array_index_nospec(dc.channel, NUM_DLCI); + dlci = gsm->dlci[addr]; + if (!dlci) { + dlci = gsm_dlci_alloc(gsm, addr); + if (!dlci) + return -ENOMEM; + } + return gsm_dlci_config(dlci, &dc, 0); default: return n_tty_ioctl_helper(tty, cmd, arg); } @@ -4008,7 +4199,6 @@ static int gsmtty_open(struct tty_struct *tty, struct file *filp) { struct gsm_dlci *dlci = tty->driver_data; struct tty_port *port = &dlci->port; - struct gsm_mux *gsm = dlci->gsm; port->count++; tty_port_tty_set(port, tty); @@ -4018,10 +4208,15 @@ static int gsmtty_open(struct tty_struct *tty, struct file *filp) a DM straight back. This is ok as that will have caused a hangup */ tty_port_set_initialized(port, true); /* Start sending off SABM messages */ - if (gsm->initiator) - gsm_dlci_begin_open(dlci); - else - gsm_dlci_set_opening(dlci); + if (!dlci->gsm->wait_config) { + /* Start sending off SABM messages */ + if (dlci->gsm->initiator) + gsm_dlci_begin_open(dlci); + else + gsm_dlci_set_opening(dlci); + } else { + gsm_dlci_set_wait_config(dlci); + } /* And wait for virtual carrier */ return tty_port_block_til_ready(port, tty, filp); } @@ -4142,6 +4337,7 @@ static int gsmtty_ioctl(struct tty_struct *tty, { struct gsm_dlci *dlci = tty->driver_data; struct gsm_netconfig nc; + struct gsm_dlci_config dc; int index; if (dlci->state == DLCI_CLOSED) @@ -4165,6 +4361,23 @@ static int gsmtty_ioctl(struct tty_struct *tty, gsm_destroy_network(dlci); mutex_unlock(&dlci->mutex); return 0; + case GSMIOC_GETCONF_DLCI: + if (copy_from_user(&dc, (void __user *)arg, sizeof(dc))) + return -EFAULT; + if (dc.channel != dlci->addr) + return -EPERM; + gsm_dlci_copy_config_values(dlci, &dc); + if (copy_to_user((void __user *)arg, &dc, sizeof(dc))) + return -EFAULT; + return 0; + case GSMIOC_SETCONF_DLCI: + if (copy_from_user(&dc, (void __user *)arg, sizeof(dc))) + return -EFAULT; + if (dc.channel >= NUM_DLCI) + return -EINVAL; + if (dc.channel != 0 && dc.channel != dlci->addr) + return -EPERM; + return gsm_dlci_config(dlci, &dc, 1); case TIOCMIWAIT: return gsm_wait_modem_change(dlci, (u32)arg); default: |