Re: [RFC PATCHES] Re: Is configfs the right solution for configuration based fs?

Ben Nizette
2008-06-20T09:22:45Z

[-- Attachment #1: Type: text/plain, Size: 1598 bytes --]

On Thu, 2008-06-19 at 23:52 -0700, Joel Becker wrote:
> On Fri, Jun 20, 2008 at 04:19:10PM +1000, Ben Nizette wrote:
> > Looks great, thx :-). I'll convert some of my stuff over to this
> > interface and see how it flies in the real world.
>
> Excellent. What is your stuff, btw? Got code? Love to see it.

I've done a few things recently. Haavard Skinnemoen has got an
out-of-tree, avr32-specific gpio interface [1] which I first converted
to the generic gpio framework then completely re-wrote. Both use
configfs but before either version could reach completion they were
obsoleted by David Brownell's gpio sysfs interface.

A version of my gpio-dev interface is attached. Bear in mind it was
never completed, it's full of known bugs but hey, might be useful for
you anyway :-)

At the moment I'm experimenting with configfs for pinmux control on
AVR32 SoCs. There's really nothing to see there yet, it's all just
dicking about atm.

>
> > ISTR when I did this I kept the old semantics and used ERR_PTR and
> > friends if things went pear-shaped. Given the small number of in-tree
> > users needing to be moved over, a more intrusive change for the sake of
> > a cleaner API like you've done is probably a good thing anyway :-)
>
> Yeah, I like this better than ERR_PTR.
> If you like the macros, I'll try to make the next merge window
> as well. So let me know.

np, will do :-)

--Ben.

>
> joel
>

[1]
http://git.kernel.org/?p=linux/kernel/git/hskinnemoen/avr32-2.6.git;a=blob;f=arch/avr32/mach-at32ap/gpio-dev.c;h=8cf6d1182c31db88fc069a046112a4eaac589308;hb=atmel-2.6.25

[-- Attachment #2: gpio-dev.c --]
[-- Type: text/x-csrc, Size: 21720 bytes --]

/*
* GPIO /dev and configfs interface
*
* Copyright (C) 2008 Nias Digital P/L
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/spinlock.h>
#include <linux/configfs.h>
#include <linux/types.h>
#include <linux/platform_device.h>
#include <linux/poll.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/kfifo.h>

#include <asm/uaccess.h>
#include <asm/bug.h>
#include <asm/gpio.h>

#include "gpio-dev.h"

#define MAX_NR_DEVICES 8
#define IRQ_BUFFER_SIZE 256

#define BUF_PUT_INT(k, b) \
do { \
char *p = (char *)&b; \
kfifo_put(k, p, sizeof(int)); \
} while (0)

#define BUF_GET_INT(k, b) \
do { \
char *p = (char *)&b; \
kfifo_get(k, p, sizeof(int)); \
} while (0)

static struct class *dev_class;
static dev_t base_devt;

struct gpiodev {
spinlock_t lock;

int id;
char *bankname;

struct gpiodev_pin *pins;
int nr_pins;

int io_enabled;
int irq_enabled;

struct kfifo *irq_buf;
spinlock_t irq_lock;

struct device *io_dev;
struct device *irq_dev;

/* We can unify these somewhat by doing some fop switching
* magic, though I can't convince myself it's worth it*/
struct cdev io_char_dev;
struct cdev irq_char_dev;

wait_queue_head_t irq_wq;
struct fasync_struct *async_queue;

struct config_group group;
};

struct gpiodev *devices[MAX_NR_DEVICES];
spinlock_t devices_lock;

static irqreturn_t irq_interrupt(int irq, void *dev_id)
{
int i, found = 0;
int gpio = irq_to_gpio(irq);

struct gpiodev *bank = dev_id;

for (i = 0; i < bank->nr_pins; i++) {
if (bank->pins[i].pin == gpio) {
found = 1;
break;
}
}

if (!found)
return IRQ_NONE;

BUF_PUT_INT(bank->irq_buf, i);

wake_up_interruptible(&bank->irq_wq);

if (bank->async_queue)
kill_fasync(&bank->async_queue, SIGIO, POLL_IN);

return IRQ_HANDLED;
}

