Commit 9ee23f48 authored by Sergey Ryazanov's avatar Sergey Ryazanov Committed by David S. Miller

wwan_hwsim: add debugfs management interface

wwan_hwsim creates and removes simulated control ports on module loading
and unloading. It would be helpful to be able to create/remove devices
and ports at run-time to trigger wwan port (un-)register actions without
module reloading.

Some simulator objects (e.g. ports) do not have the underling device and
it is not possible to fully manage the simulator via sysfs. wwan_hsim
intend for developers, so implement it as a self-contained debugfs based
management interface.
Signed-off-by: default avatarSergey Ryazanov <ryazanov.s.a@gmail.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent f36a111a
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
#include <linux/list.h> #include <linux/list.h>
#include <linux/skbuff.h> #include <linux/skbuff.h>
#include <linux/wwan.h> #include <linux/wwan.h>
#include <linux/debugfs.h>
#include <linux/workqueue.h>
static int wwan_hwsim_devsnum = 2; static int wwan_hwsim_devsnum = 2;
module_param_named(devices, wwan_hwsim_devsnum, int, 0444); module_param_named(devices, wwan_hwsim_devsnum, int, 0444);
...@@ -22,6 +24,9 @@ MODULE_PARM_DESC(devices, "Number of simulated devices"); ...@@ -22,6 +24,9 @@ MODULE_PARM_DESC(devices, "Number of simulated devices");
static struct class *wwan_hwsim_class; static struct class *wwan_hwsim_class;
static struct dentry *wwan_hwsim_debugfs_topdir;
static struct dentry *wwan_hwsim_debugfs_devcreate;
static DEFINE_SPINLOCK(wwan_hwsim_devs_lock); static DEFINE_SPINLOCK(wwan_hwsim_devs_lock);
static LIST_HEAD(wwan_hwsim_devs); static LIST_HEAD(wwan_hwsim_devs);
static unsigned int wwan_hwsim_dev_idx; static unsigned int wwan_hwsim_dev_idx;
...@@ -30,6 +35,9 @@ struct wwan_hwsim_dev { ...@@ -30,6 +35,9 @@ struct wwan_hwsim_dev {
struct list_head list; struct list_head list;
unsigned int id; unsigned int id;
struct device dev; struct device dev;
struct work_struct del_work;
struct dentry *debugfs_topdir;
struct dentry *debugfs_portcreate;
spinlock_t ports_lock; /* Serialize ports creation/deletion */ spinlock_t ports_lock; /* Serialize ports creation/deletion */
unsigned int port_idx; unsigned int port_idx;
struct list_head ports; struct list_head ports;
...@@ -40,6 +48,8 @@ struct wwan_hwsim_port { ...@@ -40,6 +48,8 @@ struct wwan_hwsim_port {
unsigned int id; unsigned int id;
struct wwan_hwsim_dev *dev; struct wwan_hwsim_dev *dev;
struct wwan_port *wwan; struct wwan_port *wwan;
struct work_struct del_work;
struct dentry *debugfs_topdir;
enum { /* AT command parser state */ enum { /* AT command parser state */
AT_PARSER_WAIT_A, AT_PARSER_WAIT_A,
AT_PARSER_WAIT_T, AT_PARSER_WAIT_T,
...@@ -48,6 +58,12 @@ struct wwan_hwsim_port { ...@@ -48,6 +58,12 @@ struct wwan_hwsim_port {
} pstate; } pstate;
}; };
static const struct file_operations wwan_hwsim_debugfs_portdestroy_fops;
static const struct file_operations wwan_hwsim_debugfs_portcreate_fops;
static const struct file_operations wwan_hwsim_debugfs_devdestroy_fops;
static void wwan_hwsim_port_del_work(struct work_struct *work);
static void wwan_hwsim_dev_del_work(struct work_struct *work);
static int wwan_hwsim_port_start(struct wwan_port *wport) static int wwan_hwsim_port_start(struct wwan_port *wport)
{ {
struct wwan_hwsim_port *port = wwan_port_get_drvdata(wport); struct wwan_hwsim_port *port = wwan_port_get_drvdata(wport);
...@@ -139,6 +155,7 @@ static const struct wwan_port_ops wwan_hwsim_port_ops = { ...@@ -139,6 +155,7 @@ static const struct wwan_port_ops wwan_hwsim_port_ops = {
static struct wwan_hwsim_port *wwan_hwsim_port_new(struct wwan_hwsim_dev *dev) static struct wwan_hwsim_port *wwan_hwsim_port_new(struct wwan_hwsim_dev *dev)
{ {
struct wwan_hwsim_port *port; struct wwan_hwsim_port *port;
char name[0x10];
int err; int err;
port = kzalloc(sizeof(*port), GFP_KERNEL); port = kzalloc(sizeof(*port), GFP_KERNEL);
...@@ -159,6 +176,13 @@ static struct wwan_hwsim_port *wwan_hwsim_port_new(struct wwan_hwsim_dev *dev) ...@@ -159,6 +176,13 @@ static struct wwan_hwsim_port *wwan_hwsim_port_new(struct wwan_hwsim_dev *dev)
goto err_free_port; goto err_free_port;
} }
INIT_WORK(&port->del_work, wwan_hwsim_port_del_work);
snprintf(name, sizeof(name), "port%u", port->id);
port->debugfs_topdir = debugfs_create_dir(name, dev->debugfs_topdir);
debugfs_create_file("destroy", 0200, port->debugfs_topdir, port,
&wwan_hwsim_debugfs_portdestroy_fops);
return port; return port;
err_free_port: err_free_port:
...@@ -169,10 +193,34 @@ static struct wwan_hwsim_port *wwan_hwsim_port_new(struct wwan_hwsim_dev *dev) ...@@ -169,10 +193,34 @@ static struct wwan_hwsim_port *wwan_hwsim_port_new(struct wwan_hwsim_dev *dev)
static void wwan_hwsim_port_del(struct wwan_hwsim_port *port) static void wwan_hwsim_port_del(struct wwan_hwsim_port *port)
{ {
debugfs_remove(port->debugfs_topdir);
/* Make sure that there is no pending deletion work */
if (current_work() != &port->del_work)
cancel_work_sync(&port->del_work);
wwan_remove_port(port->wwan); wwan_remove_port(port->wwan);
kfree(port); kfree(port);
} }
static void wwan_hwsim_port_del_work(struct work_struct *work)
{
struct wwan_hwsim_port *port =
container_of(work, typeof(*port), del_work);
struct wwan_hwsim_dev *dev = port->dev;
spin_lock(&dev->ports_lock);
if (list_empty(&port->list)) {
/* Someone else deleting port at the moment */
spin_unlock(&dev->ports_lock);
return;
}
list_del_init(&port->list);
spin_unlock(&dev->ports_lock);
wwan_hwsim_port_del(port);
}
static void wwan_hwsim_dev_release(struct device *sysdev) static void wwan_hwsim_dev_release(struct device *sysdev)
{ {
struct wwan_hwsim_dev *dev = container_of(sysdev, typeof(*dev), dev); struct wwan_hwsim_dev *dev = container_of(sysdev, typeof(*dev), dev);
...@@ -204,6 +252,17 @@ static struct wwan_hwsim_dev *wwan_hwsim_dev_new(void) ...@@ -204,6 +252,17 @@ static struct wwan_hwsim_dev *wwan_hwsim_dev_new(void)
if (err) if (err)
goto err_free_dev; goto err_free_dev;
INIT_WORK(&dev->del_work, wwan_hwsim_dev_del_work);
dev->debugfs_topdir = debugfs_create_dir(dev_name(&dev->dev),
wwan_hwsim_debugfs_topdir);
debugfs_create_file("destroy", 0200, dev->debugfs_topdir, dev,
&wwan_hwsim_debugfs_devdestroy_fops);
dev->debugfs_portcreate =
debugfs_create_file("portcreate", 0200,
dev->debugfs_topdir, dev,
&wwan_hwsim_debugfs_portcreate_fops);
return dev; return dev;
err_free_dev: err_free_dev:
...@@ -214,23 +273,136 @@ static struct wwan_hwsim_dev *wwan_hwsim_dev_new(void) ...@@ -214,23 +273,136 @@ static struct wwan_hwsim_dev *wwan_hwsim_dev_new(void)
static void wwan_hwsim_dev_del(struct wwan_hwsim_dev *dev) static void wwan_hwsim_dev_del(struct wwan_hwsim_dev *dev)
{ {
debugfs_remove(dev->debugfs_portcreate); /* Avoid new ports */
spin_lock(&dev->ports_lock); spin_lock(&dev->ports_lock);
while (!list_empty(&dev->ports)) { while (!list_empty(&dev->ports)) {
struct wwan_hwsim_port *port; struct wwan_hwsim_port *port;
port = list_first_entry(&dev->ports, struct wwan_hwsim_port, port = list_first_entry(&dev->ports, struct wwan_hwsim_port,
list); list);
list_del(&port->list); list_del_init(&port->list);
spin_unlock(&dev->ports_lock); spin_unlock(&dev->ports_lock);
wwan_hwsim_port_del(port); wwan_hwsim_port_del(port);
spin_lock(&dev->ports_lock); spin_lock(&dev->ports_lock);
} }
spin_unlock(&dev->ports_lock); spin_unlock(&dev->ports_lock);
debugfs_remove(dev->debugfs_topdir);
/* Make sure that there is no pending deletion work */
if (current_work() != &dev->del_work)
cancel_work_sync(&dev->del_work);
device_unregister(&dev->dev); device_unregister(&dev->dev);
/* Memory will be freed in the device release callback */ /* Memory will be freed in the device release callback */
} }
static void wwan_hwsim_dev_del_work(struct work_struct *work)
{
struct wwan_hwsim_dev *dev = container_of(work, typeof(*dev), del_work);
spin_lock(&wwan_hwsim_devs_lock);
if (list_empty(&dev->list)) {
/* Someone else deleting device at the moment */
spin_unlock(&wwan_hwsim_devs_lock);
return;
}
list_del_init(&dev->list);
spin_unlock(&wwan_hwsim_devs_lock);
wwan_hwsim_dev_del(dev);
}
static ssize_t wwan_hwsim_debugfs_portdestroy_write(struct file *file,
const char __user *usrbuf,
size_t count, loff_t *ppos)
{
struct wwan_hwsim_port *port = file->private_data;
/* We can not delete port here since it will cause a deadlock due to
* waiting this callback to finish in the debugfs_remove() call. So,
* use workqueue.
*/
schedule_work(&port->del_work);
return count;
}
static const struct file_operations wwan_hwsim_debugfs_portdestroy_fops = {
.write = wwan_hwsim_debugfs_portdestroy_write,
.open = simple_open,
.llseek = noop_llseek,
};
static ssize_t wwan_hwsim_debugfs_portcreate_write(struct file *file,
const char __user *usrbuf,
size_t count, loff_t *ppos)
{
struct wwan_hwsim_dev *dev = file->private_data;
struct wwan_hwsim_port *port;
port = wwan_hwsim_port_new(dev);
if (IS_ERR(port))
return PTR_ERR(port);
spin_lock(&dev->ports_lock);
list_add_tail(&port->list, &dev->ports);
spin_unlock(&dev->ports_lock);
return count;
}
static const struct file_operations wwan_hwsim_debugfs_portcreate_fops = {
.write = wwan_hwsim_debugfs_portcreate_write,
.open = simple_open,
.llseek = noop_llseek,
};
static ssize_t wwan_hwsim_debugfs_devdestroy_write(struct file *file,
const char __user *usrbuf,
size_t count, loff_t *ppos)
{
struct wwan_hwsim_dev *dev = file->private_data;
/* We can not delete device here since it will cause a deadlock due to
* waiting this callback to finish in the debugfs_remove() call. So,
* use workqueue.
*/
schedule_work(&dev->del_work);
return count;
}
static const struct file_operations wwan_hwsim_debugfs_devdestroy_fops = {
.write = wwan_hwsim_debugfs_devdestroy_write,
.open = simple_open,
.llseek = noop_llseek,
};
static ssize_t wwan_hwsim_debugfs_devcreate_write(struct file *file,
const char __user *usrbuf,
size_t count, loff_t *ppos)
{
struct wwan_hwsim_dev *dev;
dev = wwan_hwsim_dev_new();
if (IS_ERR(dev))
return PTR_ERR(dev);
spin_lock(&wwan_hwsim_devs_lock);
list_add_tail(&dev->list, &wwan_hwsim_devs);
spin_unlock(&wwan_hwsim_devs_lock);
return count;
}
static const struct file_operations wwan_hwsim_debugfs_devcreate_fops = {
.write = wwan_hwsim_debugfs_devcreate_write,
.open = simple_open,
.llseek = noop_llseek,
};
static int __init wwan_hwsim_init_devs(void) static int __init wwan_hwsim_init_devs(void)
{ {
struct wwan_hwsim_dev *dev; struct wwan_hwsim_dev *dev;
...@@ -272,7 +444,7 @@ static void wwan_hwsim_free_devs(void) ...@@ -272,7 +444,7 @@ static void wwan_hwsim_free_devs(void)
while (!list_empty(&wwan_hwsim_devs)) { while (!list_empty(&wwan_hwsim_devs)) {
dev = list_first_entry(&wwan_hwsim_devs, struct wwan_hwsim_dev, dev = list_first_entry(&wwan_hwsim_devs, struct wwan_hwsim_dev,
list); list);
list_del(&dev->list); list_del_init(&dev->list);
spin_unlock(&wwan_hwsim_devs_lock); spin_unlock(&wwan_hwsim_devs_lock);
wwan_hwsim_dev_del(dev); wwan_hwsim_dev_del(dev);
spin_lock(&wwan_hwsim_devs_lock); spin_lock(&wwan_hwsim_devs_lock);
...@@ -291,6 +463,12 @@ static int __init wwan_hwsim_init(void) ...@@ -291,6 +463,12 @@ static int __init wwan_hwsim_init(void)
if (IS_ERR(wwan_hwsim_class)) if (IS_ERR(wwan_hwsim_class))
return PTR_ERR(wwan_hwsim_class); return PTR_ERR(wwan_hwsim_class);
wwan_hwsim_debugfs_topdir = debugfs_create_dir("wwan_hwsim", NULL);
wwan_hwsim_debugfs_devcreate =
debugfs_create_file("devcreate", 0200,
wwan_hwsim_debugfs_topdir, NULL,
&wwan_hwsim_debugfs_devcreate_fops);
err = wwan_hwsim_init_devs(); err = wwan_hwsim_init_devs();
if (err) if (err)
goto err_clean_devs; goto err_clean_devs;
...@@ -299,6 +477,7 @@ static int __init wwan_hwsim_init(void) ...@@ -299,6 +477,7 @@ static int __init wwan_hwsim_init(void)
err_clean_devs: err_clean_devs:
wwan_hwsim_free_devs(); wwan_hwsim_free_devs();
debugfs_remove(wwan_hwsim_debugfs_topdir);
class_destroy(wwan_hwsim_class); class_destroy(wwan_hwsim_class);
return err; return err;
...@@ -306,7 +485,10 @@ static int __init wwan_hwsim_init(void) ...@@ -306,7 +485,10 @@ static int __init wwan_hwsim_init(void)
static void __exit wwan_hwsim_exit(void) static void __exit wwan_hwsim_exit(void)
{ {
debugfs_remove(wwan_hwsim_debugfs_devcreate); /* Avoid new devs */
wwan_hwsim_free_devs(); wwan_hwsim_free_devs();
flush_scheduled_work(); /* Wait deletion works completion */
debugfs_remove(wwan_hwsim_debugfs_topdir);
class_destroy(wwan_hwsim_class); class_destroy(wwan_hwsim_class);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment