Merge tag 'driver-core-5.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git...
[sfrench/cifs-2.6.git] / drivers / s390 / cio / css.c
index a2c97830efe0375fcc76bdfb13b0c6aae5af8ce2..22c55816100be456528ac25027d13c916643b0da 100644 (file)
@@ -20,6 +20,8 @@
 #include <linux/reboot.h>
 #include <linux/suspend.h>
 #include <linux/proc_fs.h>
+#include <linux/genalloc.h>
+#include <linux/dma-mapping.h>
 #include <asm/isc.h>
 #include <asm/crw.h>
 
@@ -165,6 +167,7 @@ static void css_subchannel_release(struct device *dev)
 
        sch->config.intparm = 0;
        cio_commit_config(sch);
+       kfree(sch->driver_override);
        kfree(sch->lock);
        kfree(sch);
 }
@@ -224,6 +227,12 @@ struct subchannel *css_alloc_subchannel(struct subchannel_id schid,
        INIT_WORK(&sch->todo_work, css_sch_todo);
        sch->dev.release = &css_subchannel_release;
        device_initialize(&sch->dev);
+       /*
+        * The physical addresses of some the dma structures that can
+        * belong to a subchannel need to fit 31 bit width (e.g. ccw).
+        */
+       sch->dev.coherent_dma_mask = DMA_BIT_MASK(31);
+       sch->dev.dma_mask = &sch->dev.coherent_dma_mask;
        return sch;
 
 err:
@@ -315,9 +324,57 @@ static ssize_t modalias_show(struct device *dev, struct device_attribute *attr,
 
 static DEVICE_ATTR_RO(modalias);
 
+static ssize_t driver_override_store(struct device *dev,
+                                    struct device_attribute *attr,
+                                    const char *buf, size_t count)
+{
+       struct subchannel *sch = to_subchannel(dev);
+       char *driver_override, *old, *cp;
+
+       /* We need to keep extra room for a newline */
+       if (count >= (PAGE_SIZE - 1))
+               return -EINVAL;
+
+       driver_override = kstrndup(buf, count, GFP_KERNEL);
+       if (!driver_override)
+               return -ENOMEM;
+
+       cp = strchr(driver_override, '\n');
+       if (cp)
+               *cp = '\0';
+
+       device_lock(dev);
+       old = sch->driver_override;
+       if (strlen(driver_override)) {
+               sch->driver_override = driver_override;
+       } else {
+               kfree(driver_override);
+               sch->driver_override = NULL;
+       }
+       device_unlock(dev);
+
+       kfree(old);
+
+       return count;
+}
+
+static ssize_t driver_override_show(struct device *dev,
+                                   struct device_attribute *attr, char *buf)
+{
+       struct subchannel *sch = to_subchannel(dev);
+       ssize_t len;
+
+       device_lock(dev);
+       len = snprintf(buf, PAGE_SIZE, "%s\n", sch->driver_override);
+       device_unlock(dev);
+       return len;
+}
+static DEVICE_ATTR_RW(driver_override);
+
 static struct attribute *subch_attrs[] = {
        &dev_attr_type.attr,
        &dev_attr_modalias.attr,
+       &dev_attr_driver_override.attr,
        NULL,
 };
 
@@ -899,6 +956,13 @@ static int __init setup_css(int nr)
        dev_set_name(&css->device, "css%x", nr);
        css->device.groups = cssdev_attr_groups;
        css->device.release = channel_subsystem_release;
+       /*
+        * We currently allocate notifier bits with this (using
+        * css->device as the device argument with the DMA API)
+        * and are fine with 64 bit addresses.
+        */
+       css->device.coherent_dma_mask = DMA_BIT_MASK(64);
+       css->device.dma_mask = &css->device.coherent_dma_mask;
 
        mutex_init(&css->mutex);
        css->cssid = chsc_get_cssid(nr);
@@ -1018,6 +1082,111 @@ static struct notifier_block css_power_notifier = {
        .notifier_call = css_power_event,
 };
 
+#define  CIO_DMA_GFP (GFP_KERNEL | __GFP_ZERO)
+static struct gen_pool *cio_dma_pool;
+
+/* Currently cio supports only a single css */
+struct device *cio_get_dma_css_dev(void)
+{
+       return &channel_subsystems[0]->device;
+}
+
+struct gen_pool *cio_gp_dma_create(struct device *dma_dev, int nr_pages)
+{
+       struct gen_pool *gp_dma;
+       void *cpu_addr;
+       dma_addr_t dma_addr;
+       int i;
+
+       gp_dma = gen_pool_create(3, -1);
+       if (!gp_dma)
+               return NULL;
+       for (i = 0; i < nr_pages; ++i) {
+               cpu_addr = dma_alloc_coherent(dma_dev, PAGE_SIZE, &dma_addr,
+                                             CIO_DMA_GFP);
+               if (!cpu_addr)
+                       return gp_dma;
+               gen_pool_add_virt(gp_dma, (unsigned long) cpu_addr,
+                                 dma_addr, PAGE_SIZE, -1);
+       }
+       return gp_dma;
+}
+
+static void __gp_dma_free_dma(struct gen_pool *pool,
+                             struct gen_pool_chunk *chunk, void *data)
+{
+       size_t chunk_size = chunk->end_addr - chunk->start_addr + 1;
+
+       dma_free_coherent((struct device *) data, chunk_size,
+                        (void *) chunk->start_addr,
+                        (dma_addr_t) chunk->phys_addr);
+}
+
+void cio_gp_dma_destroy(struct gen_pool *gp_dma, struct device *dma_dev)
+{
+       if (!gp_dma)
+               return;
+       /* this is quite ugly but no better idea */
+       gen_pool_for_each_chunk(gp_dma, __gp_dma_free_dma, dma_dev);
+       gen_pool_destroy(gp_dma);
+}
+
+static int cio_dma_pool_init(void)
+{
+       /* No need to free up the resources: compiled in */
+       cio_dma_pool = cio_gp_dma_create(cio_get_dma_css_dev(), 1);
+       if (!cio_dma_pool)
+               return -ENOMEM;
+       return 0;
+}
+
+void *cio_gp_dma_zalloc(struct gen_pool *gp_dma, struct device *dma_dev,
+                       size_t size)
+{
+       dma_addr_t dma_addr;
+       unsigned long addr;
+       size_t chunk_size;
+
+       if (!gp_dma)
+               return NULL;
+       addr = gen_pool_alloc(gp_dma, size);
+       while (!addr) {
+               chunk_size = round_up(size, PAGE_SIZE);
+               addr = (unsigned long) dma_alloc_coherent(dma_dev,
+                                        chunk_size, &dma_addr, CIO_DMA_GFP);
+               if (!addr)
+                       return NULL;
+               gen_pool_add_virt(gp_dma, addr, dma_addr, chunk_size, -1);
+               addr = gen_pool_alloc(gp_dma, size);
+       }
+       return (void *) addr;
+}
+
+void cio_gp_dma_free(struct gen_pool *gp_dma, void *cpu_addr, size_t size)
+{
+       if (!cpu_addr)
+               return;
+       memset(cpu_addr, 0, size);
+       gen_pool_free(gp_dma, (unsigned long) cpu_addr, size);
+}
+
+/*
+ * Allocate dma memory from the css global pool. Intended for memory not
+ * specific to any single device within the css. The allocated memory
+ * is not guaranteed to be 31-bit addressable.
+ *
+ * Caution: Not suitable for early stuff like console.
+ */
+void *cio_dma_zalloc(size_t size)
+{
+       return cio_gp_dma_zalloc(cio_dma_pool, cio_get_dma_css_dev(), size);
+}
+
+void cio_dma_free(void *cpu_addr, size_t size)
+{
+       cio_gp_dma_free(cio_dma_pool, cpu_addr, size);
+}
+
 /*
  * Now that the driver core is running, we can setup our channel subsystem.
  * The struct subchannel's are created during probing.
@@ -1059,16 +1228,22 @@ static int __init css_bus_init(void)
        if (ret)
                goto out_unregister;
        ret = register_pm_notifier(&css_power_notifier);
-       if (ret) {
-               unregister_reboot_notifier(&css_reboot_notifier);
-               goto out_unregister;
-       }
+       if (ret)
+               goto out_unregister_rn;
+       ret = cio_dma_pool_init();
+       if (ret)
+               goto out_unregister_pmn;
+       airq_init();
        css_init_done = 1;
 
        /* Enable default isc for I/O subchannels. */
        isc_register(IO_SCH_ISC);
 
        return 0;
+out_unregister_pmn:
+       unregister_pm_notifier(&css_power_notifier);
+out_unregister_rn:
+       unregister_reboot_notifier(&css_reboot_notifier);
 out_unregister:
        while (i-- > 0) {
                struct channel_subsystem *css = channel_subsystems[i];
@@ -1222,6 +1397,10 @@ static int css_bus_match(struct device *dev, struct device_driver *drv)
        struct css_driver *driver = to_cssdriver(drv);
        struct css_device_id *id;
 
+       /* When driver_override is set, only bind to the matching driver */
+       if (sch->driver_override && strcmp(sch->driver_override, drv->name))
+               return 0;
+
        for (id = driver->subchannel_type; id->match_flags; id++) {
                if (sch->st == id->type)
                        return 1;