| /* 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"); | 
 | } | 
 |  |