static int io_open(struct inode *inode, struct file *file)
{
struct gpiodev *bank = container_of(inode->i_cdev,
struct gpiodev,
io_char_dev);

spin_lock(&bank->lock);
config_item_get(&bank->group.cg_item);
file->private_data = bank;
spin_unlock(&bank->lock);

return 0;
}

static int io_release(struct inode *inode, struct file *file)
{
struct gpiodev *bank = file->private_data;

spin_lock(&bank->lock);
config_item_put(&bank->group.cg_item);
spin_unlock(&bank->lock);

return 0;
}

static ssize_t io_read(struct file *file, char __user *buf,
size_t count, loff_t *offset)
{
int i, ret = 0;
char *val;

struct gpiodev *bank = file->private_data;

count = min(count, (size_t)(bank->nr_pins - *offset));

val = kmalloc(sizeof(char) * count, GFP_KERNEL);
if (!val)
return -ENOMEM;

spin_lock(&bank->lock);
for (i = *offset; i < *offset + count; i++) {
int pin = bank->pins[i].pin;
int value;

if (file->f_flags & O_NONBLOCK)
value = gpio_get_value(pin);
else
value = gpio_get_value_cansleep(pin);

val[i] = (!!value) + '0';
}

*offset += count;
spin_unlock(&bank->lock);

if (copy_to_user(buf, val, count))
ret = -EFAULT;

kfree(val);
return ret ? ret : count;
}

static ssize_t io_write(struct file *file, const char __user *buf,
size_t count, loff_t *offset)
{
int i, ret = 0;
char *val, *p;

struct gpiodev *bank = file->private_data;

count = min(count, (size_t)(bank->nr_pins - *offset));

val = kmalloc(sizeof(char) * count, GFP_KERNEL);
if (!val)
return -ENOMEM;

if (copy_from_user(val, buf, count)) {
ret = -EFAULT;
goto out_cpy;
}

p = val;

spin_lock(&bank->lock);

for (i = *offset; i < *offset + count; i++) {
int pin = bank->pins[i].pin;
int value = val[i] - '0';

if (value != 0 || value != 1)
break;

bank->pins[i].outval = value;

if (file->f_flags & O_NONBLOCK)
gpio_set_value(pin, value);
else
gpio_set_value_cansleep(pin, value);
}

*offset = i;

spin_unlock(&bank->lock);

out_cpy:
kfree(val);
return ret ? ret : count;
}

static loff_t io_llseek(struct file *file, loff_t off, int whence)
{
struct gpiodev *bank = file->private_data;
loff_t newpos;

spin_lock(&bank->lock);

switch (whence) {
case SEEK_SET:
newpos = off;
break;
case SEEK_CUR:
newpos = file->f_pos + off;
break;
case SEEK_END:
newpos = bank->nr_pins + off;
break;
default:
return -EINVAL;
}

spin_unlock(&bank->lock);

return newpos;
}

static struct file_operations io_fops = {
.owner = THIS_MODULE,
.llseek = io_llseek,
.open = io_open,
.release = io_release,
.read = io_read,
.write = io_write,
};

static int irq_open(struct inode *inode, struct file *file)
{
struct gpiodev *bank = container_of(inode->i_cdev,
struct gpiodev,
io_char_dev);

nonseekable_open(inode, file);

spin_lock(&bank->lock);
config_item_get(&bank->group.cg_item);
file->private_data = bank;
spin_unlock(&bank->lock);

return 0;
}

static int irq_release(struct inode *inode, struct file *file)
{
struct gpiodev *bank = file->private_data;

spin_lock(&bank->lock);
config_item_put(&bank->group.cg_item);
spin_unlock(&bank->lock);

return 0;
}

static ssize_t irq_read(struct file *file, char __user *buf,
size_t count, loff_t *offset)
{
int i, len, *kbuf;
struct gpiodev *bank = file->private_data;

while (!kfifo_len(bank->irq_buf)) {

if (file->f_flags & O_NONBLOCK)
return -EAGAIN;

if (wait_event_interruptible(bank->irq_wq,
kfifo_len(bank->irq_buf)))
return -ERESTARTSYS;

}

len = min((size_t)kfifo_len(bank->irq_buf), count);

kbuf = kmalloc(len * sizeof(int), GFP_KERNEL);

if (!kbuf)
return -ENOMEM;

for (i = 0; i < len; i++)
BUF_GET_INT(bank->irq_buf, kbuf[i]);

if (copy_to_user(buf, kbuf, len)) {
kfree(kbuf);
return -EFAULT;
}

kfree(kbuf);
return len;
}

static ssize_t irq_write(struct file *file, const char __user *buf,
size_t count, loff_t *offset)
{
struct gpiodev *bank = file->private_data;

/* Flush buffer */
kfifo_reset(bank->irq_buf);

return count;
}

static int irq_fasync(int fd, struct file *file, int mode)
{
struct gpiodev *bank = file->private_data;

return fasync_helper(fd, file, mode, &bank->async_queue);
}

static struct file_operations irq_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.open = irq_open,
.release = irq_release,
.read = irq_read,
.write = irq_write,
.fasync = irq_fasync,
};

/*
* {enable,disable}_bank_{io,irq}() must be called under that bank's
* lock. They should only be called through their
* set_bank_{io,irq}_enabled() which will (amongst other things)
* take care of this locking for you.
*/
static int enable_bank_io(struct gpiodev *bank)
{
int i;

/* What error should a pin request failure be?? */
int ret = -ENODEV;

BUG_ON(!bank);

for (i = 0; i < bank->nr_pins; i++) {
if (!gpio_request(bank->pins[i].pin, bank->pins[i].label))
goto err_alloc_pins;
}

cdev_init(&bank->io_char_dev, &io_fops);
bank->io_char_dev.owner = THIS_MODULE;
ret = cdev_add(&bank->io_char_dev, MKDEV(MAJOR(base_devt),
bank->id * 2), 1);

if (ret < 0)
goto err_cdev_add;

bank->io_dev = device_create(dev_class, NULL,
MKDEV(MAJOR(base_devt), bank->id * 2),
"gpiod-%s", bank->bankname);

if (IS_ERR(bank->io_dev)) {
printk(KERN_ERR "gpio-dev: failed to create gpiod%d\n",
bank->id);
ret = PTR_ERR(bank->io_dev);
goto err_dev;
}

printk(KERN_INFO "gpio-dev: created gpiod%d as (%d:%d)\n",
bank->id, MAJOR(bank->io_dev->devt),
MINOR(bank->io_dev->devt));

return 0;

err_dev:
cdev_del(&bank->io_char_dev);
err_cdev_add:
err_alloc_pins:

while (i--)
gpio_free(bank->pins[i].pin);

return ret;
}

static int disable_bank_io(struct gpiodev *bank)
{
int i;

BUG_ON(!bank);

device_unregister(bank->io_dev);
cdev_del(&bank->io_char_dev);

for (i = 0; i < bank->nr_pins; i++)
gpio_free(bank->pins[i].pin);

return 0;
}

static int set_bank_io_enabled(struct gpiodev *bank, int enabled)
{
int ret = 0;

if (!bank) {
printk(KERN_ERR "gpio-dev: Attempt to change enable " \
"without valid device binding\n");
return -EINVAL;
}

pr_debug("gpio-dev: setting bank %s enabled from %d to %d\n",
bank->bankname, bank->io_enabled, enabled);

spin_lock(&bank->lock);

if (bank->io_enabled == enabled)
goto out;

if (enabled)
ret = enable_bank_io(bank);
else
ret = disable_bank_io(bank);

if (!ret)
bank->io_enabled = enabled;

out:
spin_unlock(&bank->lock);
return ret;
}

