mirror of
https://github.com/Fishwaldo/Star64_linux.git
synced 2025-06-23 15:11:16 +00:00
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: Sergey Ryazanov <ryazanov.s.a@gmail.com> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
parent
f36a111a74
commit
9ee23f48f6
1 changed files with 184 additions and 2 deletions
|
@ -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");
|
||||||
|
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
} 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 = {
|
||||||
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)
|
||||||
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 @@ err_free_port:
|
||||||
|
|
||||||
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)
|
||||||
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 @@ err_free_dev:
|
||||||
|
|
||||||
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)
|
||||||
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)
|
||||||
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)
|
||||||
|
|
||||||
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 @@ err_clean_devs:
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue