#include #include #include #include "firmware.h" MODULE_AUTHOR("Manuel Estrada Sainz "); MODULE_DESCRIPTION("Hotplug Firmware Loading Support"); MODULE_LICENSE("GPL"); #define MAX( a, b ) ( ( ( a ) > ( b ) ) ? ( a ) : ( b ) ) static inline struct class_device *to_class_dev(struct kobject *obj) { return container_of(obj,struct class_device,kobj); } static inline struct class_device_attribute *to_class_dev_attr(struct attribute *_attr) { return container_of(_attr,struct class_device_attribute,attr); } int sysfs_create_bin_file(struct kobject * kobj, struct bin_attribute * attr); int sysfs_remove_bin_file(struct kobject * kobj, struct bin_attribute * attr); struct firmware_priv { char fw_id[FIRMWARE_NAME_MAX]; struct completion completion; struct bin_attribute attr_data; struct firmware *fw; u8 loading; int alloc_size; }; int firmware_class_hotplug(struct class_device *dev, char **envp, int num_envp, char *buffer, int buffer_size); struct class firmware_class = { .name = "firmware", .hotplug = firmware_class_hotplug, }; int firmware_class_hotplug(struct class_device *class_dev, char **envp, int num_envp, char *buffer, int buffer_size) { struct firmware_priv *fw_priv = class_get_devdata(class_dev); int i=0; char *scratch=buffer; if (buffer_size < (FIRMWARE_NAME_MAX+10)) return -ENOMEM; envp [i++] = scratch; scratch += sprintf(scratch, "FIRMWARE=%s", fw_priv->fw_id) + 1; return 0; } ssize_t firmware_loading_show(struct class_device *class_dev, char *buf) { struct firmware_priv *fw_priv = class_get_devdata(class_dev); return sprintf(buf, "%d\n", fw_priv->loading); } static ssize_t firmware_loading_store(struct class_device *class_dev, const char *buf, size_t count) { int loading = simple_strtol(buf, NULL, 10); struct firmware_priv *fw_priv = class_get_devdata(class_dev); if(fw_priv->loading && !loading) complete(&fw_priv->completion); fw_priv->loading = loading; return count; } CLASS_DEVICE_ATTR(loading, 0644, firmware_loading_show, firmware_loading_store); static ssize_t firmware_data_read(struct kobject *kobj, struct sysfs_bin_buffer *buffer) { struct class_device *class_dev = to_class_dev(kobj); struct firmware_priv *fw_priv = class_get_devdata(class_dev); struct firmware *fw = fw_priv->fw; printk("%s: size:%d count:%d offset:%lld\n", __FUNCTION__, buffer->size, buffer->count, buffer->offset); buffer->data = kmalloc(fw->size, GFP_KERNEL); if(!buffer->data) return -ENOMEM; buffer->size = fw->size; memcpy(buffer->data, fw->data, fw->size); return buffer->count; } static int fw_realloc_buffer(struct firmware_priv *fw_priv, int min_size) { u8 *new_data; if (min_size <= fw_priv->alloc_size) return 0; new_data = kmalloc(fw_priv->alloc_size+PAGE_SIZE, GFP_KERNEL); if(!new_data){ printk(KERN_ERR "%s: unable to alloc buffer\n", __FUNCTION__); /* Make sure that we don't keep incomplete data */ kfree(fw_priv->fw->data); fw_priv->fw->data=NULL; fw_priv->fw->size=0; return -ENOMEM; } fw_priv->alloc_size += PAGE_SIZE; if(fw_priv->fw->data){ memcpy(new_data, fw_priv->fw->data, fw_priv->fw->size); kfree(fw_priv->fw->data); } fw_priv->fw->data=new_data; BUG_ON(min_size > fw_priv->alloc_size); return 0; } static ssize_t firmware_data_write(struct kobject *kobj, struct sysfs_bin_buffer *buffer) { struct class_device *class_dev = to_class_dev(kobj); struct firmware_priv *fw_priv = class_get_devdata(class_dev); struct firmware *fw = fw_priv->fw; int retval; printk("%s: size:%d count:%d offset:%lld\n", __FUNCTION__, buffer->size, buffer->count, buffer->offset); retval = fw_realloc_buffer(fw_priv, buffer->offset+buffer->count); if(retval) return retval; memcpy(fw->data+buffer->offset, buffer->data, buffer->count); buffer->offset += buffer->count; buffer->size = MAX(buffer->offset, buffer->size); fw->size=buffer->size; return buffer->count; } static struct bin_attribute firmware_attr_data_tmpl = { .attr = {.name = "data", .mode = 0644}, .size = 0, .read = firmware_data_read, .write = firmware_data_write, }; static int fw_setup_class_device(struct class_device *class_dev, struct firmware_priv *fw_priv, const char *fw_name, const char *dev_name) { int retval = 0; memset(fw_priv, 0, sizeof(*fw_priv)); init_completion(&fw_priv->completion); memcpy(&fw_priv->attr_data, &firmware_attr_data_tmpl, sizeof(firmware_attr_data_tmpl)); strncpy(fw_priv->fw_id, fw_name, FIRMWARE_NAME_MAX); fw_priv->fw_id[FIRMWARE_NAME_MAX-1] = '\0'; strncpy(class_dev->class_id, dev_name, BUS_ID_SIZE); class_dev->class_id[BUS_ID_SIZE-1] = '\0'; class_set_devdata(class_dev, fw_priv); retval = class_device_register(class_dev); if (retval){ printk(KERN_ERR "%s: class_device_register failed\n", __FUNCTION__); goto out; } retval = sysfs_create_bin_file(&class_dev->kobj, &fw_priv->attr_data); if (retval){ printk(KERN_ERR "%s: sysfs_create_bin_file failed\n", __FUNCTION__); goto error_unreg_class_dev; } retval = class_device_create_file(class_dev, &class_device_attr_loading); if (retval){ printk(KERN_ERR "%s: class_device_create_file failed\n", __FUNCTION__); goto error_remove_data; } fw_priv->fw=kmalloc(sizeof(struct firmware), GFP_KERNEL); if(!fw_priv->fw){ printk(KERN_ERR "%s: kmalloc(struct firmware) failed\n", __FUNCTION__); retval = -ENOMEM; goto error_remove_loading; } memset(fw_priv->fw, 0, sizeof(*fw_priv->fw)); goto out; error_remove_loading: class_device_remove_file(class_dev, &class_device_attr_loading); error_remove_data: sysfs_remove_bin_file(&class_dev->kobj, &fw_priv->attr_data); error_unreg_class_dev: class_device_unregister(class_dev); out: return retval; } static void fw_remove_class_device(struct class_device *class_dev) { struct firmware_priv *fw_priv = class_get_devdata(class_dev); class_device_remove_file(class_dev, &class_device_attr_loading); sysfs_remove_bin_file(&class_dev->kobj, &fw_priv->attr_data); class_device_unregister(class_dev); } int request_firmware (const struct firmware **firmware, const char *name, const char *device) /* Maybe 'device' should be 'struct device *' */ { static struct class_device class_dev = { .class = &firmware_class, }; struct firmware_priv fw_priv; int retval; if(!firmware){ retval = -EINVAL; goto out; } *firmware=NULL; retval = fw_setup_class_device(&class_dev, &fw_priv, name, device); if(retval) goto out; wait_for_completion(&fw_priv.completion); fw_remove_class_device(&class_dev); if(fw_priv.fw->size) *firmware = fw_priv.fw; else { retval = -ENOENT; kfree(fw_priv.fw->data); kfree(fw_priv.fw); } out: return retval; } void release_firmware (const struct firmware *fw) { kfree(fw->data); kfree(fw); } void register_firmware (const char *name, const u8 *data, size_t size) { /* This is meaningless without firmware caching, so until we * decide if firmware caching is reasonable just leave it as a * noop */ } static int __init firmware_class_init(void) { int error; error = class_register(&firmware_class); if (error) { printk(KERN_ERR "class_register failed\n"); } return error; } static void __exit firmware_class_exit(void) { class_unregister(&firmware_class); } module_init(firmware_class_init); module_exit(firmware_class_exit); EXPORT_SYMBOL(release_firmware); EXPORT_SYMBOL(request_firmware); EXPORT_SYMBOL(register_firmware); EXPORT_SYMBOL(firmware_class);