static int enable_bank_irq(struct gpiodev *bank)
{
int ret;

BUG_ON(!bank);

cdev_init(&bank->irq_char_dev, &irq_fops);
bank->irq_char_dev.owner = THIS_MODULE;
ret = cdev_add(&bank->irq_char_dev, MKDEV(MAJOR(base_devt),
bank->id * 2 + 1), 1);

if (ret < 0)
goto err_cdev_add;

bank->irq_dev = device_create(dev_class, NULL,
MKDEV(MAJOR(base_devt), bank->id),
"gpioi-%s", bank->bankname);

if (IS_ERR(bank->irq_dev)) {
printk(KERN_ERR "gpio-dev: failed to create gpioi%d\n",
bank->id);
ret = PTR_ERR(bank->irq_dev);
goto err_dev;
}

printk(KERN_INFO "gpio-dev: created gpioi%d as (%d:%d)\n",
bank->id, MAJOR(bank->irq_dev->devt),
MINOR(bank->irq_dev->devt));

return 0;

err_dev:
cdev_del(&bank->irq_char_dev);
err_cdev_add:

return ret;
}

static int disable_bank_irq(struct gpiodev *bank)
{
BUG_ON(!bank);

device_unregister(bank->irq_dev);
cdev_del(&bank->irq_char_dev);

return 0;
}

static int set_bank_irq_enabled(struct gpiodev *bank, int enabled)
{
int ret = 0;

if (!bank) {
printk(KERN_ERR "gpio-dev: Attempt to change irq enable " \
"without valid device binding\n");
return -EINVAL;
}

pr_debug("gpio-dev: setting bank irq %s enabled from %d to %d\n",
bank->bankname, bank->irq_enabled, enabled);

spin_lock(&bank->lock);

if (bank->irq_enabled == enabled)
goto out;

if (enabled)
ret = enable_bank_irq(bank);
else
ret = disable_bank_irq(bank);

if (!ret)
bank->irq_enabled = enabled;

out:
spin_unlock(&bank->lock);
return ret;
}

static inline struct gpiodev *to_gpiodev(struct config_group *group)
{
return group ? container_of(group, struct gpiodev, group) : NULL;
}

static struct configfs_attribute bank_attr_enableio = {
.ca_owner = THIS_MODULE,
.ca_name = "enable-io",
.ca_mode = S_IRUGO | S_IWUGO,
};

static struct configfs_attribute bank_attr_enableirq = {
.ca_owner = THIS_MODULE,
.ca_name = "enable-irq",
.ca_mode = S_IRUGO | S_IWUGO,
};

static struct configfs_attribute *cfg_bank_attrs[] = {
&bank_attr_enableio,
&bank_attr_enableirq,
NULL,
};

static ssize_t cfg_bank_attr_show(struct config_item *item,
struct configfs_attribute *attr,
char *page)
{
struct gpiodev *bank = to_gpiodev(to_config_group(item));

if (attr == &bank_attr_enableio)
return sprintf(page, "%d\n", bank->io_enabled);
else if (attr == &bank_attr_enableirq)
return sprintf(page, "%d\n", bank->irq_enabled);
else
WARN_ON(1);

return 0;
}

static ssize_t cfg_bank_attr_store(struct config_item *item,
struct configfs_attribute *attr,
const char *page, size_t count)
{
struct gpiodev *bank = to_gpiodev(to_config_group(item));

/* The only attributes which will come here are enable-{io,irq} */

unsigned long tmp;
int ret = 0;
char *p = (char *) page;

tmp = simple_strtoul(p, &p, 10);
if (!p || (*p && (*p != '\n')))
return -EINVAL;

if (tmp > INT_MAX)
return -ERANGE;

/* Booleanize */
tmp = !!tmp;

pr_debug("gpio-dev: changing enabled attr on bank %s, attribute %d\n",
bank->bankname, (int)attr);

if (attr == &bank_attr_enableio)
ret = set_bank_io_enabled(bank, tmp);
else if (attr == &bank_attr_enableirq)
ret = set_bank_irq_enabled(bank, tmp);
else {
printk(KERN_ERR "unexpected attribute at %s, %d",
__func__, __LINE__);
WARN_ON(1);
}

return ret ? ret : count;
}

