static int ds_event(event_t event, int priority, event_callback_args_t *args) { struct pcmcia_bus_socket *s; ds_dbg(1, "ds_event(0x%06x, %d, 0x%p)\n", event, priority, args->client_handle); s = args->client_data; switch (event) { case CS_EVENT_CARD_REMOVAL: s->state &= ~DS_SOCKET_PRESENT; if (!(s->state & DS_SOCKET_REMOVAL_PENDING)) { s->state |= DS_SOCKET_REMOVAL_PENDING; schedule_delayed_work(&s->removal, HZ/10); } break; case CS_EVENT_CARD_INSERTION: s->state |= DS_SOCKET_PRESENT; handle_event(s, event); break; case CS_EVENT_EJECTION_REQUEST: return handle_request(s, event); break; default: handle_event(s, event); break; } return 0; } /* ds_event */
static void pcmcia_release_dev(struct device *dev) { struct pcmcia_device *p_dev = to_pcmcia_dev(dev); ds_dbg(1, "releasing dev %p\n", p_dev); pcmcia_put_bus_socket(p_dev->socket->pcmcia); kfree(p_dev); }
static ssize_t ds_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { struct pcmcia_bus_socket *s; user_info_t *user; int ret; ds_dbg(2, "ds_read(socket %d)\n", iminor(file->f_dentry->d_inode)); if (count < 4) return -EINVAL; user = file->private_data; if (CHECK_USER(user)) return -EIO; s = user->socket; if (s->state & DS_SOCKET_DEAD) return -EIO; ret = wait_event_interruptible(s->queue, !queue_empty(user)); if (ret == 0) ret = put_user(get_queued_event(user), (int __user *)buf) ? -EFAULT : 4; return ret; } /* ds_read */
static ssize_t ds_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { struct pcmcia_bus_socket *s; user_info_t *user; ds_dbg(2, "ds_write(socket %d)\n", iminor(file->f_dentry->d_inode)); if (count != 4) return -EINVAL; if ((file->f_flags & O_ACCMODE) == O_RDONLY) return -EBADF; user = file->private_data; if (CHECK_USER(user)) return -EIO; s = user->socket; if (s->state & DS_SOCKET_DEAD) return -EIO; if (s->req_pending) { s->req_pending--; get_user(s->req_result, (int __user *)buf); if ((s->req_result != 0) || (s->req_pending == 0)) wake_up_interruptible(&s->request); } else return -EIO; return 4; } /* ds_write */
static int ds_open(struct inode *inode, struct file *file) { socket_t i = iminor(inode); struct pcmcia_bus_socket *s; user_info_t *user; ds_dbg(0, "ds_open(socket %d)\n", i); s = pcmcia_get_bus_socket(i); if (!s) return -ENODEV; if ((file->f_flags & O_ACCMODE) != O_RDONLY) { if (s->state & DS_SOCKET_BUSY) return -EBUSY; else s->state |= DS_SOCKET_BUSY; } user = kmalloc(sizeof(user_info_t), GFP_KERNEL); if (!user) return -ENOMEM; user->event_tail = user->event_head = 0; user->next = s->user; user->user_magic = USER_MAGIC; user->socket = s; s->user = user; file->private_data = user; if (s->state & DS_SOCKET_PRESENT) queue_event(user, CS_EVENT_CARD_INSERTION); return 0; } /* ds_open */
static int ds_release(struct inode *inode, struct file *file) { struct pcmcia_bus_socket *s; user_info_t *user, **link; ds_dbg(0, "ds_release(socket %d)\n", iminor(inode)); user = file->private_data; if (CHECK_USER(user)) goto out; s = user->socket; /* Unlink user data structure */ if ((file->f_flags & O_ACCMODE) != O_RDONLY) { s->state &= ~DS_SOCKET_BUSY; s->req_pending = 0; wake_up_interruptible(&s->request); } file->private_data = NULL; for (link = &s->user; *link; link = &(*link)->next) if (*link == user) break; if (link == NULL) goto out; *link = user->next; user->user_magic = 0; kfree(user); pcmcia_put_bus_socket(s); out: return 0; } /* ds_release */
static int pcmcia_bind_mtd(mtd_bind_t *req) { struct pcmcia_socket *s; memory_handle_t region; s = req->Socket; if (!s) return CS_BAD_SOCKET; if (req->Attributes & REGION_TYPE_AM) region = s->a_region; else region = s->c_region; while (region) { if (region->info.CardOffset == req->CardOffset) break; region = region->info.next; } if (!region || (region->mtd != NULL)) return CS_BAD_OFFSET; strlcpy(region->dev_info, (char *)req->dev_info, DEV_NAME_LEN); ds_dbg(1, "%s: bind_mtd: attr 0x%x, offset 0x%x, dev %s\n", cs_socket_name(s), req->Attributes, req->CardOffset, (char *)req->dev_info); return CS_SUCCESS; } /* bind_mtd */
static int unbind_request(struct pcmcia_bus_socket *s, bind_info_t *bind_info) { socket_bind_t **b, *c; ds_dbg(2, "unbind_request(%d, '%s')\n", s->parent->sock, (char *)bind_info->dev_info); for (b = &s->bind; *b; b = &(*b)->next) if ((strcmp((char *)(*b)->driver->drv.name, (char *)bind_info->dev_info) == 0) && ((*b)->function == bind_info->function)) break; if (*b == NULL) return -ENODEV; c = *b; c->driver->use_count--; if (c->driver->detach) { if (c->instance) c->driver->detach(c->instance); } module_put(c->driver->owner); *b = c->next; kfree(c); return 0; } /* unbind_request */
static int pcmcia_bind_device(bind_req_t *req) { client_t *client; struct pcmcia_socket *s; s = req->Socket; if (!s) return CS_BAD_SOCKET; client = (client_t *) kmalloc(sizeof(client_t), GFP_KERNEL); if (!client) return CS_OUT_OF_RESOURCE; memset(client, '\0', sizeof(client_t)); client->client_magic = CLIENT_MAGIC; strlcpy(client->dev_info, (char *)req->dev_info, DEV_NAME_LEN); client->Socket = s; client->Function = req->Function; client->state = CLIENT_UNBOUND; client->erase_busy.next = &client->erase_busy; client->erase_busy.prev = &client->erase_busy; init_waitqueue_head(&client->mtd_req); client->next = s->clients; s->clients = client; ds_dbg(1, "%s: bind_device(): client 0x%p, dev %s\n", cs_socket_name(client->Socket), client, client->dev_info); return CS_SUCCESS; } /* bind_device */
static int pcmcia_card_add(struct pcmcia_socket *s) { cisinfo_t cisinfo; cistpl_longlink_mfc_t mfc; unsigned int no_funcs, i; int ret = 0; if (!(s->resource_setup_done)) return -EAGAIN; /* try again, but later... */ if (pcmcia_validate_mem(s)) return -EAGAIN; /* try again, but later... */ ret = pccard_validate_cis(s, BIND_FN_ALL, &cisinfo); if (ret || !cisinfo.Chains) { ds_dbg(0, "invalid CIS or invalid resources\n"); return -ENODEV; } if (!pccard_read_tuple(s, BIND_FN_ALL, CISTPL_LONGLINK_MFC, &mfc)) no_funcs = mfc.nfn; else no_funcs = 1; for (i=0; i < no_funcs; i++) pcmcia_device_add(s, i); return (ret); }
/* * Removes a PCMCIA card from the device tree and socket list. */ static void pcmcia_card_remove(struct pcmcia_socket *s) { struct pcmcia_device *p_dev; unsigned long flags; ds_dbg(2, "unbind_request(%d)\n", s->sock); s->device_count = 0; for (;;) { /* unregister all pcmcia_devices registered with this socket*/ spin_lock_irqsave(&pcmcia_dev_list_lock, flags); if (list_empty(&s->devices_list)) { spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags); return; } p_dev = list_entry((&s->devices_list)->next, struct pcmcia_device, socket_device_list); list_del(&p_dev->socket_device_list); p_dev->state |= CLIENT_STALE; spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags); device_unregister(&p_dev->dev); } return; } /* unbind_request */
/* * pcmcia_device_query -- determine information about a pcmcia device */ static int pcmcia_device_query(struct pcmcia_device *p_dev) { cistpl_manfid_t manf_id; cistpl_funcid_t func_id; cistpl_vers_1_t vers1; unsigned int i; if (!pccard_read_tuple(p_dev->socket, p_dev->func, CISTPL_MANFID, &manf_id)) { p_dev->manf_id = manf_id.manf; p_dev->card_id = manf_id.card; p_dev->has_manf_id = 1; p_dev->has_card_id = 1; } if (!pccard_read_tuple(p_dev->socket, p_dev->func, CISTPL_FUNCID, &func_id)) { p_dev->func_id = func_id.func; p_dev->has_func_id = 1; } else { /* rule of thumb: cards with no FUNCID, but with * common memory device geometry information, are * probably memory cards (from pcmcia-cs) */ cistpl_device_geo_t devgeo; if (!pccard_read_tuple(p_dev->socket, p_dev->func, CISTPL_DEVICE_GEO, &devgeo)) { ds_dbg(0, "mem device geometry probably means " "FUNCID_MEMORY\n"); p_dev->func_id = CISTPL_FUNCID_MEMORY; p_dev->has_func_id = 1; } } if (!pccard_read_tuple(p_dev->socket, p_dev->func, CISTPL_VERS_1, &vers1)) { for (i=0; i < vers1.ns; i++) { char *tmp; unsigned int length; tmp = vers1.str + vers1.ofs[i]; length = strlen(tmp) + 1; if ((length < 3) || (length > 255)) continue; p_dev->prod_id[i] = kmalloc(sizeof(char) * length, GFP_KERNEL); if (!p_dev->prod_id[i]) continue; p_dev->prod_id[i] = strncpy(p_dev->prod_id[i], tmp, length); } } return 0; }
/* No kernel lock - fine */ static u_int ds_poll(struct file *file, poll_table *wait) { struct pcmcia_bus_socket *s; user_info_t *user; ds_dbg(2, "ds_poll(socket %d)\n", iminor(file->f_dentry->d_inode)); user = file->private_data; if (CHECK_USER(user)) return POLLERR; s = user->socket; /* * We don't check for a dead socket here since that * will send cardmgr into an endless spin. */ poll_wait(file, &s->queue, wait); if (!queue_empty(user)) return POLLIN | POLLRDNORM; return 0; } /* ds_poll */
/** * pcmcia_load_firmware - load CIS from userspace if device-provided is broken * @dev - the pcmcia device which needs a CIS override * @filename - requested filename in /lib/firmware/cis/ * * This uses the in-kernel firmware loading mechanism to use a "fake CIS" if * the one provided by the card is broken. The firmware files reside in * /lib/firmware/cis/ in userspace. */ static int pcmcia_load_firmware(struct pcmcia_device *dev, char * filename) { struct pcmcia_socket *s = dev->socket; const struct firmware *fw; char path[20]; int ret=-ENOMEM; cisdump_t *cis; if (!filename) return -EINVAL; ds_dbg(1, "trying to load firmware %s\n", filename); if (strlen(filename) > 14) return -EINVAL; snprintf(path, 20, "%s", filename); if (request_firmware(&fw, path, &dev->dev) == 0) { if (fw->size >= CISTPL_MAX_CIS_SIZE) goto release; cis = kzalloc(sizeof(cisdump_t), GFP_KERNEL); if (!cis) goto release; cis->Length = fw->size + 1; memcpy(cis->Data, fw->data, fw->size); if (!pcmcia_replace_cis(s, cis)) ret = 0; } release: release_firmware(fw); return (ret); }
static int ds_event(struct pcmcia_socket *skt, event_t event, int priority) { struct pcmcia_socket *s = pcmcia_get_socket(skt); ds_dbg(1, "ds_event(0x%06x, %d, 0x%p)\n", event, priority, skt); switch (event) { case CS_EVENT_CARD_REMOVAL: s->pcmcia_state.present = 0; pcmcia_card_remove(skt); handle_event(skt, event); break; case CS_EVENT_CARD_INSERTION: s->pcmcia_state.present = 1; pcmcia_card_add(skt); handle_event(skt, event); break; case CS_EVENT_EJECTION_REQUEST: break; case CS_EVENT_PM_SUSPEND: case CS_EVENT_PM_RESUME: case CS_EVENT_RESET_PHYSICAL: case CS_EVENT_CARD_RESET: default: handle_event(skt, event); break; } pcmcia_put_socket(s); return 0; } /* ds_event */
static int ds_ioctl(struct inode * inode, struct file * file, u_int cmd, u_long arg) { struct pcmcia_bus_socket *s; void __user *uarg = (char __user *)arg; u_int size; int ret, err; ds_ioctl_arg_t buf; user_info_t *user; ds_dbg(2, "ds_ioctl(socket %d, %#x, %#lx)\n", iminor(inode), cmd, arg); user = file->private_data; if (CHECK_USER(user)) return -EIO; s = user->socket; if (s->state & DS_SOCKET_DEAD) return -EIO; size = (cmd & IOCSIZE_MASK) >> IOCSIZE_SHIFT; if (size > sizeof(ds_ioctl_arg_t)) return -EINVAL; /* Permission check */ if (!(cmd & IOC_OUT) && !capable(CAP_SYS_ADMIN)) return -EPERM; if (cmd & IOC_IN) { err = verify_area(VERIFY_READ, uarg, size); if (err) { ds_dbg(3, "ds_ioctl(): verify_read = %d\n", err); return err; } } if (cmd & IOC_OUT) { err = verify_area(VERIFY_WRITE, uarg, size); if (err) { ds_dbg(3, "ds_ioctl(): verify_write = %d\n", err); return err; } } err = ret = 0; if (cmd & IOC_IN) __copy_from_user((char *)&buf, uarg, size); switch (cmd) { case DS_ADJUST_RESOURCE_INFO: ret = pcmcia_adjust_resource_info(s->handle, &buf.adjust); break; case DS_GET_CARD_SERVICES_INFO: ret = pcmcia_get_card_services_info(&buf.servinfo); break; case DS_GET_CONFIGURATION_INFO: ret = pcmcia_get_configuration_info(s->handle, &buf.config); break; case DS_GET_FIRST_TUPLE: ret = pcmcia_get_first_tuple(s->handle, &buf.tuple); break; case DS_GET_NEXT_TUPLE: ret = pcmcia_get_next_tuple(s->handle, &buf.tuple); break; case DS_GET_TUPLE_DATA: buf.tuple.TupleData = buf.tuple_parse.data; buf.tuple.TupleDataMax = sizeof(buf.tuple_parse.data); ret = pcmcia_get_tuple_data(s->handle, &buf.tuple); break; case DS_PARSE_TUPLE: buf.tuple.TupleData = buf.tuple_parse.data; ret = pcmcia_parse_tuple(s->handle, &buf.tuple, &buf.tuple_parse.parse); break; case DS_RESET_CARD: ret = pcmcia_reset_card(s->handle, NULL); break; case DS_GET_STATUS: ret = pcmcia_get_status(s->handle, &buf.status); break; case DS_VALIDATE_CIS: ret = pcmcia_validate_cis(s->handle, &buf.cisinfo); break; case DS_SUSPEND_CARD: ret = pcmcia_suspend_card(s->parent); break; case DS_RESUME_CARD: ret = pcmcia_resume_card(s->parent); break; case DS_EJECT_CARD: ret = pcmcia_eject_card(s->parent); break; case DS_INSERT_CARD: ret = pcmcia_insert_card(s->parent); break; case DS_ACCESS_CONFIGURATION_REGISTER: if ((buf.conf_reg.Action == CS_WRITE) && !capable(CAP_SYS_ADMIN)) return -EPERM; ret = pcmcia_access_configuration_register(s->handle, &buf.conf_reg); break; case DS_GET_FIRST_REGION: ret = pcmcia_get_first_region(s->handle, &buf.region); break; case DS_GET_NEXT_REGION: ret = pcmcia_get_next_region(s->handle, &buf.region); break; case DS_GET_FIRST_WINDOW: buf.win_info.handle = (window_handle_t)s->handle; ret = pcmcia_get_first_window(&buf.win_info.handle, &buf.win_info.window); break; case DS_GET_NEXT_WINDOW: ret = pcmcia_get_next_window(&buf.win_info.handle, &buf.win_info.window); break; case DS_GET_MEM_PAGE: ret = pcmcia_get_mem_page(buf.win_info.handle, &buf.win_info.map); break; case DS_REPLACE_CIS: ret = pcmcia_replace_cis(s->handle, &buf.cisdump); break; case DS_BIND_REQUEST: if (!capable(CAP_SYS_ADMIN)) return -EPERM; err = bind_request(s, &buf.bind_info); break; case DS_GET_DEVICE_INFO: err = get_device_info(s, &buf.bind_info, 1); break; case DS_GET_NEXT_DEVICE: err = get_device_info(s, &buf.bind_info, 0); break; case DS_UNBIND_REQUEST: err = unbind_request(s, &buf.bind_info); break; case DS_BIND_MTD: if (!capable(CAP_SYS_ADMIN)) return -EPERM; err = bind_mtd(s, &buf.mtd_info); break; default: err = -EINVAL; } if ((err == 0) && (ret != CS_SUCCESS)) { ds_dbg(2, "ds_ioctl: ret = %d\n", ret); switch (ret) { case CS_BAD_SOCKET: case CS_NO_CARD: err = -ENODEV; break; case CS_BAD_ARGS: case CS_BAD_ATTRIBUTE: case CS_BAD_IRQ: case CS_BAD_TUPLE: err = -EINVAL; break; case CS_IN_USE: err = -EBUSY; break; case CS_OUT_OF_RESOURCE: err = -ENOSPC; break; case CS_NO_MORE_ITEMS: err = -ENODATA; break; case CS_UNSUPPORTED_FUNCTION: err = -ENOSYS; break; default: err = -EIO; break; } } if (cmd & IOC_OUT) __copy_to_user(uarg, (char *)&buf, size); return err; } /* ds_ioctl */
static int bind_request(struct pcmcia_bus_socket *s, bind_info_t *bind_info) { struct pcmcia_driver *driver; socket_bind_t *b; bind_req_t bind_req; int ret; if (!s) return -EINVAL; ds_dbg(2, "bind_request(%d, '%s')\n", s->parent->sock, (char *)bind_info->dev_info); driver = get_pcmcia_driver(&bind_info->dev_info); if (!driver) return -EINVAL; for (b = s->bind; b; b = b->next) if ((driver == b->driver) && (bind_info->function == b->function)) break; if (b != NULL) { bind_info->instance = b->instance; return -EBUSY; } if (!try_module_get(driver->owner)) return -EINVAL; bind_req.Socket = s->parent; bind_req.Function = bind_info->function; bind_req.dev_info = (dev_info_t *) driver->drv.name; ret = pcmcia_bind_device(&bind_req); if (ret != CS_SUCCESS) { cs_error(NULL, BindDevice, ret); printk(KERN_NOTICE "ds: unable to bind '%s' to socket %d\n", (char *)dev_info, s->parent->sock); module_put(driver->owner); return -ENODEV; } /* Add binding to list for this socket */ driver->use_count++; b = kmalloc(sizeof(socket_bind_t), GFP_KERNEL); if (!b) { driver->use_count--; module_put(driver->owner); return -ENOMEM; } b->driver = driver; b->function = bind_info->function; b->instance = NULL; b->next = s->bind; s->bind = b; if (driver->attach) { b->instance = driver->attach(); if (b->instance == NULL) { printk(KERN_NOTICE "ds: unable to create instance " "of '%s'!\n", (char *)bind_info->dev_info); module_put(driver->owner); return -ENODEV; } } return 0; } /* bind_request */