fsi: Add new central chardev support
authorBenjamin Herrenschmidt <benh@kernel.crashing.org>
Wed, 20 Jun 2018 05:22:52 +0000 (15:22 +1000)
committerBenjamin Herrenschmidt <benh@kernel.crashing.org>
Thu, 26 Jul 2018 23:57:23 +0000 (09:57 +1000)
The various FSI devices (sbefifo, occ, scom, more to come)
currently use misc devices.

This is problematic as the minor device space for misc is
limited and there can be a lot of them. Also it limits our
ability to move them to a dedicated /dev/fsi directory or
to be smart about device naming and numbering.

It also means we have IDAs on every single of these drivers

This creates a common fsi "device_type" for the optional
/dev/fsi grouping and a dev_t allocator for all FSI devices.

"Legacy" devices get to use a backward compatible numbering
scheme (as long as chip id <16 and there's only one copy
of a given unit type per chip).

A single major number and a single IDA are shared for all
FSI devices.

This doesn't convert the FSI device drivers to use the new
scheme yet, they will be converted individually.

Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
drivers/fsi/Kconfig
drivers/fsi/fsi-core.c
include/linux/fsi.h

index 8d82b1e60514fc6df308dcb4aba2a2c5255ef822..af3a20dd5aa4a504524c0bd36f932ed328903c2c 100644 (file)
@@ -12,6 +12,21 @@ menuconfig FSI
 
 if FSI
 
+config FSI_NEW_DEV_NODE
+       bool "Create '/dev/fsi' directory for char devices"
+       default n
+       ---help---
+       This option causes char devices created for FSI devices to be
+       located under a common /dev/fsi/ directory. Set to N unless your
+       userspace has been updated to handle the new location.
+
+       Additionally, it also causes the char device names to be offset
+       by one so that chip 0 will have /dev/scom1 and chip1 /dev/scom2
+       to match old userspace expectations.
+
+       New userspace will use udev rules to generate predictable access
+       symlinks in /dev/fsi/by-path when this option is enabled.
+
 config FSI_MASTER_GPIO
        tristate "GPIO-based FSI master"
        depends on GPIOLIB