static struct configfs_item_operations cfg_bank_item_ops = {
.show_attribute = cfg_bank_attr_show,
.store_attribute = cfg_bank_attr_store,
};

static struct config_item_type cfg_bank_type = {
.ct_owner = THIS_MODULE,
.ct_item_ops = &cfg_bank_item_ops,
.ct_attrs = cfg_bank_attrs,
};

static struct config_group *cfg_make_group(struct config_group *group,
const char *name)
{
int i;

struct gpiodev *dev = NULL;

spin_lock(&devices_lock);
for (i = 0; i < MAX_NR_DEVICES; i++) {
if (!devices[i])
continue;

if (strcmp(name, devices[i]->bankname) == 0) {
dev = devices[i];
printk(KERN_INFO "gpio-dev: Bound configfs instance" \
" to device %s\n", devices[i]->bankname);
}
}
spin_unlock(&devices_lock);

if (!dev) {
printk(KERN_WARNING "gpio-dev: Can't find bank \"%s\"\n", name);
/*
* Once I get home and can modify the configfs_mkdir code
* to allow more informative return types we can modify this
* to, eg, ERR_PTR(-ENODEV)
*/
return NULL;
}

/*
* REVISIT: Do we need to get this here? The docco says we should drop
* a reference in drop_group() so I assume we need to actually get one
* at one stage
*/
config_item_get(&dev->group.cg_item);

return &dev->group;
}

static void cfg_drop_group(struct config_group *group,
struct config_item *item)
{
struct gpiodev *bank = to_gpiodev(to_config_group(item));

pr_debug("gpio-dev: bank %s getting dropped\n",
bank->bankname);

set_bank_io_enabled(bank, 0);
set_bank_irq_enabled(bank, 0);

config_item_put(item);
}

static struct configfs_attribute attr_banks = {
.ca_owner = THIS_MODULE,
.ca_name = "banks",
.ca_mode = S_IRUGO,
};

static struct configfs_attribute *cfg_attrs[] = {
&attr_banks,
NULL,
};

static ssize_t cfg_attr_show(struct config_item *item,
struct configfs_attribute *attr,
char *page)
{
int i;
int count = 0;

/* The only attribute which comes here is 'banks' */

spin_lock(&devices_lock);
for (i = 0; i < MAX_NR_DEVICES; i++) {
if (devices[i])
count += sprintf(page + count, "%s\n",
devices[i]->bankname);
}
spin_unlock(&devices_lock);

return count;
}

static struct configfs_item_operations cfg_item_ops = {
.show_attribute = cfg_attr_show,
};

static struct configfs_group_operations cfg_group_ops = {
.make_group = cfg_make_group,
.drop_item = cfg_drop_group,
};

static struct config_item_type cfg_type = {
.ct_item_ops = &cfg_item_ops,
.ct_group_ops = &cfg_group_ops,
.ct_attrs = cfg_attrs,
.ct_owner = THIS_MODULE,
};

static struct configfs_subsystem cfg_subsys = {
.su_group = {
.cg_item = {
.ci_namebuf = "gpio-dev",
.ci_type = &cfg_type,
},
},
};

static struct configfs_attribute pin_attr_index = {
.ca_owner = THIS_MODULE,
.ca_name = "index",
.ca_mode = S_IRUGO,
};

static struct configfs_attribute pin_attr_direction = {
.ca_owner = THIS_MODULE,
.ca_name = "direction",
.ca_mode = S_IRUGO | S_IWUGO,
};

static struct configfs_attribute pin_attr_irq = {
.ca_owner = THIS_MODULE,
.ca_name = "irq",
.ca_mode = S_IRUGO | S_IWUGO,
};

