@@ -46,10 +46,12 @@
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/poll.h>
+#include <linux/proc_fs.h>
#include <linux/bitops.h>
#include <linux/file.h>
#include <linux/uaccess.h>
#include <linux/module.h>
+#include <linux/stringify.h>
#include <linux/timer.h>
#include <linux/tty_flip.h>
#include <linux/tty_driver.h>
@@ -187,7 +189,7 @@ struct gsm_dlci {
void (*prev_data)(struct gsm_dlci *dlci, const u8 *data, int len);
struct net_device *net; /* network interface, if created */
- /* Statistics (not currently exposed) */
+ /* Statistics (exposed via proc file) */
u64 tx; /* Data bytes sent on this DLCI */
u64 rx; /* Data bytes received on this DLCI */
};
@@ -272,6 +274,7 @@ enum gsm_mux_state {
struct gsm_mux {
struct tty_struct *tty; /* The tty our ldisc is bound to */
+ struct proc_dir_entry *proc; /* Associated proc fs entry */
spinlock_t lock;
struct mutex mutex;
unsigned int num;
@@ -339,7 +342,7 @@ struct gsm_mux {
bool wait_config; /* Wait for configuration by ioctl before DLCI open */
u32 keep_alive; /* Control channel keep-alive in 10ms */
- /* Statistics (not currently exposed) */
+ /* Statistics (exposed via proc file) */
unsigned long bad_fcs;
unsigned long malformed;
unsigned long io_error;
@@ -357,6 +360,7 @@ struct gsm_mux {
#define MAX_MUX 4 /* 256 minors */
static struct gsm_mux *gsm_mux[MAX_MUX]; /* GSM muxes */
static DEFINE_SPINLOCK(gsm_mux_lock);
+static DEFINE_MUTEX(gsm_mux_mutex);
static struct tty_driver *gsm_tty_driver;
@@ -3079,6 +3083,153 @@ static void gsm_error(struct gsm_mux *gsm)
gsm->io_error++;
}
+/*
+ * Entry to the proc file system in tty/ldisc/n_gsm/
+ */
+
+static struct proc_dir_entry *proc_gsm;
+
+/**
+ * gsm_proc_print_flag - check and print termios flag
+ * @m: output handle
+ * @flags: termios flags
+ * @val: flag to check and print
+ * @str: string representation of val
+ * @first: does not need separator?
+ */
+static bool gsm_proc_print_flag(struct seq_file *m, unsigned short flags,
+ int val, const char *str, bool first)
+{
+ if ((flags & val) == 0)
+ return first;
+ if (!first)
+ seq_putc(m, '|');
+ seq_puts(m, str);
+ return false;
+}
+
+#define gsm_proc_print_flag_str(m, flags, val) \
+ (first = gsm_proc_print_flag((m), (flags), (val), __stringify(val), first))
+
+/**
+ * gsm_proc_show - output proc file
+ * @m: output handle
+ * @v: result from start
+ *
+ * Handles the output of /proc/tty/ldisc/n_gsm/mux%d
+ */
+static int gsm_proc_show(struct seq_file *m, void *v)
+{
+ struct gsm_mux *gsm = m->private;
+ struct gsm_dlci *dlci = NULL;
+ unsigned short flags;
+ int i;
+ bool first = true;
+ const char *state, *ftype;
+
+ if (!gsm)
+ return -ENODEV;
+
+ /* The proc file may get removed in gsm_cleanup_mux() if the connection
+ * was closed. Early out here to avoid a deadlock.
+ */
+ if (!mutex_trylock(&gsm_mux_mutex))
+ return -EBUSY;
+ if (!mutex_trylock(&gsm->mutex)) {
+ mutex_unlock(&gsm_mux_mutex);
+ return -EBUSY;
+ }
+
+ seq_printf(m, "tty:%s flags:", tty_name(gsm->tty));
+ flags = gsm->tty->termios.c_iflag;
+ gsm_proc_print_flag_str(m, flags, IGNBRK);
+ gsm_proc_print_flag_str(m, flags, IXON);
+ gsm_proc_print_flag_str(m, flags, IXOFF);
+ flags = gsm->tty->termios.c_cflag;
+ gsm_proc_print_flag_str(m, flags, CLOCAL);
+ gsm_proc_print_flag_str(m, flags, CRTSCTS);
+ seq_putc(m, '\n');
+
+ seq_printf(m, "initiator:%d mode:%d mru:%u mtu:%u t1:%d t2:%d t3:%d",
+ gsm->initiator, gsm->encoding, gsm->mru, gsm->mtu, gsm->t1,
+ gsm->t2, gsm->t3);
+ seq_printf(m, " n2:%d k:%d wc:%d ka:%u", gsm->n2, gsm->k,
+ gsm->wait_config ? 1 : 0, gsm->keep_alive);
+ if (gsm->dead)
+ seq_puts(m, " DEAD");
+ seq_putc(m, '\n');
+
+ seq_printf(m, "bad_fcs:%lu malformed:%lu io_error:%lu open_error:%lu",
+ gsm->bad_fcs, gsm->malformed, gsm->io_error,
+ gsm->open_error);
+ seq_printf(m, " bad_size:%lu unsupported:%lu\n\n", gsm->bad_size,
+ gsm->unsupported);
+
+ for (i = 0; i < NUM_DLCI; i++) {
+ dlci = gsm->dlci[i];
+ if (!dlci)
+ continue;
+ switch (dlci->state) {
+ case DLCI_CLOSED:
+ state = "CLOSED";
+ break;
+ case DLCI_WAITING_CONFIG:
+ state = "WAITING_CONFIG";
+ break;
+ case DLCI_CONFIGURE:
+ state = "CONFIGURE";
+ break;
+ case DLCI_OPENING:
+ state = "OPENING";
+ break;
+ case DLCI_OPEN:
+ state = "OPEN";
+ break;
+ case DLCI_CLOSING:
+ state = "CLOSING";
+ break;
+ default:
+ state = "???";
+ break;
+ }
+ switch (dlci->ftype) {
+ case UI:
+ ftype = "UI";
+ break;
+ case UIH:
+ ftype = "UIH";
+ break;
+ default:
+ ftype = "???";
+ break;
+ }
+ seq_printf(m, "dlci:%d state:%s cl:%d prio:%d i:%s k:%d mtu:%u",
+ i, state, dlci->adaption, dlci->prio, ftype, dlci->k,
+ dlci->mtu);
+ seq_printf(m, " tx:%llu rx:%llu", dlci->tx, dlci->rx);
+ if (dlci->dead)
+ seq_puts(m, " DEAD");
+ seq_putc(m, '\n');
+ }
+
+ mutex_unlock(&gsm->mutex);
+ mutex_unlock(&gsm_mux_mutex);
+
+ return 0;
+}
+
+static int gsm_proc_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, gsm_proc_show, pde_data(inode));
+}
+
+static const struct proc_ops gsm_proc_ops = {
+ .proc_open = gsm_proc_open,
+ .proc_read = seq_read,
+ .proc_lseek = seq_lseek,
+ .proc_release = single_release,
+};
+
/**
* gsm_cleanup_mux - generic GSM protocol cleanup
* @gsm: our mux
@@ -3131,6 +3282,9 @@ static void gsm_cleanup_mux(struct gsm_mux *gsm, bool disc)
list_for_each_entry_safe(txq, ntxq, &gsm->tx_data_list, list)
kfree(txq);
INIT_LIST_HEAD(&gsm->tx_data_list);
+
+ if (gsm->proc)
+ proc_remove(gsm->proc);
}
/**
@@ -3145,6 +3299,7 @@ static void gsm_cleanup_mux(struct gsm_mux *gsm, bool disc)
static int gsm_activate_mux(struct gsm_mux *gsm)
{
struct gsm_dlci *dlci;
+ char pbuf[8];
int ret;
dlci = gsm_dlci_alloc(gsm, 0);
@@ -3162,6 +3317,9 @@ static int gsm_activate_mux(struct gsm_mux *gsm)
gsm->has_devices = true;
gsm->dead = false; /* Tty opens are now permissible */
+
+ if (proc_gsm && snprintf(pbuf, sizeof(pbuf), "mux%u", gsm->num) > 0)
+ gsm->proc = proc_create_data(pbuf, 0444, proc_gsm, &gsm_proc_ops, gsm);
return 0;
}
@@ -3596,6 +3754,8 @@ static void gsmld_close(struct tty_struct *tty)
{
struct gsm_mux *gsm = tty->disc_data;
+ mutex_lock(&gsm_mux_mutex);
+
/* The ldisc locks and closes the port before calling our close. This
* means we have no way to do a proper disconnect. We will not bother
* to do one.
@@ -3607,6 +3767,8 @@ static void gsmld_close(struct tty_struct *tty)
gsmld_flush_buffer(tty);
/* Do other clean up here */
mux_put(gsm);
+
+ mutex_unlock(&gsm_mux_mutex);
}
/**
@@ -3622,14 +3784,20 @@ static void gsmld_close(struct tty_struct *tty)
static int gsmld_open(struct tty_struct *tty)
{
struct gsm_mux *gsm;
+ int ret = 0;
- if (tty->ops->write == NULL)
- return -EINVAL;
+ mutex_lock(&gsm_mux_mutex);
+ if (tty->ops->write == NULL) {
+ ret = -EINVAL;
+ goto err_unlock;
+ }
/* Attach our ldisc data */
gsm = gsm_alloc_mux();
- if (gsm == NULL)
- return -ENOMEM;
+ if (gsm == NULL) {
+ ret = -ENOMEM;
+ goto err_unlock;
+ }
tty->disc_data = gsm;
tty->receive_room = 65536;
@@ -3645,7 +3813,9 @@ static int gsmld_open(struct tty_struct *tty)
else
gsm->receive = gsm1_receive;
- return 0;
+err_unlock:
+ mutex_unlock(&gsm_mux_mutex);
+ return ret;
}
/**
@@ -3769,17 +3939,24 @@ static int gsmld_ioctl(struct tty_struct *tty, unsigned int cmd,
struct gsm_mux *gsm = tty->disc_data;
unsigned int base, addr;
struct gsm_dlci *dlci;
+ int ret = 0;
switch (cmd) {
case GSMIOC_GETCONF:
gsm_copy_config_values(gsm, &c);
+ mutex_lock(&gsm_mux_mutex);
if (copy_to_user((void __user *)arg, &c, sizeof(c)))
- return -EFAULT;
- return 0;
+ ret = -EFAULT;
+ mutex_unlock(&gsm_mux_mutex);
+ break;
case GSMIOC_SETCONF:
+ mutex_lock(&gsm_mux_mutex);
if (copy_from_user(&c, (void __user *)arg, sizeof(c)))
- return -EFAULT;
- return gsm_config(gsm, &c);
+ ret = -EFAULT;
+ else
+ ret = gsm_config(gsm, &c);
+ mutex_unlock(&gsm_mux_mutex);
+ break;
case GSMIOC_GETFIRST:
base = mux_num_to_base(gsm);
return put_user(base + 1, (__u32 __user *)arg);
@@ -3824,6 +4001,7 @@ static int gsmld_ioctl(struct tty_struct *tty, unsigned int cmd,
default:
return n_tty_ioctl_helper(tty, cmd, arg);
}
+ return ret;
}
/*
@@ -4497,7 +4675,9 @@ static void gsmtty_cleanup(struct tty_struct *tty)
dlci_put(dlci);
dlci_put(gsm->dlci[0]);
+ mutex_lock(&gsm_mux_mutex);
mux_put(gsm);
+ mutex_unlock(&gsm_mux_mutex);
}
/* Virtual ttys for the demux */
@@ -4556,6 +4736,9 @@ static int __init gsm_init(void)
status = -EBUSY;
goto err_put_driver;
}
+
+ proc_gsm = proc_mkdir_mode("tty/ldisc/n_gsm", 0555, NULL);
+
pr_debug("gsm_init: loaded as %d,%d.\n",
gsm_tty_driver->major, gsm_tty_driver->minor_start);
return 0;
@@ -4569,6 +4752,8 @@ static int __init gsm_init(void)
static void __exit gsm_exit(void)
{
tty_unregister_ldisc(&tty_ldisc_packet);
+ if (proc_gsm)
+ proc_remove(proc_gsm);
tty_unregister_driver(gsm_tty_driver);
tty_driver_kref_put(gsm_tty_driver);
}