25f4d75ea4
The discovery process begins with an optional AoE config query command and an AoE config query response. Normally when an aoe device is already open, the config query response does not trigger an ATA identify device command to be sent out, since the response contains storage capacity information that, if changed, could surprise the user of the device. The userland "aoe-revalidate" tool uses a character device to trigger an AoE config query for a particular AoE storage target and an ATA device identify command, even when the device is open. This change causes the config query to go out first, reflecting the normal discovery sequence. The responses could come back in any order, so this change is fairly cosmetic. Signed-off-by: Ed Cashin <ecashin@coraid.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
316 lines
6.1 KiB
C
316 lines
6.1 KiB
C
/* Copyright (c) 2007 Coraid, Inc. See COPYING for GPL terms. */
|
|
/*
|
|
* aoechr.c
|
|
* AoE character device driver
|
|
*/
|
|
|
|
#include <linux/hdreg.h>
|
|
#include <linux/blkdev.h>
|
|
#include <linux/completion.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/export.h>
|
|
#include "aoe.h"
|
|
|
|
enum {
|
|
//MINOR_STAT = 1, (moved to sysfs)
|
|
MINOR_ERR = 2,
|
|
MINOR_DISCOVER,
|
|
MINOR_INTERFACES,
|
|
MINOR_REVALIDATE,
|
|
MINOR_FLUSH,
|
|
MSGSZ = 2048,
|
|
NMSG = 100, /* message backlog to retain */
|
|
};
|
|
|
|
struct aoe_chardev {
|
|
ulong minor;
|
|
char name[32];
|
|
};
|
|
|
|
enum { EMFL_VALID = 1 };
|
|
|
|
struct ErrMsg {
|
|
short flags;
|
|
short len;
|
|
char *msg;
|
|
};
|
|
|
|
static DEFINE_MUTEX(aoechr_mutex);
|
|
static struct ErrMsg emsgs[NMSG];
|
|
static int emsgs_head_idx, emsgs_tail_idx;
|
|
static struct completion emsgs_comp;
|
|
static spinlock_t emsgs_lock;
|
|
static int nblocked_emsgs_readers;
|
|
static struct class *aoe_class;
|
|
static struct aoe_chardev chardevs[] = {
|
|
{ MINOR_ERR, "err" },
|
|
{ MINOR_DISCOVER, "discover" },
|
|
{ MINOR_INTERFACES, "interfaces" },
|
|
{ MINOR_REVALIDATE, "revalidate" },
|
|
{ MINOR_FLUSH, "flush" },
|
|
};
|
|
|
|
static int
|
|
discover(void)
|
|
{
|
|
aoecmd_cfg(0xffff, 0xff);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
interfaces(const char __user *str, size_t size)
|
|
{
|
|
if (set_aoe_iflist(str, size)) {
|
|
printk(KERN_ERR
|
|
"aoe: could not set interface list: too many interfaces\n");
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
revalidate(const char __user *str, size_t size)
|
|
{
|
|
int major, minor, n;
|
|
ulong flags;
|
|
struct aoedev *d;
|
|
struct sk_buff *skb;
|
|
char buf[16];
|
|
|
|
if (size >= sizeof buf)
|
|
return -EINVAL;
|
|
buf[sizeof buf - 1] = '\0';
|
|
if (copy_from_user(buf, str, size))
|
|
return -EFAULT;
|
|
|
|
n = sscanf(buf, "e%d.%d", &major, &minor);
|
|
if (n != 2) {
|
|
pr_err("aoe: invalid device specification %s\n", buf);
|
|
return -EINVAL;
|
|
}
|
|
d = aoedev_by_aoeaddr(major, minor);
|
|
if (!d)
|
|
return -EINVAL;
|
|
spin_lock_irqsave(&d->lock, flags);
|
|
aoecmd_cleanslate(d);
|
|
aoecmd_cfg(major, minor);
|
|
loop:
|
|
skb = aoecmd_ata_id(d);
|
|
spin_unlock_irqrestore(&d->lock, flags);
|
|
/* try again if we are able to sleep a bit,
|
|
* otherwise give up this revalidation
|
|
*/
|
|
if (!skb && !msleep_interruptible(250)) {
|
|
spin_lock_irqsave(&d->lock, flags);
|
|
goto loop;
|
|
}
|
|
aoedev_put(d);
|
|
if (skb) {
|
|
struct sk_buff_head queue;
|
|
__skb_queue_head_init(&queue);
|
|
__skb_queue_tail(&queue, skb);
|
|
aoenet_xmit(&queue);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
aoechr_error(char *msg)
|
|
{
|
|
struct ErrMsg *em;
|
|
char *mp;
|
|
ulong flags, n;
|
|
|
|
n = strlen(msg);
|
|
|
|
spin_lock_irqsave(&emsgs_lock, flags);
|
|
|
|
em = emsgs + emsgs_tail_idx;
|
|
if ((em->flags & EMFL_VALID)) {
|
|
bail: spin_unlock_irqrestore(&emsgs_lock, flags);
|
|
return;
|
|
}
|
|
|
|
mp = kmalloc(n, GFP_ATOMIC);
|
|
if (mp == NULL) {
|
|
printk(KERN_ERR "aoe: allocation failure, len=%ld\n", n);
|
|
goto bail;
|
|
}
|
|
|
|
memcpy(mp, msg, n);
|
|
em->msg = mp;
|
|
em->flags |= EMFL_VALID;
|
|
em->len = n;
|
|
|
|
emsgs_tail_idx++;
|
|
emsgs_tail_idx %= ARRAY_SIZE(emsgs);
|
|
|
|
spin_unlock_irqrestore(&emsgs_lock, flags);
|
|
|
|
if (nblocked_emsgs_readers)
|
|
complete(&emsgs_comp);
|
|
}
|
|
|
|
static ssize_t
|
|
aoechr_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offp)
|
|
{
|
|
int ret = -EINVAL;
|
|
|
|
switch ((unsigned long) filp->private_data) {
|
|
default:
|
|
printk(KERN_INFO "aoe: can't write to that file.\n");
|
|
break;
|
|
case MINOR_DISCOVER:
|
|
ret = discover();
|
|
break;
|
|
case MINOR_INTERFACES:
|
|
ret = interfaces(buf, cnt);
|
|
break;
|
|
case MINOR_REVALIDATE:
|
|
ret = revalidate(buf, cnt);
|
|
break;
|
|
case MINOR_FLUSH:
|
|
ret = aoedev_flush(buf, cnt);
|
|
}
|
|
if (ret == 0)
|
|
ret = cnt;
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
aoechr_open(struct inode *inode, struct file *filp)
|
|
{
|
|
int n, i;
|
|
|
|
mutex_lock(&aoechr_mutex);
|
|
n = iminor(inode);
|
|
filp->private_data = (void *) (unsigned long) n;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(chardevs); ++i)
|
|
if (chardevs[i].minor == n) {
|
|
mutex_unlock(&aoechr_mutex);
|
|
return 0;
|
|
}
|
|
mutex_unlock(&aoechr_mutex);
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int
|
|
aoechr_rel(struct inode *inode, struct file *filp)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t
|
|
aoechr_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
|
|
{
|
|
unsigned long n;
|
|
char *mp;
|
|
struct ErrMsg *em;
|
|
ssize_t len;
|
|
ulong flags;
|
|
|
|
n = (unsigned long) filp->private_data;
|
|
if (n != MINOR_ERR)
|
|
return -EFAULT;
|
|
|
|
spin_lock_irqsave(&emsgs_lock, flags);
|
|
|
|
for (;;) {
|
|
em = emsgs + emsgs_head_idx;
|
|
if ((em->flags & EMFL_VALID) != 0)
|
|
break;
|
|
if (filp->f_flags & O_NDELAY) {
|
|
spin_unlock_irqrestore(&emsgs_lock, flags);
|
|
return -EAGAIN;
|
|
}
|
|
nblocked_emsgs_readers++;
|
|
|
|
spin_unlock_irqrestore(&emsgs_lock, flags);
|
|
|
|
n = wait_for_completion_interruptible(&emsgs_comp);
|
|
|
|
spin_lock_irqsave(&emsgs_lock, flags);
|
|
|
|
nblocked_emsgs_readers--;
|
|
|
|
if (n) {
|
|
spin_unlock_irqrestore(&emsgs_lock, flags);
|
|
return -ERESTARTSYS;
|
|
}
|
|
}
|
|
if (em->len > cnt) {
|
|
spin_unlock_irqrestore(&emsgs_lock, flags);
|
|
return -EAGAIN;
|
|
}
|
|
mp = em->msg;
|
|
len = em->len;
|
|
em->msg = NULL;
|
|
em->flags &= ~EMFL_VALID;
|
|
|
|
emsgs_head_idx++;
|
|
emsgs_head_idx %= ARRAY_SIZE(emsgs);
|
|
|
|
spin_unlock_irqrestore(&emsgs_lock, flags);
|
|
|
|
n = copy_to_user(buf, mp, len);
|
|
kfree(mp);
|
|
return n == 0 ? len : -EFAULT;
|
|
}
|
|
|
|
static const struct file_operations aoe_fops = {
|
|
.write = aoechr_write,
|
|
.read = aoechr_read,
|
|
.open = aoechr_open,
|
|
.release = aoechr_rel,
|
|
.owner = THIS_MODULE,
|
|
.llseek = noop_llseek,
|
|
};
|
|
|
|
static char *aoe_devnode(struct device *dev, umode_t *mode)
|
|
{
|
|
return kasprintf(GFP_KERNEL, "etherd/%s", dev_name(dev));
|
|
}
|
|
|
|
int __init
|
|
aoechr_init(void)
|
|
{
|
|
int n, i;
|
|
|
|
n = register_chrdev(AOE_MAJOR, "aoechr", &aoe_fops);
|
|
if (n < 0) {
|
|
printk(KERN_ERR "aoe: can't register char device\n");
|
|
return n;
|
|
}
|
|
init_completion(&emsgs_comp);
|
|
spin_lock_init(&emsgs_lock);
|
|
aoe_class = class_create(THIS_MODULE, "aoe");
|
|
if (IS_ERR(aoe_class)) {
|
|
unregister_chrdev(AOE_MAJOR, "aoechr");
|
|
return PTR_ERR(aoe_class);
|
|
}
|
|
aoe_class->devnode = aoe_devnode;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(chardevs); ++i)
|
|
device_create(aoe_class, NULL,
|
|
MKDEV(AOE_MAJOR, chardevs[i].minor), NULL,
|
|
chardevs[i].name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
aoechr_exit(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(chardevs); ++i)
|
|
device_destroy(aoe_class, MKDEV(AOE_MAJOR, chardevs[i].minor));
|
|
class_destroy(aoe_class);
|
|
unregister_chrdev(AOE_MAJOR, "aoechr");
|
|
}
|
|
|