// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2021, Linaro Ltd <loic.poulain@linaro.org> */
#include <linux/bitmap.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/debugfs.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/idr.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/skbuff.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/termios.h>
#include <linux/wwan.h>
#include <net/rtnetlink.h>
#include <uapi/linux/wwan.h>
/* Maximum number of minors in use */
#define WWAN_MAX_MINORS (1 << MINORBITS)
static DEFINE_MUTEX(wwan_register_lock); /* WWAN device create|remove lock */
static DEFINE_IDA(minors); /* minors for WWAN port chardevs */
static DEFINE_IDA(wwan_dev_ids); /* for unique WWAN device IDs */
static struct class *wwan_class;
static int wwan_major;
static struct dentry *wwan_debugfs_dir;
#define to_wwan_dev(d) container_of(d, struct wwan_device, dev)
#define to_wwan_port(d) container_of(d, struct wwan_port, dev)
/* WWAN port flags */
#define WWAN_PORT_TX_OFF 0
/**
* struct wwan_device - The structure that defines a WWAN device
*
* @id: WWAN device unique ID.
* @dev: Underlying device.
* @port_id: Current available port ID to pick.
* @ops: wwan device ops
* @ops_ctxt: context to pass to ops
* @debugfs_dir: WWAN device debugfs dir
*/
struct wwan_device {
unsigned int id;
struct device dev;
atomic_t port_id;
const struct wwan_ops *ops;
void *ops_ctxt;
#ifdef CONFIG_WWAN_DEBUGFS
struct dentry *debugfs_dir;
#endif
};
/**
* struct wwan_port - The structure that defines a WWAN port
* @type: Port type
* @start_count: Port start counter
* @flags: Store port state and capabilities
* @ops: Pointer to WWAN port operations
* @ops_lock: Protect port ops
* @dev: Underlying device
* @rxq: Buffer inbound queue
* @waitqueue: The waitqueue for port fops (read/write/poll)
* @data_lock: Port specific data access serialization
* @headroom_len: SKB reserved headroom size
* @frag_len: Length to fragment packet
* @at_data: AT port specific data
*/
struct wwan_port {
enum wwan_port_type type;
unsigned int start_count;
unsigned long flags;
const struct wwan_port_ops *ops;
struct mutex ops_lock; /* Serialize ops + protect against removal */
struct device dev;
struct sk_buff_head rxq;
wait_queue_head_t waitqueue;
struct mutex data_lock; /* Port specific data access serialization */
size_t headroom_len;
size_t frag_len;
union {
struct {
struct ktermios termios;
int mdmbits;
} at_data;
};
};
static ssize_t index_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct wwan_device *wwan = to_wwan_dev(dev);
return sprintf(buf, "%d\n", wwan->id);
}
static DEVICE_ATTR_RO(index);
static struct attribute *wwan_dev_attrs[] = {
&dev_attr_index.attr,
NULL,
};
ATTRIBUTE_GROUPS(wwan_dev);
static void wwan_dev_destroy(struct device *dev)
{
struct wwan_device *wwandev = to_wwan_dev(dev);
ida_free(&wwan_dev_ids, wwandev->id);
kfree(wwandev);
}
static const struct device_type wwan_dev_type = {
.name = "wwan_dev",
.release = wwan_dev_destroy,
.groups = wwan_dev_groups,
};
static int wwan_dev_parent_match(struct device *dev, const void *parent)
{
return (dev->type == &wwan_dev_type &&
(dev->parent == parent || dev == parent));
}
static struct wwan_device *wwan_dev_get_by_parent(struct device *parent)
{
struct device *dev;
dev = class_find_device(wwan_class, NULL, parent, wwan_dev_parent_match);
if (!dev)
return