summaryrefslogtreecommitdiff
path: root/drivers/tty
diff options
context:
space:
mode:
authorDaniel Starke <daniel.starke@siemens.com>2023-03-15 11:53:52 +0100
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2023-03-29 10:50:48 +0200
commit4ca589661d964840d0d5de4b3baabbef78f453e3 (patch)
tree51bdebd25258a7aa020524a209ec877126da0310 /drivers/tty
parentcb95de8d4317b9335f1e8b687bca714cd5713502 (diff)
downloadlinux-4ca589661d964840d0d5de4b3baabbef78f453e3.tar.gz
tty: n_gsm: add ioctl for DLC specific parameter configuration
Parameter negotiation has been introduced with commit 92f1f0c3290d ("tty: n_gsm: add parameter negotiation support") However, means to set individual parameters per DLCI are not yet implemented. Furthermore, it is currently not possible to keep a DLCI half open until the user application sets the right parameters for it. This is required to allow a user application to set its specific parameters before the underlying link is established. Otherwise, the link is opened and re-established right afterwards if the user application sets incompatible parameters. This may be an unexpected behavior for the peer. Add parameter 'wait_config' to 'gsm_config' to support setups where the DLCI specific user application sets its specific parameters after open() and before the link gets fully established. Setting this to zero disables the user application specific DLCI configuration option. Add the ioctls 'GSMIOC_GETCONF_DLCI' and 'GSMIOC_SETCONF_DLCI' for the ldisc and virtual ttys. This gets/sets the DLCI specific parameters and may trigger a reconnect of the DLCI if incompatible values have been set. Only the parameters for the DLCI associated with the virtual tty can be set or retrieved if called on these. Add remark within the documentation to introduce the new ioctls. Link: https://lore.kernel.org/oe-kbuild-all/202302281856.S9Lz4gHB-lkp@intel.com/ Reported-by: kernel test robot <lkp@intel.com> Signed-off-by: Daniel Starke <daniel.starke@siemens.com> Link: https://lore.kernel.org/r/20230315105354.6234-1-daniel.starke@siemens.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/tty')
-rw-r--r--drivers/tty/n_gsm.c192
1 files changed, 187 insertions, 5 deletions
diff --git a/drivers/tty/n_gsm.c b/drivers/tty/n_gsm.c
index 7aa05ebed8e1..9d0e7701ffd4 100644
--- a/drivers/tty/n_gsm.c
+++ b/drivers/tty/n_gsm.c
@@ -128,6 +128,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 +331,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 +453,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 +2283,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 +2315,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 +2325,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 +2476,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 +3223,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
@@ -3218,6 +3364,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 +3380,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;
}
@@ -3436,6 +3588,14 @@ static int gsmld_open(struct tty_struct *tty)
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;
}
@@ -4008,7 +4168,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 +4177,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 +4306,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 +4330,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: