Hi Heikki, Thanks for sending the RFC. On Tue, Oct 26, 2021 at 05:33:50PM +0300, Heikki Krogerus wrote: > Interim. > > TODO/ideas: > - Figure out a proper magic value for the ioctl and check if > the ioctl range is OK. > - Register separate PD device for the cdev, and register it > only if the device (port, plug or partner) actually > supports USB PD (or come up with some other solution?). > - Introduce something like > > struct pd_request { > struct pd_message request; > struct pd_message __user *response; > }; > > and use it instead of only single struct pd_messages everywhere. > > - Add compat support. > - What do we do with Alerts and Attentions? > > Signed-off-by: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx> > --- > .../userspace-api/ioctl/ioctl-number.rst | 1 + > drivers/usb/typec/Makefile | 2 +- > drivers/usb/typec/class.c | 42 ++++ > drivers/usb/typec/class.h | 4 + > drivers/usb/typec/pd-dev.c | 210 ++++++++++++++++++ > drivers/usb/typec/pd-dev.h | 15 ++ > include/linux/usb/pd_dev.h | 22 ++ > include/linux/usb/typec.h | 8 + > include/uapi/linux/usb/pd_dev.h | 55 +++++ > 9 files changed, 358 insertions(+), 1 deletion(-) > create mode 100644 drivers/usb/typec/pd-dev.c > create mode 100644 drivers/usb/typec/pd-dev.h > create mode 100644 include/linux/usb/pd_dev.h > create mode 100644 include/uapi/linux/usb/pd_dev.h > > diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst > index 0ba463be6c588..fd443fd21f62a 100644 > --- a/Documentation/userspace-api/ioctl/ioctl-number.rst > +++ b/Documentation/userspace-api/ioctl/ioctl-number.rst > @@ -175,6 +175,7 @@ Code Seq# Include File Comments > 'P' 60-6F sound/sscape_ioctl.h conflict! > 'P' 00-0F drivers/usb/class/usblp.c conflict! > 'P' 01-09 drivers/misc/pci_endpoint_test.c conflict! > +'P' 70-7F uapi/linux/usb/pd_dev.h <mailto:linux-usb@xxxxxxxxxxxxxxx> > 'Q' all linux/soundcard.h > 'R' 00-1F linux/random.h conflict! > 'R' 01 linux/rfkill.h conflict! > diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile > index a0adb8947a301..be44528168013 100644 > --- a/drivers/usb/typec/Makefile > +++ b/drivers/usb/typec/Makefile > @@ -1,6 +1,6 @@ > # SPDX-License-Identifier: GPL-2.0 > obj-$(CONFIG_TYPEC) += typec.o > -typec-y := class.o mux.o bus.o port-mapper.o > +typec-y := class.o mux.o bus.o port-mapper.o pd-dev.o > obj-$(CONFIG_TYPEC) += altmodes/ > obj-$(CONFIG_TYPEC_TCPM) += tcpm/ > obj-$(CONFIG_TYPEC_UCSI) += ucsi/ > diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c > index aeef453aa6585..19fcc5da175d7 100644 > --- a/drivers/usb/typec/class.c > +++ b/drivers/usb/typec/class.c > @@ -15,6 +15,7 @@ > > #include "bus.h" > #include "class.h" > +#include "pd-dev.h" > > static DEFINE_IDA(typec_index_ida); > > @@ -665,6 +666,11 @@ static const struct attribute_group *typec_partner_groups[] = { > NULL > }; > > +char *typec_partner_devnode(struct device *dev, umode_t *mode, kuid_t *uid, kgid_t *gid) > +{ > + return kasprintf(GFP_KERNEL, "pd%u/partner", to_typec_port(dev->parent)->id); > +} > + > static void typec_partner_release(struct device *dev) > { > struct typec_partner *partner = to_typec_partner(dev); > @@ -676,6 +682,7 @@ static void typec_partner_release(struct device *dev) > const struct device_type typec_partner_dev_type = { > .name = "typec_partner", > .groups = typec_partner_groups, > + .devnode = typec_partner_devnode, > .release = typec_partner_release, > }; > > @@ -807,6 +814,7 @@ struct typec_partner *typec_register_partner(struct typec_port *port, > > ida_init(&partner->mode_ids); > partner->usb_pd = desc->usb_pd; > + partner->pd_dev = desc->pd_dev; > partner->accessory = desc->accessory; > partner->num_altmodes = -1; > partner->pd_revision = desc->pd_revision; > @@ -826,6 +834,9 @@ struct typec_partner *typec_register_partner(struct typec_port *port, > partner->dev.type = &typec_partner_dev_type; > dev_set_name(&partner->dev, "%s-partner", dev_name(&port->dev)); > > + if (partner->pd_dev) > + partner->dev.devt = MKDEV(PD_DEV_MAJOR, port->id * 4 + 3); > + > ret = device_register(&partner->dev); > if (ret) { > dev_err(&port->dev, "failed to register partner (%d)\n", ret); > @@ -853,6 +864,13 @@ EXPORT_SYMBOL_GPL(typec_unregister_partner); > /* ------------------------------------------------------------------------- */ > /* Type-C Cable Plugs */ > > +char *typec_plug_devnode(struct device *dev, umode_t *mode, kuid_t *uid, kgid_t *gid) > +{ > + return kasprintf(GFP_KERNEL, "pd%u/plug%d", > + to_typec_port(dev->parent->parent)->id, > + to_typec_plug(dev)->index); > +} > + > static void typec_plug_release(struct device *dev) > { > struct typec_plug *plug = to_typec_plug(dev); > @@ -891,6 +909,7 @@ static const struct attribute_group *typec_plug_groups[] = { > const struct device_type typec_plug_dev_type = { > .name = "typec_plug", > .groups = typec_plug_groups, > + .devnode = typec_plug_devnode, > .release = typec_plug_release, > }; > > @@ -973,11 +992,16 @@ struct typec_plug *typec_register_plug(struct typec_cable *cable, > ida_init(&plug->mode_ids); > plug->num_altmodes = -1; > plug->index = desc->index; > + plug->pd_dev = desc->pd_dev; > plug->dev.class = &typec_class; > plug->dev.parent = &cable->dev; > plug->dev.type = &typec_plug_dev_type; > dev_set_name(&plug->dev, "%s-%s", dev_name(cable->dev.parent), name); > > + if (plug->pd_dev) > + plug->dev.devt = MKDEV(PD_DEV_MAJOR, > + to_typec_port(cable->dev.parent)->id * 4 + 1 + plug->index); > + > ret = device_register(&plug->dev); > if (ret) { > dev_err(&cable->dev, "failed to register plug (%d)\n", ret); > @@ -1595,6 +1619,11 @@ static int typec_uevent(struct device *dev, struct kobj_uevent_env *env) > return ret; > } > > +char *typec_devnode(struct device *dev, umode_t *mode, kuid_t *uid, kgid_t *gid) > +{ > + return kasprintf(GFP_KERNEL, "pd%u/port", to_typec_port(dev)->id); > +} > + > static void typec_release(struct device *dev) > { > struct typec_port *port = to_typec_port(dev); > @@ -1611,6 +1640,7 @@ const struct device_type typec_port_dev_type = { > .name = "typec_port", > .groups = typec_groups, > .uevent = typec_uevent, > + .devnode = typec_devnode, > .release = typec_release, > }; > > @@ -2044,6 +2074,7 @@ struct typec_port *typec_register_port(struct device *parent, > > port->id = id; > port->ops = cap->ops; > + port->pd_dev = cap->pd_dev; > port->port_type = cap->type; > port->prefer_role = cap->prefer_role; > > @@ -2055,6 +2086,9 @@ struct typec_port *typec_register_port(struct device *parent, > dev_set_name(&port->dev, "port%d", id); > dev_set_drvdata(&port->dev, cap->driver_data); > > + if (port->pd_dev) > + port->dev.devt = MKDEV(PD_DEV_MAJOR, id * 4); > + > port->cap = kmemdup(cap, sizeof(*cap), GFP_KERNEL); > if (!port->cap) { > put_device(&port->dev); > @@ -2121,8 +2155,15 @@ static int __init typec_init(void) > if (ret) > goto err_unregister_mux_class; > > + ret = usbpd_dev_init(); > + if (ret) > + goto err_unregister_class; > + > return 0; > > +err_unregister_class: > + class_unregister(&typec_class); > + > err_unregister_mux_class: > class_unregister(&typec_mux_class); > > @@ -2135,6 +2176,7 @@ subsys_initcall(typec_init); > > static void __exit typec_exit(void) > { > + usbpd_dev_exit(); > class_unregister(&typec_class); > ida_destroy(&typec_index_ida); > bus_unregister(&typec_bus); > diff --git a/drivers/usb/typec/class.h b/drivers/usb/typec/class.h > index aef03eb7e1523..87c072f2ad753 100644 > --- a/drivers/usb/typec/class.h > +++ b/drivers/usb/typec/class.h > @@ -14,6 +14,7 @@ struct typec_plug { > enum typec_plug_index index; > struct ida mode_ids; > int num_altmodes; > + const struct pd_dev *pd_dev; > }; > > struct typec_cable { > @@ -33,6 +34,7 @@ struct typec_partner { > int num_altmodes; > u16 pd_revision; /* 0300H = "3.0" */ > enum usb_pd_svdm_ver svdm_version; > + const struct pd_dev *pd_dev; > }; > > struct typec_port { > @@ -59,6 +61,8 @@ struct typec_port { > struct mutex port_list_lock; /* Port list lock */ > > void *pld; > + > + const struct pd_dev *pd_dev; > }; > > #define to_typec_port(_dev_) container_of(_dev_, struct typec_port, dev) > diff --git a/drivers/usb/typec/pd-dev.c b/drivers/usb/typec/pd-dev.c > new file mode 100644 > index 0000000000000..436853e046ce4 > --- /dev/null > +++ b/drivers/usb/typec/pd-dev.c > @@ -0,0 +1,210 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * USB Power Delivery /dev entries > + * > + * Copyright (C) 2021 Intel Corporation > + * Author: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx> > + */ > + > +#include <linux/cdev.h> > +#include <linux/fs.h> > +#include <linux/slab.h> > +#include <linux/usb/pd_dev.h> > + > +#include "class.h" > + > +#define PD_DEV_MAX (MINORMASK + 1) > + > +dev_t usbpd_devt; > +static struct cdev usb_pd_cdev; > + > +struct pddev { > + struct device *dev; > + struct typec_port *port; > + const struct pd_dev *pd_dev; > +}; > + > +static ssize_t usbpd_read(struct file *file, char __user *buf, size_t count, loff_t *offset) > +{ > + /* FIXME TODO XXX */ > + > + /* Alert and Attention handling here (with poll) ? */ > + > + return 0; > +} > + > +static long usbpd_ioctl(struct file *file, unsigned int cmd, unsigned long arg) > +{ > + struct pddev *pd = file->private_data; > + void __user *p = (void __user *)arg; > + unsigned int pwr_role; > + struct pd_message msg; > + u32 configuration; > + int ret = 0; > + > + switch (cmd) { > + case USBPDDEV_INFO: > + if (copy_to_user(p, pd->pd_dev->info, sizeof(*pd->pd_dev->info))) > + return -EFAULT; > + break; > + case USBPDDEV_CONFIGURE: > + if (!pd->pd_dev->ops->configure) > + return -ENOTTY; > + > + if (copy_from_user(&configuration, p, sizeof(configuration))) > + return -EFAULT; > + > + ret = pd->pd_dev->ops->configure(pd->pd_dev, configuration); > + if (ret) > + return ret; > + break; > + case USBPDDEV_PWR_ROLE: > + if (is_typec_plug(pd->dev)) > + return -ENOTTY; > + > + if (is_typec_partner(pd->dev)) { > + if (pd->port->pwr_role == TYPEC_SINK) > + pwr_role = TYPEC_SOURCE; > + else > + pwr_role = TYPEC_SINK; > + } else { > + pwr_role = pd->port->pwr_role; > + } > + > + if (copy_to_user(p, &pwr_role, sizeof(unsigned int))) > + return -EFAULT; > + break; > + case USBPDDEV_GET_MESSAGE: > + if (!pd->pd_dev->ops->get_message) > + return -ENOTTY; > + > + if (copy_from_user(&msg, p, sizeof(msg))) > + return -EFAULT; > + > + ret = pd->pd_dev->ops->get_message(pd->pd_dev, &msg); > + if (ret) > + return ret; > + > + if (copy_to_user(p, &msg, sizeof(msg))) > + return -EFAULT; > + break; > + case USBPDDEV_SET_MESSAGE: > + if (!pd->pd_dev->ops->set_message) > + return -ENOTTY; > + > + ret = pd->pd_dev->ops->set_message(pd->pd_dev, &msg); > + if (ret) > + return ret; > + > + if (copy_to_user(p, &msg, sizeof(msg))) > + return -EFAULT; > + break; > + case USBPDDEV_SUBMIT_MESSAGE: > + if (!pd->pd_dev->ops->submit) > + return -ENOTTY; > + > + if (copy_from_user(&msg, p, sizeof(msg))) > + return -EFAULT; > + > + ret = pd->pd_dev->ops->submit(pd->pd_dev, &msg); > + if (ret) > + return ret; > + > + if (copy_to_user(p, &msg, sizeof(msg))) > + return -EFAULT; > + break; Why is USBPDDEV_SUBMIT_MESSAGE different from USBPDDEV_SET_MESSAGE? Shouldn't "setting" a PDO or property automatically "submit" it (using TCPM or whatever interface is actually performing the PD messaging) if appropriate (e.g Source Caps?). Is there a situation where one would want to "set" a property but not "send" it? It seems to me that the two can be combined into 1 rather than having a separate command just for ports. Best regards, -Prashant