static struct configfs_attribute *pin_attrs[] = {
&pin_attr_index,
&pin_attr_direction,
&pin_attr_irq,
NULL,
};

static inline struct gpiodev_pin *to_gpiodev_pin(struct config_group *group)
{
return group ? container_of(group, struct gpiodev_pin, group) : NULL;
}

static ssize_t pin_show(struct config_item *item,
struct configfs_attribute *attr,
char *page)
{
struct gpiodev_pin *pin = to_gpiodev_pin(to_config_group(item));

/* All attributes for a pin come here */

if (attr == &pin_attr_index)
return sprintf(page, "%d\n", pin->index);
else if (attr == &pin_attr_direction)
return sprintf(page, "%d\n", pin->direction);
else if (attr == &pin_attr_irq)
return sprintf(page, "%d\n", pin->irq);
else {
printk(KERN_ERR "gpio-dev: Unexpected attribute in %s\n",
__func__);
return 0;
}
}

static ssize_t pin_store(struct config_item *item,
struct configfs_attribute *attr,
const char *page, size_t count)
{
struct gpiodev_pin *pin = to_gpiodev_pin(to_config_group(item));
char *p = (char *)page;
int val;
int ret = 0;

val = simple_strtoul(p, &p, 0);
if (!p || (*p && (*p != '\n')))
return -EINVAL;

if (attr == &pin_attr_direction) {
pin->direction = val;

if (val)
gpio_direction_output(pin->pin, pin->outval);
else
gpio_direction_input(pin->pin);

} else if (attr == &pin_attr_irq) {

if (!pin->irq && val) {
ret = request_irq(gpio_to_irq(pin->pin), irq_interrupt,
0, "gpio-dev", pin);
if (ret)
return ret;
} else if (pin->irq && !val) {
free_irq(gpio_to_irq(pin->pin), pin);
}

pin->irq = val;

/*
* The values written by the user directly correspond to
* the values behind the IRQF_TRIGGER_* tokens. Can we
* rely on this or should we explicitly check and set each
* possible trigger type?
*/
if (val)
ret = set_irq_type(gpio_to_irq(pin->pin), val);

if (ret)
return -EINVAL;

} else {
printk(KERN_ERR "gpio-dev: Unexpected attribute in %s\n",
__func__);
}

return count;
}

static struct configfs_item_operations pin_ops = {
.show_attribute = pin_show,
.store_attribute = pin_store,
};

static struct config_item_type cfg_pin_type = {
.ct_owner = THIS_MODULE,
.ct_attrs = pin_attrs,
.ct_item_ops = &pin_ops,
};

static int __init gpiodev_probe(struct platform_device *pdev)
{
int i, ret;
struct gpiodev *bank = NULL;
struct device *dev = &pdev->dev;
struct gpiodev_pdata *pdata = dev->platform_data;

if (!pdata) {
printk(KERN_ERR "gpio-dev: No configuration data available; "\
"aborting probe.");
return -EINVAL;
}

bank = kzalloc(sizeof(struct gpiodev), GFP_KERNEL);
if (!bank)
return -ENOMEM;

spin_lock_init(&bank->lock);
spin_lock_init(&bank->irq_lock);

bank->bankname = pdata->bankname;
bank->nr_pins = pdata->nr_pins;

bank->pins = kmalloc(bank->nr_pins * sizeof(struct gpiodev_pin),
GFP_KERNEL);

if (!bank->pins) {
ret = -ENOMEM;
goto err_pin_alloc;
}

bank->irq_buf = kfifo_alloc(IRQ_BUFFER_SIZE, GFP_KERNEL,
&bank->irq_lock);

if (IS_ERR(bank->irq_buf)) {
ret = PTR_ERR(bank->irq_buf);
goto err_irq_buf_alloc;
}

memcpy(bank->pins, pdata->pins,
bank->nr_pins * sizeof(struct gpiodev_pin));

pr_debug("gpio-dev: initing device group %s\n", bank->bankname);

config_group_init_type_name(&bank->group, bank->bankname,
&cfg_bank_type);

bank->group.default_groups = kzalloc(sizeof(struct config_group)
* bank->nr_pins, GFP_KERNEL);

if (!bank->group.default_groups) {
ret = -ENOMEM;
goto err_group_alloc;
}

for (i = 0; i < bank->nr_pins; i++) {
pr_debug("gpio-dev: initing pin %s on bank %s\n",
bank->pins[i].label, bank->bankname);
config_group_init_type_name(&bank->pins[i].group,
bank->pins[i].label, &cfg_pin_type);
bank->group.default_groups[i] = &bank->pins[i].group;
}

bank->io_enabled = 0;
bank->irq_enabled = 0;

platform_set_drvdata(pdev, bank);

bank->id = -1;

/* Find first empty slot */
spin_lock(&devices_lock);
for (i = 0; i < MAX_NR_DEVICES; i++) {
if (devices[i] == NULL) {
devices[i] = bank;
bank->id = i;
break;
}
}
spin_unlock(&devices_lock);

if (bank->id == -1) {
printk(KERN_ERR "gpio-dev: More than MAX_NR_DEVICES probed;"\
" no free slots found!");
ret = -ENODEV;
goto err_no_slot;
}

return 0;

err_no_slot:
kfree(bank->group.default_groups);
err_group_alloc:
kfifo_free(bank->irq_buf);
err_irq_buf_alloc:
kfree(bank->pins);
err_pin_alloc:
kfree(bank);

return ret;
}

static int __exit gpiodev_remove(struct platform_device *pdev)
{
struct gpiodev *bank = platform_get_drvdata(pdev);

spin_lock(&devices_lock);
devices[bank->id] = NULL;
spin_unlock(&devices_lock);

kfifo_free(bank->irq_buf);

kfree(bank->pins);
kfree(bank);

return 0;
}

static struct platform_driver gpiodev_driver = {
.driver = {
.name = "gpio-dev",
.owner = THIS_MODULE,
},
.remove = __exit_p(gpiodev_remove),
.probe = gpiodev_probe,
};

static int __init gpiodev_init(void)
{
int ret;

spin_lock_init(&devices_lock);

dev_class = class_create(THIS_MODULE, "gpio-dev");
if (IS_ERR(dev_class)) {
ret = PTR_ERR(dev_class);
goto err_class_create;
}

ret = alloc_chrdev_region(&base_devt, 0, MAX_NR_DEVICES * 2, "gpio-dev");
if (ret < 0)
goto err_alloc_chrdev;

pr_debug("gpio-dev: Registering configfs subsystem %s\n",
cfg_subsys.su_group.cg_item.ci_namebuf);
config_group_init(&cfg_subsys.su_group);
mutex_init(&cfg_subsys.su_mutex);
ret = configfs_register_subsystem(&cfg_subsys);
if (ret) {
printk(KERN_ERR "Error %d while registering subsystem %s\n",
ret,
cfg_subsys.su_group.cg_item.ci_namebuf);
goto err_subsys;
}

ret = platform_driver_register(&gpiodev_driver);
if (ret)
goto err_dev_register;

return 0;

err_dev_register:
configfs_unregister_subsystem(&cfg_subsys);
err_subsys:
unregister_chrdev_region(base_devt, MAX_NR_DEVICES);
err_alloc_chrdev:
class_destroy(dev_class);
err_class_create:
printk(KERN_ALERT "gpio-dev: Failed to init gpio-dev interface\n");
return ret;
}

static void __exit gpiodev_exit(void)
{
platform_driver_unregister(&gpiodev_driver);

configfs_unregister_subsystem(&cfg_subsys);

unregister_chrdev_region(base_devt, MAX_NR_DEVICES * 2);

class_destroy(dev_class);

printk(KERN_INFO "gpio-dev: unloaded gpio-dev\n");
}

module_init(gpiodev_init);
module_exit(gpiodev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Ben Nizette <bn@niasdigital.com>");
MODULE_DESCRIPTION("Userspace interface to the gpio framework");