/* * crypto_dev.c * * Copyright (c) 2004 Evgeniy Polyakov * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include "acrypto.h" static LIST_HEAD(cdev_list); static spinlock_t cdev_lock = SPIN_LOCK_UNLOCKED; static u32 cdev_ids; struct list_head *crypto_device_list = &cdev_list; spinlock_t *crypto_device_lock = &cdev_lock; static int crypto_match(struct device *dev, struct device_driver *drv) { return 1; } static int crypto_probe(struct device *dev) { return -ENODEV; } static int crypto_remove(struct device *dev) { return 0; } static void crypto_release(struct device *dev) { struct crypto_device *d = container_of(dev, struct crypto_device, device); complete(&d->dev_released); } static void crypto_class_release(struct class *class) { } static void crypto_class_release_device(struct class_device *class_dev) { } struct class crypto_class = { .name = "acrypto", .class_release = crypto_class_release, .release = crypto_class_release_device }; struct bus_type crypto_bus_type = { .name = "acrypto", .match = crypto_match }; struct device_driver crypto_driver = { .name = "crypto_driver", .bus = &crypto_bus_type, .probe = crypto_probe, .remove = crypto_remove, }; struct device crypto_dev = { .parent = NULL, .bus = &crypto_bus_type, .bus_id = "Asynchronous crypto", .driver = &crypto_driver, .release = &crypto_release }; static ssize_t sessions_show(struct class_device *dev, char *buf) { struct crypto_device *d = container_of(dev, struct crypto_device, class_device); return sprintf(buf, "%d\n", atomic_read(&d->refcnt)); } static ssize_t name_show(struct class_device *dev, char *buf) { struct crypto_device *d = container_of(dev, struct crypto_device, class_device); return sprintf(buf, "%s\n", d->name); } static ssize_t devices_show(struct class_device *dev, char *buf) { struct crypto_device *d; int off = 0; spin_lock_bh(&cdev_lock); list_for_each_entry(d, &cdev_list, cdev_entry) { off += sprintf(buf+off, "%s ", d->name); } spin_unlock_bh(&cdev_lock); if (!off) off = sprintf(buf, "No devices registered yet."); off += sprintf(buf+off, "\n"); return off; } static ssize_t kmem_failed_show(struct class_device *dev, char *buf) { struct crypto_device *d = container_of(dev, struct crypto_device, class_device); return sprintf(buf, "%llu\n", d->stat.kmem_failed); } static ssize_t sstarted_show(struct class_device *dev, char *buf) { struct crypto_device *d = container_of(dev, struct crypto_device, class_device); return sprintf(buf, "%llu\n", d->stat.sstarted); } static ssize_t sfinished_show(struct class_device *dev, char *buf) { struct crypto_device *d = container_of(dev, struct crypto_device, class_device); return sprintf(buf, "%llu\n", d->stat.sfinished); } static ssize_t scompleted_show(struct class_device *dev, char *buf) { struct crypto_device *d = container_of(dev, struct crypto_device, class_device); return sprintf(buf, "%llu\n", d->stat.scompleted); } static CLASS_DEVICE_ATTR(sessions, 0444, sessions_show, NULL); static CLASS_DEVICE_ATTR(name, 0444, name_show, NULL); CLASS_DEVICE_ATTR(devices, 0444, devices_show, NULL); static CLASS_DEVICE_ATTR(scompleted, 0444, scompleted_show, NULL); static CLASS_DEVICE_ATTR(sstarted, 0444, sstarted_show, NULL); static CLASS_DEVICE_ATTR(sfinished, 0444, sfinished_show, NULL); static CLASS_DEVICE_ATTR(kmem_failed, 0444, kmem_failed_show, NULL); static inline int compare_device(struct crypto_device *d1, struct crypto_device *d2) { if (!strncmp(d1->name, d2->name, sizeof(d1->name))) return 1; return 0; } static void create_device_attributes(struct crypto_device *dev) { class_device_create_file(&dev->class_device, &class_device_attr_sessions); class_device_create_file(&dev->class_device, &class_device_attr_name); class_device_create_file(&dev->class_device, &class_device_attr_scompleted); class_device_create_file(&dev->class_device, &class_device_attr_sstarted); class_device_create_file(&dev->class_device, &class_device_attr_sfinished); class_device_create_file(&dev->class_device, &class_device_attr_kmem_failed); } static void remove_device_attributes(struct crypto_device *dev) { class_device_remove_file(&dev->class_device, &class_device_attr_sessions); class_device_remove_file(&dev->class_device, &class_device_attr_name); class_device_remove_file(&dev->class_device, &class_device_attr_scompleted); class_device_remove_file(&dev->class_device, &class_device_attr_sstarted); class_device_remove_file(&dev->class_device, &class_device_attr_sfinished); class_device_remove_file(&dev->class_device, &class_device_attr_kmem_failed); } static int __match_initializer(struct crypto_capability *cap, struct crypto_session_initializer *ci) { if ( cap->operation == ci->operation && cap->type == ci->type && cap->mode == (ci->mode & 0x1fff)) return 1; return 0; } int match_initializer(struct crypto_device *dev, struct crypto_session_initializer *ci) { int i; for (i=0; icap_number; ++i) { struct crypto_capability *cap = &dev->cap[i]; if (__match_initializer(cap, ci)) { if (cap->qlen >= atomic_read(&dev->refcnt) + 1) return 1; } } return 0; } inline void crypto_device_get(struct crypto_device *dev) { atomic_inc(&dev->refcnt); } inline struct crypto_device *crypto_device_get_name(char *name) { struct crypto_device *dev; int found = 0; spin_lock_bh(&cdev_lock); list_for_each_entry(dev, &cdev_list, cdev_entry) { if (!strcmp(dev->name, name)) { found = 1; crypto_device_get(dev); break; } } spin_unlock_bh(&cdev_lock); if (!found) return NULL; return dev; } inline void crypto_device_put(struct crypto_device *dev) { atomic_dec(&dev->refcnt); } int __crypto_device_add(struct crypto_device *dev) { int err; memset(&dev->stat, 0, sizeof(dev->stat)); spin_lock_init(&dev->stat_lock); spin_lock_init(&dev->lock); spin_lock_init(&dev->session_lock); INIT_LIST_HEAD(&dev->session_list); atomic_set(&dev->refcnt, 0); dev->sid = 0; dev->flags = 0; init_completion(&dev->dev_released); memcpy(&dev->device, &crypto_dev, sizeof(struct device)); dev->driver = &crypto_driver; snprintf(dev->device.bus_id, sizeof(dev->device.bus_id), "%s", dev->name); err = device_register(&dev->device); if (err) { dprintk(KERN_ERR "Failed to register crypto device %s: err=%d.\n", dev->name, err); return err; } snprintf(dev->class_device.class_id, sizeof(dev->class_device.class_id), "%s", dev->name); dev->class_device.dev = &dev->device; dev->class_device.class = &crypto_class; err = class_device_register(&dev->class_device); if (err) { dprintk(KERN_ERR "Failed to register crypto class device %s: err=%d.\n", dev->name, err); device_unregister(&dev->device); return err; } create_device_attributes(dev); return 0; } void __crypto_device_remove(struct crypto_device *dev) { remove_device_attributes(dev); class_device_unregister(&dev->class_device); device_unregister(&dev->device); } int crypto_device_add(struct crypto_device *dev) { int err; err = __crypto_device_add(dev); if (err) return err; spin_lock_bh(&cdev_lock); list_add(&dev->cdev_entry, &cdev_list); dev->id = ++cdev_ids; spin_unlock_bh(&cdev_lock); dprintk(KERN_INFO "Crypto device %s was registered with ID=%x.\n", dev->name, dev->id); return 0; } void crypto_device_remove(struct crypto_device *dev) { struct crypto_device *__dev, *n; unsigned long flags; __crypto_device_remove(dev); spin_lock_irqsave(&cdev_lock, flags); list_for_each_entry_safe(__dev, n, &cdev_list, cdev_entry) { if (compare_device(__dev, dev)) { list_del(&__dev->cdev_entry); spin_unlock_irqrestore(&cdev_lock, flags); /* * In test cases or when crypto device driver is not written correctly, * it's ->data_ready() method will not be callen anymore * after device is removed from crypto device list. * * For such cases we either should provide ->flush() call * or properly write ->data_ready() method. */ while (atomic_read(&__dev->refcnt)) { dprintk(KERN_INFO "Waiting for %s to become free: refcnt=%d.\n", __dev->name, atomic_read(&dev->refcnt)); /* * Hack zone: you need to write good ->data_ready() * and crypto device driver itself. * * Driver shoud not buzz if it has pending sessions * in it's queue and it was removed from global device list. * * Although I can workaround it here, for example by * flushing the whole queue and drop all pending sessions. */ __dev->data_ready(__dev); set_current_state(TASK_UNINTERRUPTIBLE); schedule_timeout(HZ); } dprintk(KERN_ERR "Crypto device %s was unregistered.\n", dev->name); return; } } spin_unlock_irqrestore(&cdev_lock, flags); dprintk(KERN_ERR "Crypto device %s was not registered.\n", dev->name); } EXPORT_SYMBOL(crypto_device_add); EXPORT_SYMBOL(crypto_device_remove); EXPORT_SYMBOL(crypto_device_get); EXPORT_SYMBOL(crypto_device_get_name); EXPORT_SYMBOL(crypto_device_put); EXPORT_SYMBOL(match_initializer);