index eab6c5c4990edff9392e4a1ad752116a03afae6c..faa1760a5a4087df33312942f738bf8c7b9db31a 100644 (file)
@@ -92,6 +92,13 @@ struct fsi_slave {
 static const int slave_retries = 2;
 static int discard_errors;
 
+static dev_t fsi_base_dev;
+static DEFINE_IDA(fsi_minor_ida);
+#define FSI_CHAR_MAX_DEVICES   0x1000
+
+/* Legacy /dev numbering: 4 devices per chip, 16 chips */
+#define FSI_CHAR_LEGACY_TOP    64
+
 static int fsi_master_read(struct fsi_master *master, int link,
                uint8_t slave_id, uint32_t addr, void *val, size_t size);
 static int fsi_master_write(struct fsi_master *master, int link,
@@ -627,6 +634,7 @@ static void fsi_slave_release(struct device *dev)
 {
        struct fsi_slave *slave = to_fsi_slave(dev);
 
+       fsi_free_minor(slave->dev.devt);
        of_node_put(dev->of_node);
        kfree(slave);
 }
@@ -729,6 +737,75 @@ static ssize_t chip_id_show(struct device *dev,
 
 static DEVICE_ATTR_RO(chip_id);
 
+static char *fsi_cdev_devnode(struct device *dev, umode_t *mode,
+                             kuid_t *uid, kgid_t *gid)
+{
+#ifdef CONFIG_FSI_NEW_DEV_NODE
+       return kasprintf(GFP_KERNEL, "fsi/%s", dev_name(dev));
+#else
+       return kasprintf(GFP_KERNEL, "%s", dev_name(dev));
+#endif
+}
+
+const struct device_type fsi_cdev_type = {
+       .name = "fsi-cdev",
+       .devnode = fsi_cdev_devnode,
+};
+EXPORT_SYMBOL_GPL(fsi_cdev_type);
+
+/* Backward compatible /dev/ numbering in "old style" mode */
+static int fsi_adjust_index(int index)
+{
+#ifdef CONFIG_FSI_NEW_DEV_NODE
+       return index;
+#else
+       return index + 1;
+#endif
+}
+
+static int __fsi_get_new_minor(struct fsi_slave *slave, enum fsi_dev_type type,
+                              dev_t *out_dev, int *out_index)
+{
+       int cid = slave->chip_id;
+       int id;
+
+       /* Check if we qualify for legacy numbering */
+       if (cid >= 0 && cid < 16 && type < 4) {
+               /* Try reserving the legacy number */
+               id = (cid << 4) | type;
+               id = ida_simple_get(&fsi_minor_ida, id, id + 1, GFP_KERNEL);
+               if (id >= 0) {
+                       *out_index = fsi_adjust_index(cid);
+                       *out_dev = fsi_base_dev + id;
+                       return 0;
+               }
+               /* Other failure */
+               if (id != -ENOSPC)
+                       return id;
+               /* Fallback to non-legacy allocation */
+       }
+       id = ida_simple_get(&fsi_minor_ida, FSI_CHAR_LEGACY_TOP,
+                           FSI_CHAR_MAX_DEVICES, GFP_KERNEL);
+       if (id < 0)
+               return id;
+       *out_index = fsi_adjust_index(id);
+       *out_dev = fsi_base_dev + id;
+       return 0;
+}
+
+int fsi_get_new_minor(struct fsi_device *fdev, enum fsi_dev_type type,
+                     dev_t *out_dev, int *out_index)
+{
+       return __fsi_get_new_minor(fdev->slave, type, out_dev, out_index);
+}
+EXPORT_SYMBOL_GPL(fsi_get_new_minor);
+
+void fsi_free_minor(dev_t dev)
+{
+       ida_simple_remove(&fsi_minor_ida, MINOR(dev));
+}
+EXPORT_SYMBOL_GPL(fsi_free_minor);
+
 static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id)
 {
        uint32_t chip_id;
@@ -953,7 +1030,7 @@ static int fsi_slave_remove_device(struct device *dev, void *arg)
 static int fsi_master_remove_slave(struct device *dev, void *arg)
 {
        device_for_each_child(dev, NULL, fsi_slave_remove_device);
-       device_unregister(dev);
+       put_device(dev);
        return 0;
 }
 
@@ -1091,13 +1168,27 @@ EXPORT_SYMBOL_GPL(fsi_bus_type);
 
 static int __init fsi_init(void)
 {
-       return bus_register(&fsi_bus_type);
+       int rc;
+
+       rc = alloc_chrdev_region(&fsi_base_dev, 0, FSI_CHAR_MAX_DEVICES, "fsi");
+       if (rc)
+               return rc;
+       rc = bus_register(&fsi_bus_type);
+       if (rc)
+               goto fail_bus;
+       return 0;
+
+ fail_bus:
+       unregister_chrdev_region(fsi_base_dev, FSI_CHAR_MAX_DEVICES);
+       return rc;
 }
 postcore_initcall(fsi_init);
 
 static void fsi_exit(void)
 {
        bus_unregister(&fsi_bus_type);
+       unregister_chrdev_region(fsi_base_dev, FSI_CHAR_MAX_DEVICES);
+       ida_destroy(&fsi_minor_ida);
 }
 module_exit(fsi_exit);
 module_param(discard_errors, int, 0664);
index 141fd38d061ffbe168355e4938ed83bcf407eca1..ec3be0d5b786ef6f79035efde7cb4d23677df469 100644 (file)
@@ -76,8 +76,18 @@ extern int fsi_slave_read(struct fsi_slave *slave, uint32_t addr,
 extern int fsi_slave_write(struct fsi_slave *slave, uint32_t addr,
                const void *val, size_t size);
 
+extern struct bus_type fsi_bus_type;
+extern const struct device_type fsi_cdev_type;
 
+enum fsi_dev_type {
+       fsi_dev_cfam,
+       fsi_dev_sbefifo,
+       fsi_dev_scom,
+       fsi_dev_occ
+};
 
-extern struct bus_type fsi_bus_type;
+extern int fsi_get_new_minor(struct fsi_device *fdev, enum fsi_dev_type type,
+                            dev_t *out_dev, int *out_index);
+extern void fsi_free_minor(dev_t dev);
 
 #endif /* LINUX_FSI_H */