123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559 |
- // SPDX-License-Identifier: GPL-2.0-only
- /*
- * WWAN device simulator for WWAN framework testing.
- *
- * Copyright (c) 2021, Sergey Ryazanov <[email protected]>
- */
- #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/slab.h>
- #include <linux/device.h>
- #include <linux/spinlock.h>
- #include <linux/list.h>
- #include <linux/skbuff.h>
- #include <linux/netdevice.h>
- #include <linux/wwan.h>
- #include <linux/debugfs.h>
- #include <linux/workqueue.h>
- #include <net/arp.h>
- static int wwan_hwsim_devsnum = 2;
- module_param_named(devices, wwan_hwsim_devsnum, int, 0444);
- MODULE_PARM_DESC(devices, "Number of simulated devices");
- 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 LIST_HEAD(wwan_hwsim_devs);
- static unsigned int wwan_hwsim_dev_idx;
- static struct workqueue_struct *wwan_wq;
- struct wwan_hwsim_dev {
- struct list_head list;
- unsigned int id;
- struct device dev;
- struct work_struct del_work;
- struct dentry *debugfs_topdir;
- struct dentry *debugfs_portcreate;
- spinlock_t ports_lock; /* Serialize ports creation/deletion */
- unsigned int port_idx;
- struct list_head ports;
- };
- struct wwan_hwsim_port {
- struct list_head list;
- unsigned int id;
- struct wwan_hwsim_dev *dev;
- struct wwan_port *wwan;
- struct work_struct del_work;
- struct dentry *debugfs_topdir;
- enum { /* AT command parser state */
- AT_PARSER_WAIT_A,
- AT_PARSER_WAIT_T,
- AT_PARSER_WAIT_TERM,
- AT_PARSER_SKIP_LINE,
- } 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 netdev_tx_t wwan_hwsim_netdev_xmit(struct sk_buff *skb,
- struct net_device *ndev)
- {
- ndev->stats.tx_packets++;
- ndev->stats.tx_bytes += skb->len;
- consume_skb(skb);
- return NETDEV_TX_OK;
- }
- static const struct net_device_ops wwan_hwsim_netdev_ops = {
- .ndo_start_xmit = wwan_hwsim_netdev_xmit,
- };
- static void wwan_hwsim_netdev_setup(struct net_device *ndev)
- {
- ndev->netdev_ops = &wwan_hwsim_netdev_ops;
- ndev->needs_free_netdev = true;
- ndev->mtu = ETH_DATA_LEN;
- ndev->min_mtu = ETH_MIN_MTU;
- ndev->max_mtu = ETH_MAX_MTU;
- ndev->type = ARPHRD_NONE;
- ndev->flags = IFF_POINTOPOINT | IFF_NOARP;
- }
- static const struct wwan_ops wwan_hwsim_wwan_rtnl_ops = {
- .priv_size = 0, /* No private data */
- .setup = wwan_hwsim_netdev_setup,
- };
- static int wwan_hwsim_port_start(struct wwan_port *wport)
- {
- struct wwan_hwsim_port *port = wwan_port_get_drvdata(wport);
- port->pstate = AT_PARSER_WAIT_A;
- return 0;
- }
- static void wwan_hwsim_port_stop(struct wwan_port *wport)
- {
- }
- /* Implements a minimalistic AT commands parser that echo input back and
- * reply with 'OK' to each input command. See AT command protocol details in the
- * ITU-T V.250 recomendations document.
- *
- * Be aware that this processor is not fully V.250 compliant.
- */
- static int wwan_hwsim_port_tx(struct wwan_port *wport, struct sk_buff *in)
- {
- struct wwan_hwsim_port *port = wwan_port_get_drvdata(wport);
- struct sk_buff *out;
- int i, n, s;
- /* Estimate a max possible number of commands by counting the number of
- * termination chars (S3 param, CR by default). And then allocate the
- * output buffer that will be enough to fit the echo and result codes of
- * all commands.
- */
- for (i = 0, n = 0; i < in->len; ++i)
- if (in->data[i] == '\r')
- n++;
- n = in->len + n * (2 + 2 + 2); /* Output buffer size */
- out = alloc_skb(n, GFP_KERNEL);
- if (!out)
- return -ENOMEM;
- for (i = 0, s = 0; i < in->len; ++i) {
- char c = in->data[i];
- if (port->pstate == AT_PARSER_WAIT_A) {
- if (c == 'A' || c == 'a')
- port->pstate = AT_PARSER_WAIT_T;
- else if (c != '\n') /* Ignore formating char */
- port->pstate = AT_PARSER_SKIP_LINE;
- } else if (port->pstate == AT_PARSER_WAIT_T) {
- if (c == 'T' || c == 't')
- port->pstate = AT_PARSER_WAIT_TERM;
- else
- port->pstate = AT_PARSER_SKIP_LINE;
- } else if (port->pstate == AT_PARSER_WAIT_TERM) {
- if (c != '\r')
- continue;
- /* Consume the trailing formatting char as well */
- if ((i + 1) < in->len && in->data[i + 1] == '\n')
- i++;
- n = i - s + 1;
- skb_put_data(out, &in->data[s], n);/* Echo */
- skb_put_data(out, "\r\nOK\r\n", 6);
- s = i + 1;
- port->pstate = AT_PARSER_WAIT_A;
- } else if (port->pstate == AT_PARSER_SKIP_LINE) {
- if (c != '\r')
- continue;
- port->pstate = AT_PARSER_WAIT_A;
- }
- }
- if (i > s) {
- /* Echo the processed portion of a not yet completed command */
- n = i - s;
- skb_put_data(out, &in->data[s], n);
- }
- consume_skb(in);
- wwan_port_rx(wport, out);
- return 0;
- }
- static const struct wwan_port_ops wwan_hwsim_port_ops = {
- .start = wwan_hwsim_port_start,
- .stop = wwan_hwsim_port_stop,
- .tx = wwan_hwsim_port_tx,
- };
- static struct wwan_hwsim_port *wwan_hwsim_port_new(struct wwan_hwsim_dev *dev)
- {
- struct wwan_hwsim_port *port;
- char name[0x10];
- int err;
- port = kzalloc(sizeof(*port), GFP_KERNEL);
- if (!port)
- return ERR_PTR(-ENOMEM);
- port->dev = dev;
- spin_lock(&dev->ports_lock);
- port->id = dev->port_idx++;
- spin_unlock(&dev->ports_lock);
- port->wwan = wwan_create_port(&dev->dev, WWAN_PORT_AT,
- &wwan_hwsim_port_ops,
- port);
- if (IS_ERR(port->wwan)) {
- err = PTR_ERR(port->wwan);
- 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;
- err_free_port:
- kfree(port);
- return ERR_PTR(err);
- }
- 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);
- 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)
- {
- struct wwan_hwsim_dev *dev = container_of(sysdev, typeof(*dev), dev);
- kfree(dev);
- }
- static struct wwan_hwsim_dev *wwan_hwsim_dev_new(void)
- {
- struct wwan_hwsim_dev *dev;
- int err;
- dev = kzalloc(sizeof(*dev), GFP_KERNEL);
- if (!dev)
- return ERR_PTR(-ENOMEM);
- spin_lock(&wwan_hwsim_devs_lock);
- dev->id = wwan_hwsim_dev_idx++;
- spin_unlock(&wwan_hwsim_devs_lock);
- dev->dev.release = wwan_hwsim_dev_release;
- dev->dev.class = wwan_hwsim_class;
- dev_set_name(&dev->dev, "hwsim%u", dev->id);
- spin_lock_init(&dev->ports_lock);
- INIT_LIST_HEAD(&dev->ports);
- err = device_register(&dev->dev);
- if (err)
- goto err_free_dev;
- INIT_WORK(&dev->del_work, wwan_hwsim_dev_del_work);
- err = wwan_register_ops(&dev->dev, &wwan_hwsim_wwan_rtnl_ops, dev, 1);
- if (err)
- goto err_unreg_dev;
- 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;
- err_unreg_dev:
- device_unregister(&dev->dev);
- /* Memory will be freed in the device release callback */
- return ERR_PTR(err);
- err_free_dev:
- put_device(&dev->dev);
- return ERR_PTR(err);
- }
- static void wwan_hwsim_dev_del(struct wwan_hwsim_dev *dev)
- {
- debugfs_remove(dev->debugfs_portcreate); /* Avoid new ports */
- spin_lock(&dev->ports_lock);
- while (!list_empty(&dev->ports)) {
- struct wwan_hwsim_port *port;
- port = list_first_entry(&dev->ports, struct wwan_hwsim_port,
- list);
- list_del_init(&port->list);
- spin_unlock(&dev->ports_lock);
- wwan_hwsim_port_del(port);
- spin_lock(&dev->ports_lock);
- }
- spin_unlock(&dev->ports_lock);
- debugfs_remove(dev->debugfs_topdir);
- /* This will remove all child netdev(s) */
- wwan_unregister_ops(&dev->dev);
- /* 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);
- /* 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.
- */
- queue_work(wwan_wq, &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.
- */
- queue_work(wwan_wq, &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)
- {
- struct wwan_hwsim_dev *dev;
- int i, j;
- for (i = 0; i < wwan_hwsim_devsnum; ++i) {
- 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);
- /* Create a couple of ports per each device to accelerate
- * the simulator readiness time.
- */
- for (j = 0; j < 2; ++j) {
- 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 0;
- }
- static void wwan_hwsim_free_devs(void)
- {
- struct wwan_hwsim_dev *dev;
- spin_lock(&wwan_hwsim_devs_lock);
- while (!list_empty(&wwan_hwsim_devs)) {
- dev = list_first_entry(&wwan_hwsim_devs, struct wwan_hwsim_dev,
- list);
- list_del_init(&dev->list);
- spin_unlock(&wwan_hwsim_devs_lock);
- wwan_hwsim_dev_del(dev);
- spin_lock(&wwan_hwsim_devs_lock);
- }
- spin_unlock(&wwan_hwsim_devs_lock);
- }
- static int __init wwan_hwsim_init(void)
- {
- int err;
- if (wwan_hwsim_devsnum < 0 || wwan_hwsim_devsnum > 128)
- return -EINVAL;
- wwan_wq = alloc_workqueue("wwan_wq", 0, 0);
- if (!wwan_wq)
- return -ENOMEM;
- wwan_hwsim_class = class_create(THIS_MODULE, "wwan_hwsim");
- if (IS_ERR(wwan_hwsim_class)) {
- err = PTR_ERR(wwan_hwsim_class);
- goto err_wq_destroy;
- }
- 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();
- if (err)
- goto err_clean_devs;
- return 0;
- err_clean_devs:
- debugfs_remove(wwan_hwsim_debugfs_devcreate); /* Avoid new devs */
- wwan_hwsim_free_devs();
- flush_workqueue(wwan_wq); /* Wait deletion works completion */
- debugfs_remove(wwan_hwsim_debugfs_topdir);
- class_destroy(wwan_hwsim_class);
- err_wq_destroy:
- destroy_workqueue(wwan_wq);
- return err;
- }
- static void __exit wwan_hwsim_exit(void)
- {
- debugfs_remove(wwan_hwsim_debugfs_devcreate); /* Avoid new devs */
- wwan_hwsim_free_devs();
- flush_workqueue(wwan_wq); /* Wait deletion works completion */
- debugfs_remove(wwan_hwsim_debugfs_topdir);
- class_destroy(wwan_hwsim_class);
- destroy_workqueue(wwan_wq);
- }
- module_init(wwan_hwsim_init);
- module_exit(wwan_hwsim_exit);
- MODULE_AUTHOR("Sergey Ryazanov");
- MODULE_DESCRIPTION("Device simulator for WWAN framework");
- MODULE_LICENSE("GPL");
|