freebsd-nq/usr.sbin/pccard/pccardd/cardd.c
Mitsuru IWASAKI 29d1eb210d Merge from PAO3 -- Quick fix a bug which assign_driver() checks wrong
member variable to find the configuration on new driver allocation.
Correct condition is that card_config and driver are not in use.  Both
of them are cleared in card_removed() (conf->driver->card never be
cleared).

This fix problems `No free configuration for card' on insertion, and
pccardd core dump on removal in condition of the same driver but
different card.

Also this might be emergency measures, complete solution would be made
after Hosokawa-san come back.

Consulted with:	imp
Waiting for:	hosokawa
2000-01-24 13:15:05 +00:00

661 lines
16 KiB
C

/*
* Copyright (c) 1995 Andrew McRae. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef lint
static const char rcsid[] =
"$FreeBSD$";
#endif /* not lint */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <ctype.h>
#include <sys/ioctl.h>
#include "cardd.h"
static struct card_config *assign_driver(struct card *);
static int assign_io(struct slot *);
static int setup_slot(struct slot *);
static void card_inserted(struct slot *);
static void card_removed(struct slot *);
static void pr_cmd(struct cmd *);
static void read_ether(struct slot *);
static void read_ether_attr2(struct slot *sp);
/*
* Dump configuration file data.
*/
void
dump_config_file(void)
{
struct card *cp;
struct card_config *confp;
for (cp = cards; cp; cp = cp->next) {
printf("Card manuf %s, vers %s\n", cp->manuf, cp->version);
printf("Configuration entries:\n");
for (confp = cp->config; confp; confp = confp->next)
printf("\tIndex code = 0x%x, driver name = %s\n",
confp->index, confp->driver->name);
if (cp->insert) {
printf("Insert commands are:\n");
pr_cmd(cp->insert);
}
if (cp->remove) {
printf("Remove commands are:\n");
pr_cmd(cp->remove);
}
}
}
static void
pr_cmd(struct cmd *cp)
{
while (cp) {
printf("\t%s\n", cp->line);
cp = cp->next;
}
}
/*
* readslots - read all the PCMCIA slots, and build
* a list of the slots.
*/
struct slot *
readslots(void)
{
char name[128];
int i, fd;
struct slot *slots, *sp;
slots = NULL;
for (i = 0; i < MAXSLOT; i++) {
sprintf(name, CARD_DEVICE, i);
fd = open(name, O_RDWR);
if (fd < 0)
continue;
sp = xmalloc(sizeof(*sp));
sp->fd = fd;
sp->name = newstr(name);
sp->slot = i;
/* Check to see if the controller memory has been set up. */
if (slots == 0) {
unsigned long mem = 0;
if (ioctl(fd, PIOCRWMEM, &mem))
logerr("ioctl (PIOCRWMEM)");
#ifdef DEBUG
logmsg("mem=0x%x\n", mem);
#endif
if (mem == 0) {
mem = alloc_memory(4 * 1024);
if (mem == 0)
die("can't allocate memory for "
"controller access");
if (ioctl(fd, PIOCRWMEM, &mem))
logerr("ioctl (PIOCRWMEM)");
}
}
sp->next = slots;
slots = sp;
slot_change(sp);
}
return (slots);
}
/*
* slot_change - Card status has changed.
* read new state and process.
*/
void
slot_change(struct slot *sp)
{
struct slotstate state;
if (ioctl(sp->fd, PIOCGSTATE, &state)) {
logerr("ioctl (PIOCGSTATE)");
return;
}
switch (state.state) {
case empty:
case noslot:
/* Debounce potentially incorrectly reported removals */
if (state.laststate == filled || state.laststate == suspend)
card_removed(sp);
break;
case filled:
/* KLUDGE: if we were suspended, remove card */
if (state.laststate == suspend)
card_removed(sp);
card_inserted(sp);
break;
case suspend:
/* ignored */
break;
}
}
/*
* card_removed - card has been removed from slot.
* Execute the remove commands, and clear the slot's state.
* Execute the device commands, then the driver commands
* and then the card commands. This is the reverse
* order to the insertion commands
*/
void
card_removed(struct slot *sp)
{
struct card *cp;
if (sp->cis)
freecis(sp->cis);
if (sp->config) {
sp->config->inuse = 0;
sp->config->driver->inuse = 0;
}
if ((cp = sp->card) != 0)
execute(cp->remove, sp);
sp->cis = 0;
sp->config = 0;
/* release io */
bit_nset(io_avail, sp->io.addr, sp->io.addr + sp->io.size - 1);
/* release irq */
if (sp->irq)
pool_irq[sp->irq] = 1;
}
/*
* card_inserted - Card has been inserted;
* - Read the CIS
* - match the card type.
* - Match the driver and allocate a driver instance.
* - Allocate I/O ports, memory and IRQ.
* - Set up the slot.
* - assign the driver (if failed, then terminate).
* - Run the card commands.
* - Run the driver commands
* - Run the device commands
*/
void
card_inserted(struct slot *sp)
{
struct card *cp;
usleep(pccard_init_sleep);
sp->cis = readcis(sp->fd);
if (sp->cis == 0) {
logmsg("Error reading CIS on %s\n", sp->name);
return;
}
#if 0
dumpcis(sp->cis);
#endif
for (cp = cards; cp; cp = cp->next)
if (strncmp(cp->manuf, sp->cis->manuf, CIS_MAXSTR) == 0 &&
strncmp(cp->version, sp->cis->vers, CIS_MAXSTR) == 0)
break;
sp->card = cp;
#if 0
reset_slot(sp);
#endif
if (cp == 0) {
logmsg("No card in database for \"%s\"(\"%s\")",
sp->cis->manuf, sp->cis->vers);
return;
}
if (cp->ether) {
struct ether *e = 0;
e = cp->ether;
switch (e->type) {
case ETHTYPE_ATTR2:
read_ether_attr2(sp);
break;
default:
read_ether(sp);
break;
}
}
if ((sp->config = assign_driver(cp)) == NULL)
return;
if (assign_io(sp)) {
logmsg("Resource allocation failure for %s", sp->cis->manuf);
return;
}
/*
*
* Once assigned, set up the I/O & mem contexts, set up the
* windows, and then attach the driver.
*/
if (setup_slot(sp))
execute(cp->insert, sp);
#if 0
else
reset_slot(sp);
#endif
}
/*
* read_ether - read ethernet address from card. Offset is
* the offset into the attribute memory of the card.
*/
static void
read_ether(struct slot *sp)
{
unsigned char net_addr[12];
lseek(sp->fd, (off_t)sp->card->ether->value, SEEK_SET);
if (read(sp->fd, net_addr, sizeof(net_addr)) != sizeof(net_addr)) {
logerr("read err on net addr");
return;
}
sp->eaddr[0] = net_addr[0];
sp->eaddr[1] = net_addr[2];
sp->eaddr[2] = net_addr[4];
sp->eaddr[3] = net_addr[6];
sp->eaddr[4] = net_addr[8];
sp->eaddr[5] = net_addr[10];
logmsg("Ether=%02x:%02x:%02x:%02x:%02x:%02x\n",
sp->eaddr[0], sp->eaddr[1], sp->eaddr[2],
sp->eaddr[3], sp->eaddr[4], sp->eaddr[5]);
}
/*
* Megahertz X-Jack Ethernet uses unique way to get/set MAC
* address of the card.
*/
static void
read_ether_attr2(struct slot *sp)
{
int i;
char *hexaddr;
hexaddr = sp->cis->add_info2;
for (i = 0; i < 6; i++)
sp->eaddr[i] = 0;
if (!hexaddr)
return;
if (strlen(hexaddr) != 12)
return;
for (i = 0; i < 12; i++)
if (!isxdigit(hexaddr[i]))
return;
for (i = 0; i < 6; i++) {
u_int d;
char s[3];
s[0] = hexaddr[i * 2];
s[1] = hexaddr[i * 2 + 1];
s[2] = '\0';
if (!sscanf(s, "%x", &d)) {
int j;
for (j = 0; j < 6; j++)
sp->eaddr[j] = 0;
return;
}
sp->eaddr[i] = (u_char)d;
}
sp->flags |= EADDR_CONFIGED;
}
/*
* assign_driver - Assign driver to card.
* First, see if an existing driver is already setup.
*/
static struct card_config *
assign_driver(struct card *cp)
{
struct driver *drvp;
struct card_config *conf;
for (conf = cp->config; conf; conf = conf->next)
if (conf->inuse == 0 && conf->driver->card == cp &&
conf->driver->config == conf &&
conf->driver->inuse == 0) {
if (debug_level > 0) {
logmsg("Found existing driver (%s) for %s\n",
conf->driver->name, cp->manuf);
}
conf->driver->inuse = 1;
conf->inuse = 1;
return (conf);
}
/*
* New driver must be allocated. Find the first configuration
* not in use.
*/
for (conf = cp->config; conf; conf = conf->next)
if (conf->inuse == 0 && conf->driver->inuse/*card*/ == 0)
break;
if (conf == 0) {
logmsg("No free configuration for card %s", cp->manuf);
return (NULL);
}
/*
* Now we have a free driver and a matching configuration.
* Before assigning and allocating everything, check to
* see if a device class can be allocated to this.
*/
drvp = conf->driver;
/* If none available, then we can't use this card. */
if (drvp->inuse) {
logmsg("Driver already being used for %s", cp->manuf);
return (NULL);
}
/* Allocate a free IRQ if none has been specified */
if (conf->irq == 0) {
int i;
for (i = 1; i < 16; i++)
if (pool_irq[i]) {
conf->irq = i;
pool_irq[i] = 0;
break;
}
if (conf->irq == 0) {
logmsg("Failed to allocate IRQ for %s\n", cp->manuf);
return (NULL);
}
}
drvp->card = cp;
drvp->config = conf;
drvp->inuse = 1;
conf->inuse = 1;
return (conf);
}
/*
* assign_io - Allocate resources to slot matching the
* configuration index selected.
*/
static int
assign_io(struct slot *sp)
{
struct cis *cis;
struct cis_config *cisconf, *defconf;
cis = sp->cis;
defconf = cis->def_config;
for (cisconf = cis->conf; cisconf; cisconf = cisconf->next)
if (cisconf->id == sp->config->index)
break;
if (cisconf == 0) {
logmsg("Config id %d not present in this card",
sp->config->index);
return (-1);
}
sp->card_config = cisconf;
/*
* Found a matching configuration. Now look at the I/O, memory and IRQ
* to create the desired parameters. Look at memory first.
*/
if (cisconf->memspace || (defconf && defconf->memspace)) {
struct cis_memblk *mp;
mp = cisconf->mem;
if (!cisconf->memspace)
mp = defconf->mem;
sp->mem.size = mp->length;
sp->mem.cardaddr = mp->address;
/* For now, we allocate our own memory from the pool. */
sp->mem.addr = sp->config->driver->mem;
/*
* Host memory address is required. Allocate one
* from our pool.
*/
if (sp->mem.size && sp->mem.addr == 0) {
sp->mem.addr = alloc_memory(mp->length);
if (sp->mem.addr == 0)
return (-2);
sp->config->driver->mem = sp->mem.addr;
}
sp->mem.cardaddr = 0x4000;
if (debug_level > 0) {
logmsg("Using mem addr 0x%x, size %d, card addr 0x%x\n",
sp->mem.addr, sp->mem.size, sp->mem.cardaddr);
}
}
/* Now look at I/O. */
bzero(&sp->io, sizeof(sp->io));
if (cisconf->iospace || (defconf && defconf->iospace)
|| sp->card->iosize) {
struct cis_config *cp;
int iosize;
cp = cisconf;
if (!cisconf->iospace)
cp = defconf;
iosize = sp->card->iosize;
/* iosize auto */
if (iosize < 0) {
if (cp->io)
iosize = cp->io->size;
else
iosize = 1 << cp->io_addr;
}
/*
* If # of I/O lines decoded == 10, then card does its
* own decoding.
*
* If an I/O block exists, then use it.
* If no address (but a length) is available, allocate
* from the pool.
*/
if (iosize) {
sp->io.addr = 0;
sp->io.size = iosize;
}
else if (cp->io) {
sp->io.addr = cp->io->addr;
sp->io.size = cp->io->size;
} else {
/*
* No I/O block, assume the address lines
* decode gives the size.
*/
sp->io.size = 1 << cp->io_addr;
}
if (sp->io.addr == 0) {
int i = bit_fns(io_avail, IOPORTS, sp->io.size);
if (i < 0)
return (-1);
sp->io.addr = i;
}
bit_nclear(io_avail, sp->io.addr,
sp->io.addr + sp->io.size - 1);
sp->flags |= IO_ASSIGNED;
/* Set up the size to take into account the decode lines. */
sp->io.cardaddr = cp->io_addr;
switch (cp->io_bus) {
case 0:
break;
case 1:
sp->io.flags = IODF_WS;
break;
case 2:
sp->io.flags = IODF_WS | IODF_CS16;
break;
case 3:
sp->io.flags = IODF_WS | IODF_CS16 | IODF_16BIT;
break;
}
if (debug_level > 0) {
logmsg("Using I/O addr 0x%x, size %d\n",
sp->io.addr, sp->io.size);
}
}
sp->irq = sp->config->irq;
sp->flags |= IRQ_ASSIGNED;
return (0);
}
/*
* setup_slot - Allocate the I/O and memory contexts
* return true if completed OK.
*/
static int
setup_slot(struct slot *sp)
{
struct mem_desc mem;
struct io_desc io;
struct dev_desc drv;
struct driver *drvp = sp->config->driver;
char *p;
char c;
off_t offs;
int rw_flags;
memset(&io, 0, sizeof io);
memset(&drv, 0, sizeof drv);
offs = sp->cis->reg_addr;
rw_flags = MDF_ATTR;
ioctl(sp->fd, PIOCRWFLAG, &rw_flags);
lseek(sp->fd, offs, SEEK_SET);
c = 0x80;
write(sp->fd, &c, sizeof(c));
usleep(sp->card->reset_time * 1000);
lseek(sp->fd, offs, SEEK_SET);
c = 0x00;
write(sp->fd, &c, sizeof(c));
usleep(sp->card->reset_time * 1000);
lseek(sp->fd, offs, SEEK_SET);
c = sp->config->index;
c |= 0x40;
write(sp->fd, &c, sizeof(c));
if (debug_level > 0) {
logmsg("Setting config reg at offs 0x%lx to 0x%x, "
"Reset time = %d ms\n", (unsigned long)offs, c,
sp->card->reset_time);
}
usleep(pccard_init_sleep);
usleep(sp->card->reset_time * 1000);
/* If other config registers exist, set them up. */
if (sp->cis->ccrs & 2) {
/* CCSR */
c = 0;
if (sp->cis->def_config && sp->cis->def_config->misc_valid &&
(sp->cis->def_config->misc & 0x8))
c |= 0x08;
if (sp->card_config->io_bus == 1)
c |= 0x20;
lseek(sp->fd, offs + 2, SEEK_SET);
write(sp->fd, &c, sizeof(c));
}
if (sp->flags & MEM_ASSIGNED) {
mem.window = 0;
mem.flags = sp->mem.flags | MDF_ACTIVE | MDF_16BITS;
mem.start = (caddr_t) sp->mem.addr;
mem.card = sp->mem.cardaddr;
mem.size = sp->mem.size;
if (ioctl(sp->fd, PIOCSMEM, &mem)) {
logerr("ioctl (PIOCSMEM)");
return (0);
}
}
io.window = 0;
if (sp->flags & IO_ASSIGNED) {
io.flags = sp->io.flags;
io.start = sp->io.addr;
io.size = sp->io.size;
#if 0
io.start = sp->io.addr & ~((1 << sp->io.cardaddr) - 1);
io.size = 1 << sp->io.cardaddr;
if (io.start < 0x100) {
io.start = 0x100;
io.size = 0x300;
}
#endif
if (debug_level > 0) {
logmsg("Assigning I/O window %d, start 0x%x, "
"size 0x%x flags 0x%x\n", io.window, io.start,
io.size, io.flags);
}
io.flags |= IODF_ACTIVE;
if (ioctl(sp->fd, PIOCSIO, &io)) {
logerr("ioctl (PIOCSIO)");
return (0);
}
}
strcpy(drv.name, drvp->kernel);
drv.unit = drvp->unit;
drv.irqmask = 1 << sp->irq;
drv.flags = sp->config->flags;
if (sp->flags & MEM_ASSIGNED) {
drv.mem = sp->mem.addr;
drv.memsize = sp->mem.size;
} else {
drv.mem = 0;
drv.memsize = 0;
}
if (sp->flags & IO_ASSIGNED)
drv.iobase = sp->io.addr;
else
drv.iobase = 0;
#ifdef DEV_DESC_HAS_SIZE
drv.iosize = sp->io.size;
#endif
if (debug_level > 0) {
logmsg("Assign %s%d, io 0x%x-0x%x, mem 0x%lx, %d bytes, "
"irq %d, flags %x\n", drv.name, drv.unit, drv.iobase,
drv.iobase + sp->io.size - 1, drv.mem, drv.memsize,
sp->irq, drv.flags);
}
/*
* If the driver fails to be connected to the device,
* then it may mean that the driver did not recognise it.
*/
memcpy(drv.misc, sp->eaddr, 6);
if (ioctl(sp->fd, PIOCSDRV, &drv)) {
logmsg("driver allocation failed for %s(%s): %s",
sp->card->manuf, sp->card->version, strerror(errno));
return (0);
}
drv.name[sizeof(drv.name) - 1] = '\0';
if (strncmp(drv.name, drvp->kernel, sizeof(drv.name))) {
drvp->kernel = newstr(drv.name);
p = drvp->kernel;
while (*p++)
if (*p >= '0' && *p <= '9') {
drvp->unit = atoi(p);
*p = '\0';
break;
}
}
return (1);
}