@@ -290,6 +290,19 @@ config SCSI_SAS_ATTRS
If you wish to export transport-specific information about
each attached SAS device to sysfs, say Y.
+config SCSI_UFS_ATTRS
+ tristate "UFS Transport Attributes"
+ depends on SCSI
+ select BLK_DEV_BSGLIB
+ help
+ Scsi transport is a framework that allow to send scsi commands to
+ a non-scsi devices. Still, it is flexible enough to allow sending
+ non-scsi commands as well. We will use this framework to manage
+ ufs devices by sending UPIU transactions.
+
+ If you wish to export transport-specific information about
+ each attached UFS device to sysfs, say Y.
+
source "drivers/scsi/libsas/Kconfig"
config SCSI_SRP_ATTRS
@@ -34,6 +34,7 @@ obj-$(CONFIG_SCSI_ISCSI_ATTRS) += scsi_transport_iscsi.o
obj-$(CONFIG_SCSI_SAS_ATTRS) += scsi_transport_sas.o
obj-$(CONFIG_SCSI_SAS_LIBSAS) += libsas/
obj-$(CONFIG_SCSI_SRP_ATTRS) += scsi_transport_srp.o
+obj-$(CONFIG_SCSI_UFS_ATTRS) += scsi_transport_ufs.o
obj-$(CONFIG_SCSI_DH) += device_handler/
obj-$(CONFIG_LIBFC) += libfc/
new file mode 100644
@@ -0,0 +1,337 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SCSI UFS transport class
+ *
+ * Copyright (C) 2018 Western Digital Corporation
+ */
+
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/blkdev.h>
+#include <linux/bsg.h>
+
+#include <scsi/scsi.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_host.h>
+#include <scsi/scsi_transport.h>
+#include <scsi/scsi_transport_ufs.h>
+
+
+#define UFS_HOST_ATTRS 0
+#define UFS_PORT_ATTRS 0
+
+struct ufs_internal {
+ struct scsi_transport_template t;
+ struct ufs_function_template *f;
+
+ struct device_attribute *host_attrs[UFS_HOST_ATTRS + 1];
+ struct device_attribute *port_attrs[UFS_PORT_ATTRS + 1];
+ struct transport_container port_attr_cont;
+};
+#define to_ufs_internal(tmpl) container_of(tmpl, struct ufs_internal, t)
+
+struct ufs_host_attrs {
+ atomic_t next_port_id;
+};
+#define to_ufs_host_attrs(x) ((struct ufs_host_attrs *)(x)->shost_data)
+
+static int ufs_bsg_dispatch(struct bsg_job *job)
+{
+ struct Scsi_Host *shost = dev_to_shost(job->dev);
+ struct ufs_internal *i = to_ufs_internal(shost->transportt);
+
+ /* check for payload_len when we'll support data transfer upiu */
+
+ return i->f->bsg_request(job);
+}
+
+static int ufs_bsg_initialize(struct Scsi_Host *shost, struct ufs_port *port)
+{
+ struct request_queue *q;
+ struct ufs_internal *i = to_ufs_internal(shost->transportt);
+
+ if (!i->f->bsg_request) {
+ pr_info("%s can't handle ufs requests\n", shost->hostt->name);
+ return 0;
+ }
+
+ q = bsg_setup_queue(&port->dev, dev_name(&port->dev),
+ ufs_bsg_dispatch, 0, NULL);
+ if (IS_ERR(q))
+ return PTR_ERR(q);
+ port->q = q;
+
+ return 0;
+}
+
+static int ufs_host_setup(struct transport_container *tc, struct device *dev,
+ struct device *cdev)
+{
+ struct Scsi_Host *shost = dev_to_shost(dev);
+ struct ufs_host_attrs *ufs_host = to_ufs_host_attrs(shost);
+
+ atomic_set(&ufs_host->next_port_id, 0);
+
+ return 0;
+}
+
+static DECLARE_TRANSPORT_CLASS(ufs_host_class, "ufs_host", ufs_host_setup,
+ NULL, NULL);
+
+static DECLARE_TRANSPORT_CLASS(ufs_port_class, "ufs_ports",
+ NULL, NULL, NULL);
+
+static int ufs_host_match(struct attribute_container *cont,
+ struct device *dev)
+{
+ struct Scsi_Host *shost;
+ struct ufs_internal *i;
+
+ if (!scsi_is_host_device(dev))
+ return 0;
+
+ shost = dev_to_shost(dev);
+ if (!shost->transportt)
+ return 0;
+
+ if (shost->transportt->host_attrs.ac.class != &ufs_host_class.class)
+ return 0;
+
+ i = to_ufs_internal(shost->transportt);
+
+ return &i->t.host_attrs.ac == cont;
+}
+
+static void ufs_port_release(struct device *dev)
+{
+ struct ufs_port *port = dev_to_ufs_port(dev);
+
+ put_device(dev->parent);
+ kfree(port);
+}
+
+static inline int scsi_is_ufs_port(const struct device *dev)
+{
+ return dev->release == ufs_port_release;
+}
+
+static int ufs_port_match(struct attribute_container *cont, struct device *dev)
+{
+ struct Scsi_Host *shost;
+ struct ufs_internal *i;
+
+ if (!scsi_is_ufs_port(dev))
+ return 0;
+
+ shost = dev_to_shost(dev->parent);
+
+ if (!shost->transportt)
+ return 0;
+
+ if (shost->transportt->host_attrs.ac.class != &ufs_host_class.class)
+ return 0;
+
+ i = to_ufs_internal(shost->transportt);
+ return &i->port_attr_cont.ac == cont;
+}
+
+/**
+ * ufs_attach_transport - instantiate UFS transport template
+ * @ft: UFS transport class function template
+ */
+struct scsi_transport_template *
+ufs_attach_transport(struct ufs_function_template *ft)
+{
+ struct ufs_internal *i = kzalloc(sizeof(struct ufs_internal),
+ GFP_KERNEL);
+
+ if (unlikely(!i))
+ return NULL;
+
+ i->f = ft;
+ i->t.host_attrs.ac.attrs = &i->host_attrs[0];
+ i->t.host_attrs.ac.class = &ufs_host_class.class;
+ i->t.host_attrs.ac.match = ufs_host_match;
+ i->t.host_size = sizeof(struct ufs_host_attrs);
+ i->host_attrs[0] = NULL;
+ transport_container_register(&i->t.host_attrs);
+
+ i->port_attr_cont.ac.class = &ufs_port_class.class;
+ i->port_attr_cont.ac.attrs = &i->port_attrs[0];
+ i->port_attr_cont.ac.match = ufs_port_match;
+ i->port_attrs[0] = NULL;
+ transport_container_register(&i->port_attr_cont);
+
+ return &i->t;
+}
+EXPORT_SYMBOL(ufs_attach_transport);
+
+/**
+ * ufs_release_transport - release UFS transport template instance
+ * @t: transport template instance
+ */
+void ufs_release_transport(struct scsi_transport_template *t)
+{
+ struct ufs_internal *i = to_ufs_internal(t);
+
+ transport_container_unregister(&i->t.host_attrs);
+ transport_container_unregister(&i->port_attr_cont);
+ kfree(i);
+}
+EXPORT_SYMBOL(ufs_release_transport);
+
+/** ufs_port_alloc - allocate and initialize a UFS port structure
+ * @shost: scsi host that the port is attached to
+ *
+ * Allocates a UFS port structure. It will be added to the device tree
+ * below the device specified by its Scsi_Host parent.
+ * Returns %NULL on error
+ */
+struct ufs_port *ufs_port_alloc(struct Scsi_Host *shost)
+{
+ struct ufs_port *port;
+ struct device *parent = &shost->shost_gendev;
+ struct ufs_host_attrs *ufs_host = to_ufs_host_attrs(shost);
+
+ port = kzalloc(sizeof(*port), GFP_KERNEL);
+ if (!port)
+ return NULL;
+
+ port->id = atomic_inc_return(&ufs_host->next_port_id);
+
+ device_initialize(&port->dev);
+
+ port->dev.parent = get_device(parent);
+ port->dev.release = ufs_port_release;
+
+ dev_set_name(&port->dev, "ufs-port-%d:%d", shost->host_no, port->id);
+
+ transport_setup_device(&port->dev);
+
+ return port;
+}
+EXPORT_SYMBOL(ufs_port_alloc);
+
+/**
+ * ufs_port_add - add a UFS port to the device hierarchy
+ * @port: port to be added
+ *
+ * publishes a port to the rest of the system
+ */
+int ufs_port_add(struct ufs_port *port)
+{
+ struct device *dev = &port->dev;
+ struct Scsi_Host *shost = dev_to_shost(dev->parent);
+ int error;
+
+ if (!port) {
+ pr_err("%s failed with null port\n", __func__);
+ return -EINVAL;
+ }
+
+ error = device_add(dev);
+
+ if (error)
+ return error;
+
+ transport_add_device(dev);
+ transport_configure_device(dev);
+
+ if (ufs_bsg_initialize(shost, port))
+ dev_err(dev, "fail to initialize a bsg dev %d\n",
+ shost->host_no);
+
+ return 0;
+}
+EXPORT_SYMBOL(ufs_port_add);
+
+/**
+ * ufs_port_free - free a ufs port
+ * @port: ufs port to free
+ *
+ * Frees the specified ufs port. Should be called on ufs_port_add() failure.
+ */
+void ufs_port_free(struct ufs_port *port)
+{
+ transport_destroy_device(&port->dev);
+ put_device(&port->dev);
+}
+EXPORT_SYMBOL(ufs_port_free);
+
+/**
+ * ufs_port_delete - remove a ufs port
+ * @port: ufs port to remove
+ *
+ */
+void ufs_port_delete(struct ufs_port *port)
+{
+ struct device *dev = &port->dev;
+
+ if (port->q)
+ bsg_unregister_queue(port->q);
+
+ transport_remove_device(dev);
+ device_del(dev);
+ transport_destroy_device(dev);
+
+ put_device(dev);
+}
+EXPORT_SYMBOL(ufs_port_delete);
+
+static int do_ufs_port_del(struct device *dev, void *data)
+{
+ if (scsi_is_ufs_port(dev))
+ ufs_port_delete(dev_to_ufs_port(dev));
+
+ return 0;
+}
+
+/**
+ * ufs_remove_host - tear down a ufs port devices
+ * @shost: the scsi host which is parenting the ufs port objects
+ *
+ * Removes all ufs port devices.
+ * Unharm Other devices that are attached to that host.
+ */
+void ufs_remove_host(struct Scsi_Host *shost)
+{
+ device_for_each_child(&shost->shost_gendev, NULL, do_ufs_port_del);
+}
+EXPORT_SYMBOL_GPL(ufs_remove_host);
+
+static __init int ufs_transport_init(void)
+{
+ int error;
+
+ error = transport_class_register(&ufs_host_class);
+ if (error)
+ goto out;
+
+ error = transport_class_register(&ufs_port_class);
+ if (error)
+ goto out_unregister_transport;
+
+ return 0;
+
+out_unregister_transport:
+ transport_class_unregister(&ufs_host_class);
+out:
+ return error;
+}
+
+static void __exit ufs_transport_exit(void)
+{
+ transport_class_unregister(&ufs_host_class);
+ transport_class_unregister(&ufs_port_class);
+}
+
+MODULE_AUTHOR("WDC");
+MODULE_DESCRIPTION("UFS Transport Attributes");
+MODULE_LICENSE("GPL");
+
+module_init(ufs_transport_init);
+module_exit(ufs_transport_exit);
new file mode 100644
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 Western Digital Corporation
+ */
+
+#ifndef SCSI_TRANSPORT_UFS_H
+#define SCSI_TRANSPORT_UFS_H
+
+#include <linux/bsg-lib.h>
+#include <scsi/scsi.h>
+#include <scsi/scsi_host.h>
+
+
+struct ufs_function_template {
+ int (*bsg_request)(struct bsg_job *job);
+};
+
+/* One might expect a more complex structure –
+ * some representation of a list containing all the luns of the systems,
+ * including the w-luns. However, as we are using upiu transactions that
+ * contains the lun index in its header, we need not to have its representation
+ * in the device tree.
+ */
+struct ufs_port {
+ struct device dev;
+ int id;
+ struct request_queue *q;
+};
+#define dev_to_ufs_port(d) \
+ container_of((d), struct ufs_port, dev)
+
+struct scsi_transport_template *
+ufs_attach_transport(struct ufs_function_template *ft);
+void ufs_release_transport(struct scsi_transport_template *t);
+struct ufs_port *ufs_port_alloc(struct Scsi_Host *shost);
+int ufs_port_add(struct ufs_port *port);
+void ufs_port_free(struct ufs_port *port);
+void ufs_port_delete(struct ufs_port *port);
+void ufs_remove_host(struct Scsi_Host *shost);
+#endif
Scsi transport is a framework that allow to send scsi commands to a non-scsi devices. Still, it is flexible enough to allow sending non-scsi commands as well. We will use this framework to manage ufs devices by sending UPIU transactions. In addition to the basic SCSI core objects this transport class introduces two additional (currently empty) class objects: “ufs-host” and “ufs-port”. There is only one “ufs-host” in the system, but can be more-than-one “ufs-ports”. Those classes are left empty for now, as ufs-sysfs already contains an abundant amount of attributes. A “ufs-port” is purely a software object. Evidently, the function template takes no port as an argument, as the driver has no concept of "port". We only need it as a hanging point in the bsg device tree, so maybe a more appropriate term would be: "ufs-bsg-dev-node". The ufs-port has a pretty lean structure. This is because we are using upiu transactions that contains the outmost detailed info, so we don't really need complex constructs to support it. The transport will keep track of its ufs-ports via its scsi host. Signed-off-by: Avri Altman <avri.altman@sandisk.com> --- drivers/scsi/Kconfig | 13 ++ drivers/scsi/Makefile | 1 + drivers/scsi/scsi_transport_ufs.c | 337 ++++++++++++++++++++++++++++++++++++++ include/scsi/scsi_transport_ufs.h | 40 +++++ 4 files changed, 391 insertions(+) create mode 100644 drivers/scsi/scsi_transport_ufs.c create mode 100644 include/scsi/scsi_transport_ufs.h