/* * dspdl core driver * * Copyright (C) 2008 Lyrtech * * Based on code found in book "Linux Device Drivers" by * Alessandro Rubini and Jonathan Corbet, published by O'Reilly & Associates. * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MODULE_NAME "dspdl" /* Define this to enable verbose debug messages */ #define DSPDL_DEBUG 1 static const char dspdl_driver_version[] = "v1.0"; /* Module parameters */ static unsigned int dspdl_debug; module_param_named(debug, dspdl_debug, int, 0644); #ifdef DSPDL_DEBUG #define INFOMSG(fmt, args...) \ do { \ printk(KERN_INFO "%s: "fmt"\n", MODULE_NAME, ## args); \ } while (0) #define DBGMSG(fmt, args...) \ do { \ if (dspdl_debug > 0) \ printk(KERN_DEBUG "%s: "fmt"\n", \ MODULE_NAME, ## args); \ } while (0) #define DBGMSG_ENTER() \ DBGMSG("%s() enter", __func__); #define DBGMSG_LEAVE() \ DBGMSG("%s() leave", __func__); #else #define INFOMSG(fmt, args...) do {} while (0) #define DBGMSG(fmt, args...) do {} while (0) #define DBGMSG_ENTER() do {} while (0) #define DBGMSG_LEAVE() do {} while (0) #endif #define FAILMSG(fmt, args...) \ do { \ printk(KERN_ERR "%s: "fmt"\n", MODULE_NAME, ## args); \ } while (0) #define DSPDL_FW_MAX_SIZE 0x800000 #define MAX_DSPDL_DEV 4 static unsigned fw_max_size = DSPDL_FW_MAX_SIZE; static int dspdl_dev_count; static struct dspdl_device *dspdl_dev_array[MAX_DSPDL_DEV]; /* Respond to hotplug events. */ static int dspdl_uevent(struct device *dev, struct kobj_uevent_env *env) { DBGMSG_ENTER(); if (add_uevent_var(env, "DSPDL_BUS_VERSION=%s", dspdl_driver_version)) return -ENOMEM; return 0; }; static int dspdl_fw_load(struct dspdl_device *dspdl_dev, const u8 *data, size_t size) { int i; struct file_hdr_t *file_hdr; struct opt_hdr_t *opt_hdr; struct section_hdr_t *section_hdr; int ret; if (size == 0) { return 0; } dspdl_dev->fw_loaded = 0; DBGMSG("DSP %d: Starting programming", dspdl_dev->id); if (dspdl_dev->cb_start) { ret = dspdl_dev->cb_start(dspdl_dev); if (ret < 0) return ret; } /* Extract header infos. */ file_hdr = (struct file_hdr_t *) data; if (file_hdr->magic != 0xC2) { FAILMSG("Invalid magic number in header (0x%02X)", file_hdr->magic); return -EFAULT; } if (file_hdr->target_id != 0x0099) { FAILMSG("Invalid target ID in header (0x%04X)", file_hdr->target_id); return -EFAULT; } /* Make sure file is a valid COFF. */ if (!(file_hdr->flags & F_EXEC)) { FAILMSG("Not an executable"); return -ENOEXEC; } /* The DSPBOOTADDR contains the upper 22 bits of the DSP reset vector. * This driver creates a reset vector that is always located at the * very beginning of the DDR2 memory region occupied by the DSP. * Therefore, the DSP application must not use the first 12 bytes of * DDR2 memory. */ opt_hdr = (struct opt_hdr_t *) (data + sizeof(struct file_hdr_t)); DBGMSG(" Entry point: $%08X", opt_hdr->entrypt); /* Points to first Section */ section_hdr = (struct section_hdr_t *) ((void *) opt_hdr + file_hdr->opthdr); /* Scan each section and copies their data into memory if their flag is * set to STYP_COPY */ for (i = 0; i < file_hdr->nscns; i++) { if ((section_hdr->s_size != 0) && (section_hdr->s_scnptr != 0) && !(section_hdr->s_flags & STYP_COPY)) { DBGMSG(" Section %02d, name = \"%s\"", i, section_hdr->s_name); DBGMSG(" addr = $%08X, size = $%08X", section_hdr->s_paddr, section_hdr->s_size); ret = dspdl_dev->cb_write_section( dspdl_dev, section_hdr->s_paddr, (u8 *) (data + section_hdr->s_scnptr), section_hdr->s_size); if (ret < 0) return ret; } section_hdr++; } if (dspdl_dev->cb_finish) { ret = dspdl_dev->cb_finish(dspdl_dev, opt_hdr->entrypt); if (ret < 0) return ret; } INFOMSG("DSP %d: Firmware loaded", dspdl_dev->id); dspdl_dev->fw_loaded = 1; return 0; } /* Open method. */ static int dspdl_open(struct inode *inode, struct file *filp) { int k; int found = 0; struct dspdl_device *dspdl_dev; DBGMSG_ENTER(); DBGMSG(" Opening device minor %d", MINOR(inode->i_rdev)); for (k = 0; k < dspdl_dev_count; k++) { dspdl_dev = dspdl_dev_array[k]; if (dspdl_dev) { if (dspdl_dev->miscdev.minor == MINOR(inode->i_rdev)) { found = 1; break; } } } if (!found) { FAILMSG(" Invalid minor device"); return -ENOMEM; } filp->private_data = dspdl_dev; dspdl_dev->fw_length = 0; #ifdef USE_KMALLOC // Use kmaloc fw_max_size = DSPDL_FW_MAX_SIZE; dspdl_dev->fw_data = kmalloc(fw_max_size, GFP_KERNEL); if (!dspdl_dev->fw_data) { fw_max_size -= 0x200000; dspdl_dev->fw_data = kmalloc(fw_max_size, GFP_KERNEL); if (!dspdl_dev->fw_data) { fw_max_size -= 0x200000; dspdl_dev->fw_data = kmalloc(fw_max_size, GFP_KERNEL); if (!dspdl_dev->fw_data) { FAILMSG("Failed to allocate memory for firmware"); return -ENOMEM; } } }a #else // Use vmalloc fw_max_size = DSPDL_FW_MAX_SIZE; dspdl_dev->fw_data = vmalloc(fw_max_size); if (!dspdl_dev->fw_data) { FAILMSG("Failed to allocate memory for firmware"); return -ENOMEM; } #endif dspdl_dev->fw_buffer_allocated = 1; return 0; } /* Write method. Fill buffer with bitstream data. */ static ssize_t dspdl_write(struct file *filp, const char __user *buff, size_t count, loff_t *offp) { struct dspdl_device *dspdl_dev = filp->private_data; if ((dspdl_dev->fw_length + count) >= fw_max_size) { FAILMSG("Bitstream buffer size exceeded"); return -EFBIG; } if (copy_from_user(dspdl_dev->fw_data + dspdl_dev->fw_length, (void __user *) buff, count)) return -EFAULT; dspdl_dev->fw_length += count; return count; } /* Release method. This will initiate the DSP programming. */ static int dspdl_release(struct inode *inode, struct file *filp) { int retval; struct dspdl_device *dspdl_dev = filp->private_data; if (!dspdl_dev->fw_data) return -EFAULT; retval = dspdl_fw_load(dspdl_dev, dspdl_dev->fw_data, dspdl_dev->fw_length); #ifdef USE_KMALLOC // Use kmaloc kfree(dspdl_dev->fw_data); #else // Use vmalloc vfree(dspdl_dev->fw_data); #endif dspdl_dev->fw_buffer_allocated = 0; return retval; } static long dspdl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { int retval; void __user *argp = (void __user *)arg; struct dspdl_device *dspdl_dev = filp->private_data; switch ( cmd ) { // 32-bit read case 0: { u32 addr, data; if (copy_from_user(&addr, argp, sizeof(u32))) return -EFAULT; if (dspdl_dev->cb_read_mem) { retval = dspdl_dev->cb_read_mem(dspdl_dev, addr, &data); if (retval < 0) return retval; if (copy_to_user(argp, &data, sizeof(u32))) return -EFAULT; break; } return -EFAULT; } // 32-bit write case 1: { u32 addr, data; if (copy_from_user(&addr, argp, sizeof(u32))) return -EFAULT; if (copy_from_user(&data, argp + sizeof(u32), sizeof(u32))) return -EFAULT; if (dspdl_dev->cb_write_mem) { retval = dspdl_dev->cb_write_mem(dspdl_dev, addr, data); if (retval < 0) return retval; break; } return -EFAULT; } default: { return -EINVAL; } } return 0; } static struct file_operations fops_dspdl = { .owner = THIS_MODULE, .open = dspdl_open, .write = dspdl_write, .unlocked_ioctl = dspdl_ioctl, .release = dspdl_release, }; /* Match dspdl devices to drivers. Just do a simple name test. */ static int dspdl_device_match(struct device *dev, struct device_driver *drv) { DBGMSG_ENTER(); return !strncmp(dev_name(dev), drv->name, strlen(drv->name)); } static ssize_t show_version(struct device_driver *driver, char *buf) { struct dspdl_driver *dspdldriver = to_dspdl_driver(driver); sprintf(buf, "%s\n", dspdldriver->version); return strlen(buf); } int dspdl_register_driver(struct dspdl_driver *drv) { int res; DBGMSG_ENTER(); /* Initialize common driver fields */ drv->driver.bus = &dspdl_bus_type; /* Register with core */ res = driver_register(&drv->driver); if (res) FAILMSG(" driver_register() failed"); drv->version_attr.attr.name = "version"; drv->version_attr.attr.mode = S_IRUGO; drv->version_attr.show = show_version; drv->version_attr.store = NULL; res = driver_create_file(&drv->driver, &drv->version_attr); return res; } EXPORT_SYMBOL(dspdl_register_driver); void dspdl_unregister_driver(struct dspdl_driver *drv) { DBGMSG_ENTER(); driver_unregister(&drv->driver); } EXPORT_SYMBOL(dspdl_unregister_driver); /* The dspdl bus device. */ static void dspdl_bus_release(struct device *dev) { DBGMSG_ENTER(); } struct device dspdl_bus = { .init_name = "dspdl0", .release = dspdl_bus_release }; struct bus_type dspdl_bus_type = { .name = "dspdl", .match = dspdl_device_match, .uevent = dspdl_uevent, }; EXPORT_SYMBOL(dspdl_bus_type); /* Export a simple sysfs attribute. */ static ssize_t show_bus_version(struct bus_type *bus, char *buf) { return snprintf(buf, PAGE_SIZE, "%s\n", dspdl_driver_version); } static BUS_ATTR(version, S_IRUGO, show_bus_version, NULL); /* * dspdl devices. * For now, no references to dspdlbus devices go out which are not * tracked via the module reference count, so we use a no-op * release function. */ static void dspdl_dev_release(struct device *dev) { DBGMSG_ENTER(); } static void dspdl_cleanup(struct dspdl_device *dspdl_dev) { DBGMSG_ENTER(); if (!dspdl_dev) return; dspdl_dev_array[dspdl_dev->id] = NULL; /* Get rid of any allocated buffer, not freed */ if (dspdl_dev->fw_buffer_allocated) #ifdef USE_KMALLOC // Use kmaloc kfree(dspdl_dev->fw_data); #else // Use vmalloc vfree(dspdl_dev->fw_data); #endif switch (dspdl_dev->state) { case DSPDL_DEV_STATE_CHAR_DEV_REGISTERED: misc_deregister(&dspdl_dev->miscdev); case DSPDL_DEV_STATE_DEVICE_REGISTERED: device_unregister(&dspdl_dev->dev); case DSPDL_DEV_STATE_START: break; } } int dspdl_register_device(struct dspdl_device *dspdl_dev) { int res; const struct firmware *fw_entry; DBGMSG_ENTER(); dspdl_dev->state = DSPDL_DEV_STATE_START; /* Sanity checks. */ if (!dspdl_dev->name) { FAILMSG(" Error, missing device name"); res = -EFAULT; goto error; } if (!dspdl_dev->cb_write_section) { FAILMSG(" Error, missing cb_write_section() callback"); res = -ENOMEM; goto error; } if (dspdl_dev_count == MAX_DSPDL_DEV) { FAILMSG("Maximum number of devices reached (%d)", dspdl_dev_count); res = -ENODEV; goto error; } DBGMSG(" device %d", dspdl_dev_count); /* Set some default values. */ dspdl_dev->fw_loaded = 0; dspdl_dev->fw_buffer_allocated = 0; dspdl_dev->dev.bus = &dspdl_bus_type; dspdl_dev->dev.parent = &dspdl_bus; dspdl_dev->dev.release = dspdl_dev_release; dev_set_name(&dspdl_dev->dev, dspdl_dev->name); res = device_register(&dspdl_dev->dev); if (res) { FAILMSG(" device_register() failed"); goto error; } dspdl_dev->state = DSPDL_DEV_STATE_DEVICE_REGISTERED; dspdl_dev->miscdev.name = dspdl_dev->name; dspdl_dev->miscdev.minor = MISC_DYNAMIC_MINOR; dspdl_dev->miscdev.fops = &fops_dspdl; res = misc_register(&dspdl_dev->miscdev); if (res < 0) { FAILMSG("Error registering misc driver"); goto error; } DBGMSG(" MINOR = %d", dspdl_dev->miscdev.minor); dspdl_dev->state = DSPDL_DEV_STATE_CHAR_DEV_REGISTERED; /* Try to load firmware through hotplug if available. */ if (dspdl_dev->fw_name) { res = request_firmware(&fw_entry, dspdl_dev->fw_name, &dspdl_dev->dev); if (res < 0) { /* Not a critical error. */ res = 0; DBGMSG("firmware <%s> not available", dspdl_dev->fw_name); } else { res = dspdl_fw_load(dspdl_dev, fw_entry->data, fw_entry->size); release_firmware(fw_entry); } } dspdl_dev->id = dspdl_dev_count; dspdl_dev_array[dspdl_dev_count] = dspdl_dev; dspdl_dev_count++; return 0; error: dspdl_cleanup(dspdl_dev); return res; } EXPORT_SYMBOL(dspdl_register_device); void dspdl_unregister_device(struct dspdl_device *dspdl_dev) { DBGMSG_ENTER(); /* Warning: fix this, potential bug if removing a device in-between. */ dspdl_dev_array[dspdl_dev->id] = NULL; dspdl_dev_count--; dspdl_cleanup(dspdl_dev); } EXPORT_SYMBOL(dspdl_unregister_device); static int __init dspdl_init(void) { int res; DBGMSG_ENTER(); INFOMSG("DSP firmware loader %s", dspdl_driver_version); res = bus_register(&dspdl_bus_type); if (res) { FAILMSG(" bus_register() failed"); goto fail_bus; } if (bus_create_file(&dspdl_bus_type, &bus_attr_version)) { FAILMSG("Unable to create version attribute"); goto fail_create_file; } res = device_register(&dspdl_bus); if (res) { FAILMSG(" failed registering %s", dspdl_bus.init_name); goto fail_dev_reg; } return 0; fail_dev_reg: fail_create_file: bus_unregister(&dspdl_bus_type); fail_bus: return res; } module_init(dspdl_init); static void __exit dspdl_exit(void) { DBGMSG_ENTER(); device_unregister(&dspdl_bus); bus_unregister(&dspdl_bus_type); } module_exit(dspdl_exit); MODULE_AUTHOR("Hugo Villeneuve "); MODULE_DESCRIPTION("DSP loader driver"); MODULE_LICENSE("GPL");