/** * 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 { unsignedint id; struct device dev;
atomic_t port_id; conststruct 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; unsignedint start_count; unsignedlong flags; conststruct 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;
};
};
/* wwan_dev_get_by_debugfs() also got a reference */
put_device(&wwandev->dev);
put_device(&wwandev->dev);
}
EXPORT_SYMBOL_GPL(wwan_put_debugfs_dir); #endif
/* This function allocates and registers a new WWAN device OR if a WWAN device * already exist for the given parent, it gets a reference and return it. * This function is not exported (for now), it is called indirectly via * wwan_create_port().
*/ staticstruct wwan_device *wwan_create_dev(struct device *parent)
{ struct wwan_device *wwandev; int err, id;
/* The 'find-alloc-register' operation must be protected against * concurrent execution, a WWAN device is possibly shared between * multiple callers or concurrently unregistered from wwan_remove_dev().
*/
mutex_lock(&wwan_register_lock);
/* If wwandev already exists, return it */
wwandev = wwan_dev_get_by_parent(parent); if (!IS_ERR(wwandev)) goto done_unlock;
id = ida_alloc(&wwan_dev_ids, GFP_KERNEL); if (id < 0) {
wwandev = ERR_PTR(id); goto done_unlock;
}
staticvoid wwan_remove_dev(struct wwan_device *wwandev)
{ int ret;
/* Prevent concurrent picking from wwan_create_dev */
mutex_lock(&wwan_register_lock);
/* WWAN device is created and registered (get+add) along with its first * child port, and subsequent port registrations only grab a reference * (get). The WWAN device must then be unregistered (del+put) along with * its last port, and reference simply dropped (put) otherwise. In the * same fashion, we must not unregister it when the ops are still there.
*/ if (wwandev->ops)
ret = 1; else
ret = device_for_each_child(&wwandev->dev, NULL, is_wwan_child);
dev = class_find_device(&wwan_class, NULL, &minor, wwan_port_minor_match); if (!dev) return ERR_PTR(-ENODEV);
return to_wwan_port(dev);
}
/* Allocate and set unique name based on passed format * * Name allocation approach is highly inspired by the __dev_alloc_name() * function. * * To avoid names collision, the caller must prevent the new port device * registration as well as concurrent invocation of this function.
*/ staticint __wwan_port_dev_assign_name(struct wwan_port *port, constchar *fmt)
{ struct wwan_device *wwandev = to_wwan_dev(port->dev.parent); constunsignedint max_ports = PAGE_SIZE * 8; struct class_dev_iter iter; unsignedlong *idmap; struct device *dev; char buf[0x20]; int id;
idmap = bitmap_zalloc(max_ports, GFP_KERNEL); if (!idmap) return -ENOMEM;
/* Collect ids of same name format ports */
class_dev_iter_init(&iter, &wwan_class, NULL, &wwan_port_dev_type); while ((dev = class_dev_iter_next(&iter))) { if (dev->parent != &wwandev->dev) continue; if (sscanf(dev_name(dev), fmt, &id) != 1) continue; if (id < 0 || id >= max_ports) continue;
set_bit(id, idmap);
}
class_dev_iter_exit(&iter);
/* Allocate unique id */
id = find_first_zero_bit(idmap, max_ports);
bitmap_free(idmap);
snprintf(buf, sizeof(buf), fmt, id); /* Name generation */
dev = device_find_child_by_name(&wwandev->dev, buf); if (dev) {
put_device(dev); return -ENFILE;
}
if (type > WWAN_PORT_MAX || !ops) return ERR_PTR(-EINVAL);
/* A port is always a child of a WWAN device, retrieve (allocate or * pick) the WWAN device based on the provided parent device.
*/
wwandev = wwan_create_dev(parent); if (IS_ERR(wwandev)) return ERR_CAST(wwandev);
/* A port is exposed as character device, get a minor */
minor = ida_alloc_range(&minors, 0, WWAN_MAX_MINORS - 1, GFP_KERNEL); if (minor < 0) {
err = minor; goto error_wwandev_remove;
}
port = kzalloc(sizeof(*port), GFP_KERNEL); if (!port) {
err = -ENOMEM;
ida_free(&minors, minor); goto error_wwandev_remove;
}
/* allocate unique name based on wwan device id, port type and number */
snprintf(namefmt, sizeof(namefmt), "wwan%u%s%%d", wwandev->id,
wwan_port_types[port->type].devsuf);
mutex_lock(&port->ops_lock); if (port->start_count)
port->ops->stop(port);
port->ops = NULL; /* Prevent any new port operations (e.g. from fops) */
mutex_unlock(&port->ops_lock);
/* Implements minimalistic stub terminal IOCTLs support */ staticlong wwan_port_fops_at_ioctl(struct wwan_port *port, unsignedint cmd, unsignedlong arg)
{ int ret = 0;
mutex_lock(&port->data_lock);
switch (cmd) { case TCFLSH: break;
case TCGETS: if (copy_to_user((void __user *)arg, &port->at_data.termios, sizeof(struct termios)))
ret = -EFAULT; break;
case TCSETS: case TCSETSW: case TCSETSF: if (copy_from_user(&port->at_data.termios, (void __user *)arg, sizeof(struct termios)))
ret = -EFAULT; break;
#ifdef TCGETS2 case TCGETS2: if (copy_to_user((void __user *)arg, &port->at_data.termios, sizeof(struct termios2)))
ret = -EFAULT; break;
case TCSETS2: case TCSETSW2: case TCSETSF2: if (copy_from_user(&port->at_data.termios, (void __user *)arg, sizeof(struct termios2)))
ret = -EFAULT; break; #endif
case TIOCMGET:
ret = put_user(port->at_data.mdmbits, (int __user *)arg); break;
case TIOCMSET: case TIOCMBIC: case TIOCMBIS: { int mdmbits;
/* shouldn't have a netdev (left) with us as parent so WARN */ if (WARN_ON(!wwandev->ops)) {
ret = -EOPNOTSUPP; goto out;
}
priv->link_id = link_id; if (wwandev->ops->newlink)
ret = wwandev->ops->newlink(wwandev->ops_ctxt, dev,
link_id, extack); else
ret = register_netdevice(dev);
out: /* release the reference */
put_device(&wwandev->dev); return ret;
}
/* Forge attributes required to create a WWAN netdev. We first * build a netlink message and then parse it. This looks * odd, but such approach is less error prone.
*/
msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); if (WARN_ON(!msg)) return;
nlh = nlmsg_put(msg, 0, 0, RTM_NEWLINK, 0, 0); if (WARN_ON(!nlh)) goto free_attrs;
if (nla_put_string(msg, IFLA_PARENT_DEV_NAME, dev_name(&wwandev->dev))) goto free_attrs;
tb[IFLA_LINKINFO] = nla_nest_start(msg, IFLA_LINKINFO); if (!tb[IFLA_LINKINFO]) goto free_attrs;
linkinfo[IFLA_INFO_DATA] = nla_nest_start(msg, IFLA_INFO_DATA); if (!linkinfo[IFLA_INFO_DATA]) goto free_attrs; if (nla_put_u32(msg, IFLA_WWAN_LINK_ID, def_link_id)) goto free_attrs;
nla_nest_end(msg, linkinfo[IFLA_INFO_DATA]);
nla_nest_end(msg, tb[IFLA_LINKINFO]);
nlmsg_end(msg, nlh);
/* The next three parsing calls can not fail */
nlmsg_parse_deprecated(nlh, 0, tb, IFLA_MAX, NULL, NULL);
nla_parse_nested_deprecated(linkinfo, IFLA_INFO_MAX, tb[IFLA_LINKINFO],
NULL, NULL);
nla_parse_nested_deprecated(data, IFLA_WWAN_MAX,
linkinfo[IFLA_INFO_DATA], NULL, NULL);
rtnl_lock();
dev = rtnl_create_link(&init_net, "wwan%d", NET_NAME_ENUM,
&wwan_rtnl_link_ops, tb, NULL); if (WARN_ON(IS_ERR(dev))) goto unlock;
if (WARN_ON(wwan_rtnl_newlink(dev, ¶ms, NULL))) {
free_netdev(dev); goto unlock;
}
rtnl_configure_link(dev, NULL, 0, NULL); /* Link initialized, notify new link */
unlock:
rtnl_unlock();
free_attrs:
nlmsg_free(msg);
}
/** * wwan_register_ops - register WWAN device ops * @parent: Device to use as parent and shared by all WWAN ports and * created netdevs * @ops: operations to register * @ctxt: context to pass to operations * @def_link_id: id of the default link that will be automatically created by * the WWAN core for the WWAN device. The default link will not be created * if the passed value is WWAN_NO_DEFAULT_LINK. * * Returns: 0 on success, a negative error code on failure
*/ int wwan_register_ops(struct device *parent, conststruct wwan_ops *ops, void *ctxt, u32 def_link_id)
{ struct wwan_device *wwandev;
if (WARN_ON(!parent || !ops || !ops->setup)) return -EINVAL;
wwandev = wwan_create_dev(parent); if (IS_ERR(wwandev)) return PTR_ERR(wwandev);
if (WARN_ON(wwandev->ops)) {
wwan_remove_dev(wwandev); return -EBUSY;
}
wwandev->ops = ops;
wwandev->ops_ctxt = ctxt;
/* NB: we do not abort ops registration in case of default link * creation failure. Link ops is the management interface, while the * default link creation is a service option. And we should not prevent * a user from manually creating a link latter if service option failed * now.
*/ if (def_link_id != WWAN_NO_DEFAULT_LINK)
wwan_create_default_link(wwandev, def_link_id);
if (dev->type == &wwan_type)
wwan_rtnl_dellink(to_net_dev(dev), kill_list);
return 0;
}
/** * wwan_unregister_ops - remove WWAN device ops * @parent: Device to use as parent and shared by all WWAN ports and * created netdevs
*/ void wwan_unregister_ops(struct device *parent)
{ struct wwan_device *wwandev = wwan_dev_get_by_parent(parent);
LIST_HEAD(kill_list);
if (WARN_ON(IS_ERR(wwandev))) return; if (WARN_ON(!wwandev->ops)) {
put_device(&wwandev->dev); return;
}
/* put the reference obtained by wwan_dev_get_by_parent(), * we should still have one (that the owner is giving back * now) due to the ops being assigned.
*/
put_device(&wwandev->dev);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.