/* firmware filesystem * * Copyright (C) 2003 Manuel Estrada Sainz . * */ /* The main idea is making it easy both to users and driver developers to * provide binary firmware from userspace to the kernel drivers. * Although any other kind of binary data could be handled */ #include #include #include #include #include #include #include #include #include #include #include "fwfs.h" static struct vfsmount *fwfs_mnt; static spinlock_t fwfs_lock = SPIN_LOCK_UNLOCKED; static struct fwfs_entry *fwfs_grab_image(struct inode *inode, struct dentry *dentry); /* Refcounting fwfs_entrys is probably not worth it. * In the general case, there will be just one client per entry, so allocating * on fwfs_get and freeing on fwfs_put should be good enough. * But since I wrote it, here it is for consideration. */ static void put_entry(struct fwfs_entry *entry) { spin_lock(&fwfs_lock); if (entry && atomic_dec_and_test(&entry->use_count)){ if(entry->dentry->d_inode->u.generic_ip == entry) entry->dentry->d_inode->u.generic_ip = NULL; kfree(entry->data); kfree(entry); } spin_unlock(&fwfs_lock); } static struct fwfs_entry *get_entry(struct inode *inode) { struct fwfs_entry *entry; spin_lock(&fwfs_lock); entry = inode->u.generic_ip; if(entry){ atomic_inc(&entry->use_count); } spin_unlock(&fwfs_lock); return entry; } static struct fwfs_entry *alloc_entry(size_t size) { struct fwfs_entry *entry = kmalloc(sizeof(struct fwfs_entry), GFP_KERNEL); if(entry){ entry->dentry = NULL; atomic_set(&entry->use_count, 1); entry->size = size; if(size){ entry->data = kmalloc(size, GFP_KERNEL); if(!entry->data){ kfree(entry); entry = NULL; } } else { entry->data = NULL; } } return entry; } static struct fwfs_entry *entry_lookup(const char *str_name) { struct dentry *dentry; struct dentry *parent = dget(fwfs_mnt->mnt_root); struct qstr name= {str_name, strlen(str_name), 0}; struct fwfs_entry *entry; name.hash = full_name_hash(name.name, name.len); dentry = d_lookup(parent, &name); dput(parent); if(!dentry) return NULL; if (!dentry->d_inode){ dput(dentry); return NULL; } entry = fwfs_grab_image(dentry->d_inode, dentry); if (!entry) dput(dentry); return entry; } const struct fwfs_entry *fwfs_get(const char *name) { struct fwfs_entry *entry; entry = entry_lookup(name); if (entry && !entry->data){ dput(entry->dentry); entry = NULL; } return entry; } void fwfs_put(const struct fwfs_entry *entry) { if(entry){ struct dentry *dentry = entry->dentry; put_entry((struct fwfs_entry *)entry); dput(dentry); } } static struct fwfs_entry *fwfs_grab_image(struct inode *inode, struct dentry *dentry) { struct fwfs_entry *fwfs_entry_old = get_entry(inode); struct fwfs_entry *fwfs_entry; struct address_space *mapping = inode->i_mapping; unsigned long index, end_index; down(&inode->i_sem); if(fwfs_entry_old && inode->i_ctime == fwfs_entry_old->ctime){ printk("fwfs_entry is up to date\n"); up(&inode->i_sem); return fwfs_entry_old; } #if 0 /* There is a race here, if userspace is writing the file while we read it, we can get just a piece of it. Locking i_sem is not enough. The following looks neat to me, but I don't have a file pointer to do it. I would appreciate any sugestions. */ while (deny_write_access(struct file)){ set_current_state(TASK_UNINTERRUPTIBLE); schedule_timeout(HZ); } /* get the data */ allow_write_access(struct file); #else /* This at least reduces the chances of the problem */ while (atomic_read(&inode->i_writecount) > 0) { printk(KERN_WARNING "fwfs: inode busy\n"); set_current_state(TASK_UNINTERRUPTIBLE); schedule_timeout(HZ); } #endif fwfs_entry = alloc_entry(inode->i_size); if(!fwfs_entry){ up(&inode->i_sem); return fwfs_entry_old; } fwfs_entry->dentry = dentry; fwfs_entry->ctime = inode->i_ctime; end_index = inode->i_size >> PAGE_CACHE_SHIFT; for (index=0; index <= end_index; index++) { struct page *page, **hash; unsigned long bytes; u8 *buf = fwfs_entry->data + index * PAGE_CACHE_SIZE; bytes = PAGE_CACHE_SIZE; if (index == end_index) { bytes = inode->i_size & ~PAGE_CACHE_MASK; if(!bytes) break; } /* in ramfs, the data is always in the page cache , right? */ hash = page_hash(mapping, index); page = __find_get_page(mapping, index, hash); BUG_ON(!Page_Uptodate(page)); memcpy(buf, kmap(page), bytes); kunmap(page); page_cache_release(page); } UPDATE_ATIME(inode); put_entry(fwfs_entry_old); spin_lock(&fwfs_lock); inode->u.generic_ip=fwfs_entry; spin_unlock(&fwfs_lock); up(&inode->i_sem); return fwfs_entry; } int fwfs_write_image(struct inode *inode, const u8 *buf, size_t size) { struct address_space *mapping = inode->i_mapping; loff_t pos = 0; struct page *page, *cached_page=NULL; long retval = 0; unsigned bytes; down(&inode->i_sem); inode->i_ctime = inode->i_mtime = CURRENT_TIME; mark_inode_dirty_sync(inode); do { unsigned long index; char *kaddr; index = pos >> PAGE_CACHE_SHIFT; bytes = PAGE_CACHE_SIZE; if (bytes > size) bytes = size; page = grab_cache_page(mapping, index); if (!page){ retval = -ENOMEM; break; } BUG_ON (!PageLocked(page)); kaddr = kmap(page); /* prepare_write and commit_write never fail in ramfs, and * they don't use the 'file' argument */ mapping->a_ops->prepare_write(NULL, page, 0, bytes); memcpy(kaddr, buf, bytes); flush_dcache_page(page); mapping->a_ops->commit_write(NULL, page, 0, bytes); size -= bytes; pos += bytes; buf += bytes; kunmap(page); SetPageReferenced(page); UnlockPage(page); page_cache_release(page); } while (size); if (cached_page) page_cache_release(cached_page); up(&inode->i_sem); return retval; } void fwfs_write_default(const char *name, const u8 *data, size_t size) { struct dentry * dentry; struct dentry * parent = fwfs_mnt->mnt_root; int error; struct qstr d_name = { name, strlen(name) }; d_name.hash = full_name_hash(d_name.name, d_name.len); down(&parent->d_inode->i_sem); dentry = lookup_hash(&d_name, parent); if(dentry->d_inode){ /* We already have an image */ printk("we already have %s\n", name); goto up_parent; } error = vfs_create(parent->d_inode, dentry, 0644); if(error){ printk("%d\n", error); goto up_parent; } if((error = fwfs_write_image(dentry->d_inode, data, size))) printk("fwfs_write_image: status %d\n", error); up_parent: up(&parent->d_inode->i_sem); dput(dentry); return; } EXPORT_SYMBOL(fwfs_get); EXPORT_SYMBOL(fwfs_put); EXPORT_SYMBOL(fwfs_write_default); static DECLARE_FSTYPE(fwfs_fs_type, "fwfs", NULL, FS_LITTER|FS_SINGLE); static int can_unload (void) { /* kern_mount makes the module busy for ever, so I have to keep track * of busyness myself */ int usecount = atomic_read(&THIS_MODULE->uc.usecount); int s_active = atomic_read(&fwfs_mnt->mnt_sb->s_active); if (usecount == 1 && s_active == 1) /* both will go on kern_umount */ return 0; return usecount; } static int __init init_fwfs_fs(void) { int err; struct file_system_type *ramfs_fs_type = get_fs_type("ramfs"); if (!ramfs_fs_type) return -ENOENT; fwfs_fs_type.read_super = ramfs_fs_type->read_super; err = register_filesystem(&fwfs_fs_type); if (err) return err; fwfs_mnt = kern_mount(&fwfs_fs_type); if (IS_ERR(fwfs_mnt)){ unregister_filesystem(&fwfs_fs_type); return PTR_ERR(fwfs_mnt); } THIS_MODULE->can_unload = can_unload; return 0; } static void __exit exit_fwfs_fs(void) { kern_umount(fwfs_mnt); BUG_ON(MOD_IN_USE); unregister_filesystem(&fwfs_fs_type); } module_init(init_fwfs_fs) module_exit(exit_fwfs_fs) MODULE_LICENSE("GPL");