/* Copyright (c) 2004 Coraid, Inc. See COPYING for GPL terms. */ /* * aoechr.c * AoE character device driver */ #include #include #include "aoe.h" enum { //MINOR_STAT = 1, (moved to sysfs) MINOR_ERR = 2, MINOR_DISCOVER, MINOR_INTERFACES, MSGSZ = 2048, NARGS = 10, 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 struct ErrMsg emsgs[NMSG]; static int emsgs_head_idx, emsgs_tail_idx; static struct semaphore emsgs_sema; 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" }, }; 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_CRIT "%s: could not set interface list: %s\n", __FUNCTION__, "too many interfaces"); return -EINVAL; } 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_CRIT "aoe: aoechr_error: 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) up(&emsgs_sema); } 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: aoechr_write: can't write to that file.\n"); break; case MINOR_DISCOVER: ret = discover(); break; case MINOR_INTERFACES: ret = interfaces(buf, cnt); break; } if (ret == 0) ret = cnt; return ret; } static int aoechr_open(struct inode *inode, struct file *filp) { int n, i; n = MINOR(inode->i_rdev); filp->private_data = (void *) (unsigned long) n; for (i = 0; i < ARRAY_SIZE(chardevs); ++i) if (chardevs[i].minor == n) return 0; 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; switch (n) { case MINOR_ERR: spin_lock_irqsave(&emsgs_lock, flags); loop: em = emsgs + emsgs_head_idx; if ((em->flags & EMFL_VALID) == 0) { if (filp->f_flags & O_NDELAY) { spin_unlock_irqrestore(&emsgs_lock, flags); return -EAGAIN; } nblocked_emsgs_readers++; spin_unlock_irqrestore(&emsgs_lock, flags); n = down_interruptible(&emsgs_sema); spin_lock_irqsave(&emsgs_lock, flags); nblocked_emsgs_readers--; if (n) { spin_unlock_irqrestore(&emsgs_lock, flags); return -ERESTARTSYS; } goto loop; } 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; default: return -EFAULT; } } static struct file_operations aoe_fops = { .write = aoechr_write, .read = aoechr_read, .open = aoechr_open, .release = aoechr_rel, .owner = THIS_MODULE, }; int __init aoechr_init(void) { int n, i; n = register_chrdev(AOE_MAJOR, "aoechr", &aoe_fops); if (n < 0) { printk(KERN_ERR "aoe: aoechr_init: can't register char device\n"); return n; } sema_init(&emsgs_sema, 0); 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); } for (i = 0; i < ARRAY_SIZE(chardevs); ++i) class_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) class_device_destroy(aoe_class, MKDEV(AOE_MAJOR, chardevs[i].minor)); class_destroy(aoe_class); unregister_chrdev(AOE_MAJOR, "aoechr"); }