fsi: Add new central chardev support

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: default avatarBenjamin Herrenschmidt <benh@kernel.crashing.org>
parent 537052df
......@@ -12,6 +12,21 @@ menuconfig FSI
if FSI
bool "Create '/dev/fsi' directory for char devices"
default n
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.
tristate "GPIO-based FSI master"
depends on GPIOLIB
......@@ -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 */
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);
......@@ -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)
return kasprintf(GFP_KERNEL, "fsi/%s", dev_name(dev));
return kasprintf(GFP_KERNEL, "%s", dev_name(dev));
const struct device_type fsi_cdev_type = {
.name = "fsi-cdev",
.devnode = fsi_cdev_devnode,
/* Backward compatible /dev/ numbering in "old style" mode */
static int fsi_adjust_index(int index)
return index;
return index + 1;
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,
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);
void fsi_free_minor(dev_t dev)
ida_simple_remove(&fsi_minor_ida, MINOR(dev));
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);
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;
unregister_chrdev_region(fsi_base_dev, FSI_CHAR_MAX_DEVICES);
return rc;
static void fsi_exit(void)
unregister_chrdev_region(fsi_base_dev, FSI_CHAR_MAX_DEVICES);
module_param(discard_errors, int, 0664);
......@@ -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 {
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 */
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment