forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
320 lines
6.2 KiB
320 lines
6.2 KiB
/* Copyright (c) 2012 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); |
|
|
|
/* A ring buffer of error messages, to be read through |
|
* "/dev/etherd/err". When no messages are present, |
|
* readers will block waiting for messages to appear. |
|
*/ |
|
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, 0); |
|
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 = kmemdup(msg, n, GFP_ATOMIC); |
|
if (mp == NULL) { |
|
printk(KERN_ERR "aoe: allocation failure, len=%ld\n", n); |
|
goto bail; |
|
} |
|
|
|
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); |
|
break; |
|
} |
|
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"); |
|
} |
|
|
|
|