/*
 *	pccard.c - Interface code for PC-CARD controllers.
 *
 *	June 1995, Andrew McRae (andrew@mega.com.au)
 *-------------------------------------------------------------------------
 *
 * 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.
 */

#include "crd.h"
#if	NCRD > 0

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/errno.h>
#include <sys/file.h>
#include <sys/proc.h>
#include <sys/ioctl.h>
#include <sys/syslog.h>
#include <sys/devconf.h>
#include <sys/malloc.h>

#include <i386/isa/isa.h>
#include <i386/isa/isa_device.h>
#include <i386/isa/icu.h>

#include "apm.h"
#if	NAPM > 0
#include <machine/apm_bios.h>
#endif /* NAPM > 0 */

#include <pccard/card.h>
#include <pccard/slot.h>

#define	PCCARD_MEMSIZE	(4*1024)

#define MIN(a,b)	((a)<(b)?(a):(b))

/*
 *	cdevsw entry points
 */
int	crdopen	__P((dev_t dev, int oflags, int devtype,
				 struct proc *p));
int	crdclose	__P((dev_t dev, int fflag, int devtype,
				 struct proc *p));
int	crdread	__P((dev_t dev, struct uio *uio, int ioflag));
int	crdwrite	__P((dev_t dev, struct uio *uio, int ioflag));
int	crdioctl	__P((dev_t dev, int cmd, caddr_t data,
				 int fflag, struct proc *p));
int	crdselect	__P((dev_t dev, int rw, struct proc *p));

static int allocate_driver(struct slot *, struct drv_desc *);
static void inserted(void *);
static void disable_slot(struct slot *);
static int invalid_io_memory(unsigned long, int);
static struct pccard_drv *find_driver(char *);
static void remove_device(struct pccard_dev *);
static void slot_irq_handler(int);
#if	NAPM > 0
/*
 *	For the APM stuff, the apmhook structure is kept
 *	separate from the slot structure so that the slot
 *	drivers do not need to know about the hooks (or the
 *	data structures).
 */
static int slot_suspend(struct slot *sp);
static int slot_resume(struct slot *sp);
static struct apmhook s_hook[MAXSLOT];		/* APM suspend */
static struct apmhook r_hook[MAXSLOT];		/* APM resume */
#endif /* NAPM > 0 */

void	pcic_probe();

static struct slot *pccard_slots[MAXSLOT];	/* slot entries */
static struct slot *slot_list;
static struct slot_cont *cont_list;
static struct pccard_drv *drivers;		/* Card drivers */
/*
 *	The driver interface for read/write uses a block
 *	of memory in the ISA I/O memory space allocated via
 *	an ioctl setting.
 */
static unsigned long pccard_mem;	/* Physical memory */
static unsigned char *pccard_kmem;	/* Kernel virtual address */
/*
 *	pccard_configure - called by autoconf code.
 *	Probes for various PC-CARD controllers, and
 *	initialises data structures to point to the
 *	various slots.
 *
 *	Each controller indicates the number of slots
 *	that it sees, and these are mapped to a master
 *	slot number accessed via the character device entries.
 */
void
pccard_configure()
{
struct slot_cont *cp;
struct slot *sp;

#include "pcic.h"
#if NPCIC > 0
	pcic_probe();
#endif
}
/*
 *	pccard_add_driver - Add a new driver to the list of
 *	drivers available for allocation.
 */
void
pccard_add_driver(struct pccard_drv *dp)
{
/*
 *	If already loaded, then reject the driver.
 */
	if (find_driver(dp->name))
		{
		printf("Driver %s already loaded\n", dp->name);
		return;
		}
	dp->next = drivers;
	drivers = dp;
}
/*
 *	pccard_remove_driver - called to unlink driver
 *	from devices. Usually called when drivers are
 *	are unloaded from kernel.
 */
void
pccard_remove_driver(struct pccard_drv *dp)
{
struct slot *sp;
struct pccard_dev *devp, *next;
struct pccard_drv *drvp;

	for (sp = slot_list; sp; sp = sp->next)
		for (devp = sp->devices; devp; devp = next)
			{
			next = devp->next;
			if (devp->drv == dp)
				remove_device(devp);
			}
/*
 *	Once all the devices belonging to this driver have been
 *	freed, then remove the driver from the list
 *	of registered drivers.
 */
	if (drivers == dp)
		drivers = dp->next;
	else
		for (drvp = drivers; drvp->next; drvp = drvp->next)
			if (drvp->next == dp)
				{
				drvp->next = dp->next;
				break;
				}
}
/*
 *	pccard_remove_controller - Called when the slot
 *	driver is unloaded. The plan is to unload
 *	drivers from the slots, and then remove the
 *	slots from the slot list, and then finally
 *	remove the controller structure. Messy...
 */
void
pccard_remove_controller(struct slot_cont *cp)
{
struct slot *sp, *next, *last = 0;
struct slot_cont *cl;
struct pccard_dev *dp;

	for (sp = slot_list; sp; sp = next)
		{
		next = sp->next;
/*
 *	If this slot belongs to this controller,
 *	remove this slot.
 */
		if (sp->cinfo == cp)
			{
			pccard_slots[sp->slot] = 0;
			if (sp->insert_timeout)
				untimeout(inserted, (void *)sp);
/*
 *	Unload the drivers attached to this slot.
 */
			while (dp = sp->devices)
				remove_device(dp);
/*
 *	Disable the slot and unlink the slot from the slot list.
 */
			disable_slot(sp);
			if (last)
				last->next = next;
			else
				slot_list = next;
#if NAPM > 0
			apm_hook_disestablish(APM_HOOK_SUSPEND,
				&s_hook[sp->slot]);
			apm_hook_disestablish(APM_HOOK_RESUME,
				&r_hook[sp->slot]);
#endif
			if (cp->extra && sp->cdata)
				FREE(sp->cdata, M_DEVBUF);
			FREE(sp, M_DEVBUF);
/*
 *	xx Can't use sp after we have freed it.
 */
			}
		else
			last = sp;
		}
/*
 *	Unlink controller structure from controller list.
 */
	if (cont_list == cp)
		cont_list = cp->next;
	else
		for (cl = cont_list; cl->next; cl = cl->next)
			if (cl->next == cp)
				{
				cl->next = cp->next;
				break;
				}
}

/*
 *	disable_slot - Disables the slot by removing
 *	the power and unmapping the I/O
 */
static void
disable_slot(struct slot *sp)
{
int i;
struct pccard_dev *devp;
/*
 *	Unload all the drivers on this slot. Note we can't
 *	call remove_device from here, because this may be called
 *	from the event routine, which is called from the slot
 *	controller's ISR, and this could remove the device
 *	structure out in the middle of some driver activity.
 *
 *	Note that a race condition is possible here; if a
 *	driver is accessing the device and it is removed, then
 *	all bets are off...
 */
	for (devp = sp->devices; devp; devp = devp->next)
		{
		devp->drv->unload(devp);
		devp->running = 0;
		}
/*
 *	Power off the slot.
 */
	sp->cinfo->disable(sp);
/*
 *	De-activate all contexts.
 */
	for (i = 0; i < sp->cinfo->maxmem; i++)
		if (sp->mem[i].flags & MDF_ACTIVE)
			{
			sp->mem[i].flags = 0;
			(void)sp->cinfo->mapmem(sp, i);
			}
	for (i = 0; i < sp->cinfo->maxio; i++)
		if (sp->io[i].flags & IODF_ACTIVE)
			{
			sp->io[i].flags = 0;
			(void)sp->cinfo->mapio(sp, i);
			}
}

/*
 *	APM hooks for suspending and resuming.
 */
#if	NAPM > 0
static int
slot_suspend(struct slot *sp)
{
struct pccard_dev *dp;

	for (dp = sp->devices; dp; dp = dp->next)
		(void)dp->drv->suspend(dp);
	sp->cinfo->disable(sp);
	return(0);
}
static int
slot_resume(struct slot *sp)
{
struct pccard_dev *dp;

	sp->cinfo->power(sp);
	if (sp->irq)
		sp->cinfo->mapirq(sp, sp->irq);
	for (dp = sp->devices; dp; dp = dp->next)
		(void)dp->drv->init(dp, 0);
	return(0);
}
#endif /* NAPM > 0 */
/*
 *	pccard_alloc_slot - Called from controller probe
 *	routine, this function allocates a new PC-CARD slot
 *	and initialises the data structures using the data provided.
 *	It returns the allocated structure to the probe routine
 *	to allow the controller specific data to be initialised.
 */
struct slot *
pccard_alloc_slot(struct slot_cont *cp)
{
struct slot *sp;
int	slotno;

	for (slotno = 0; slotno < MAXSLOT; slotno++)
		if (pccard_slots[slotno] == 0)
			break;
	if (slotno >= MAXSLOT)
		return(0);
	MALLOC(sp, struct slot *, sizeof(*sp), M_DEVBUF, M_WAITOK);
	bzero(sp, sizeof(*sp));
	if (cp->extra)
		{
		MALLOC(sp->cdata, void *, cp->extra, M_DEVBUF, M_WAITOK);
		bzero(sp->cdata, cp->extra);
		}
	sp->cinfo = cp;
	sp->slot = slotno;
	pccard_slots[slotno] = sp;
	sp->next = slot_list;
	slot_list = sp;
/*
 *	If this controller hasn't been seen before, then
 *	link it into the list of controllers.
 */
	if (cp->slots++ == 0)
		{
		cp->next = cont_list;
		cont_list = cp;
		if (cp->maxmem > NUM_MEM_WINDOWS)
			cp->maxmem = NUM_MEM_WINDOWS;
		if (cp->maxio > NUM_IO_WINDOWS)
			cp->maxio = NUM_IO_WINDOWS;
		printf("PC-Card %s (%d mem & %d I/O windows)\n",
			cp->name, cp->maxmem, cp->maxio);
		}
#if NAPM > 0
	{
	struct apmhook *ap;

	ap = &s_hook[sp->slot];
	ap->ah_fun = slot_suspend;
	ap->ah_arg = (void *) sp;
	ap->ah_name = cp->name;
	ap->ah_order = APM_MID_ORDER;
	apm_hook_establish(APM_HOOK_SUSPEND, ap);
	ap = &r_hook[sp->slot];
	ap->ah_fun = slot_resume;
	ap->ah_arg = (void *) sp;
	ap->ah_name = cp->name;
	ap->ah_order = APM_MID_ORDER;
	apm_hook_establish(APM_HOOK_RESUME, ap);
	}
#endif	/* NAPM > 0 */
	return(sp);
}
/*
 *	pccard_alloc_intr - allocate an interrupt from the
 *	free interrupts and return its number. The interrupts
 *	allowed are passed as a mask.
 */
int
pccard_alloc_intr(int imask, inthand2_t *hand, int unit, int *maskp)
{
int rv, irq;
unsigned int mask;

	for (irq = 1; irq < 16; irq++)
		{
		mask = 1ul << irq;
		if ((mask & imask) &&
		    register_intr(irq, 0, 0, hand, maskp, unit)==0)
			{
			if (maskp)
				INTRMASK (*maskp, mask);

			update_intr_masks();

			INTREN (mask);
			return(irq);
			}
		}
	return(-1);
}
/*
 *	allocate_driver - Create a new device entry for this
 *	slot, and attach a driver to it.
 */
static int
allocate_driver(struct slot *sp, struct drv_desc *drvp)
{
struct pccard_dev *devp;
struct pccard_drv *dp;
int err, irq = 0, s;

	dp = find_driver(drvp->name);
	if (dp == 0)
		return(ENXIO);
/*
 *	If an instance of this driver is already installed,
 *	but not running, then remove it. If it is running,
 *	then reject the request.
 */
	for (devp = sp->devices; devp; devp = devp->next)
		if (devp->drv == dp && devp->isahd.id_unit == drvp->unit)
			{
			if (devp->running)
				return(EBUSY);
			remove_device(devp);
			break;
			}
/*
 *	If an interrupt mask has been given, then check it
 *	against the slot interrupt (if one has been allocated).
 */
	if (drvp->irqmask && dp->imask)
		{
		if ((sp->cinfo->irqs & drvp->irqmask)==0)
			return(EINVAL);
		if (sp->irq)
			{
			if (((1 << sp->irq)&drvp->irqmask)==0)
				return(EINVAL);
			sp->irqref++;
			irq = sp->irq;
			}
/*
 *	Attempt to allocate an interrupt.  XXX We lose at the moment
 *	if the second device relies on a different interrupt mask.
 */
		else
			{
			irq = pccard_alloc_intr(drvp->irqmask,
				slot_irq_handler, (int)sp, dp->imask);
			if (irq < 0)
				return(EINVAL);
			sp->irq = irq;
			sp->irqref = 1;
			sp->cinfo->mapirq(sp, sp->irq);
			}
		}
	MALLOC(devp, struct pccard_dev *, sizeof(*devp), M_DEVBUF, M_WAITOK);
	bzero(devp, sizeof(*devp));
/*
 *	Create an entry for the device under this slot.
 */
	devp->drv = dp;
	devp->sp = sp;
	devp->isahd.id_unit = drvp->unit;
	devp->isahd.id_msize = drvp->memsize;
	devp->isahd.id_iobase = drvp->iobase;
	if (irq)
		devp->isahd.id_irq = 1 << irq;
	devp->isahd.id_flags = drvp->flags;
/*
 *	Convert the memory to kernel space.
 */
	if (drvp->mem)
		devp->isahd.id_maddr = (caddr_t)(drvp->mem + atdevbase - 0xA0000);
	else
		devp->isahd.id_maddr = 0;
	devp->next = sp->devices;
	sp->devices = devp;
	s = splhigh();
	err = dp->init(devp, 1);
	splx(s);
/*
 *	If the init functions returns no error, then the
 *	device has been successfully installed. If so, then
 *	attach it to the slot, otherwise free it and return
 *	the error.
 */
	if (err)
		remove_device(devp);
	else
		devp->running = 1;
	return(err);
}
static void
remove_device(struct pccard_dev *dp)
{
struct slot *sp = dp->sp;
struct pccard_dev *list;
int s;

/*
 *	If an interrupt is enabled on this slot,
 *	then unregister it if no-one else is using it.
 */
	s = splhigh();
	if (dp->running)
		dp->drv->unload(dp);
	if (dp->isahd.id_irq && --sp->irqref == 0)
		{
		sp->cinfo->mapirq(sp, 0);
		unregister_intr(sp->irq, slot_irq_handler);
		sp->irq = 0;
		}
	splx(s);
/*
 *	Remove from device list on this slot.
 */
	if (sp->devices == dp)
		sp->devices = dp->next;
	else
		for (list = sp->devices; list->next; list = list->next)
			if (list->next == dp)
				{
				list->next = dp->next;
				break;
				}
/*
 *	Finally, free the memory space.
 */
	FREE(dp, M_DEVBUF);
}
/*
 *	card insert routine - Called from a timeout to debounce
 *	insertion events.
 */
static void
inserted(void *arg)
{
struct slot *sp = arg;

	sp->insert_timeout = 0;
	sp->state = filled;
/*
 *	Enable 5V to the card so that the CIS can be read.
 */
	sp->pwr.vcc = 50;
	sp->pwr.vpp = 0;
	sp->cinfo->power(sp);
	printf("Card inserted, slot %d\n", sp->slot);
/*
 *	Now reset the card.
 */
	sp->cinfo->reset(sp);
	selwakeup(&sp->selp);
}
/*
 *	Card event callback. Called at splhigh to prevent
 *	device interrupts from interceding.
 */
void
pccard_event(struct slot *sp, enum card_event event)
{
int s;

	if (sp->insert_timeout)
		{
		sp->insert_timeout = 0;
		untimeout(inserted, (void *)sp);
		}
	switch(event)
		{
/*
 *	The slot and devices are disabled, but the
 *	data structures are not unlinked.
 */
	case card_removed:
		if (sp->state == filled)
			{
			s = splhigh();
			disable_slot(sp);
			sp->state = empty;
			splx(s);
			printf("Card removed, slot %d\n", sp->slot);
			selwakeup(&sp->selp);
			}
		break;
	case card_inserted:
		sp->insert_timeout = 1;
		timeout(inserted, (void *)sp, hz/4);
		break;
		}
}
/*
 *	slot_irq_handler - Interrupt handler for shared irq devices.
 */
static void
slot_irq_handler(int sp)
{
struct pccard_dev *dp;

/*
 *	For each device that has the shared interrupt,
 *	call the interrupt handler. If the interrupt was
 *	caught, the handler returns true.
 */
	for (dp = ((struct slot *)sp)->devices; dp; dp = dp->next)
		if (dp->isahd.id_irq && dp->running && dp->drv->handler(dp))
			return;
	printf("Slot %d, unfielded interrupt (%d)\n",
		((struct slot *)sp)->slot, ((struct slot *)sp)->irq);
}
/*
 *	Device driver interface.
 */
int
crdopen(dev_t dev, int oflags, int devtype, struct proc *p)
{
struct slot *sp;

	if (minor(dev) >= MAXSLOT)
		return(ENXIO);
	sp = pccard_slots[minor(dev)];
	if (sp==0)
		return(ENXIO);
	if (sp->rwmem == 0)
		sp->rwmem = MDF_ATTR;
	return(0);
}
/*
 *	Close doesn't de-allocate any resources, since
 *	slots may be assigned to drivers already.
 */
int
crdclose(dev_t dev, int fflag, int devtype, struct proc *p)
{
	return(0);
}
/*
 *	read interface. Map memory at lseek offset,
 *	then transfer to user space.
 */
int
crdread(dev_t dev, struct uio *uio, int ioflag)
{
struct slot *sp = pccard_slots[minor(dev)];
unsigned char *p;
int	error = 0, win, count;
struct mem_desc *mp, oldmap;
unsigned int offs;

	if (sp == 0 || sp->state != filled)
		return(ENXIO);
	if (pccard_mem == 0)
		return(ENOMEM);
	for (win = 0; win < sp->cinfo->maxmem; win++)
		if ((sp->mem[win].flags & MDF_ACTIVE)==0)
			break;
	if (win >= sp->cinfo->maxmem)
		return(EBUSY);
	mp = &sp->mem[win];
	oldmap = *mp;
	mp->flags = sp->rwmem|MDF_ACTIVE;
#if 0
	printf("Rd at offs %d, size %d\n", (int)uio->uio_offset,
				uio->uio_resid);
#endif
	while (uio->uio_resid && error == 0)
		{
		mp->card = uio->uio_offset;
		mp->size = PCCARD_MEMSIZE;
		mp->start = (caddr_t)pccard_mem;
		if (error = sp->cinfo->mapmem(sp, win))
			break;
		offs = (unsigned int)uio->uio_offset & (PCCARD_MEMSIZE - 1);
		p = pccard_kmem + offs;
		count = MIN(PCCARD_MEMSIZE - offs, uio->uio_resid);
		error = uiomove(p, count, uio);
		}
/*
 *	Restore original map.
 */
	*mp = oldmap;
	sp->cinfo->mapmem(sp, win);

	return(error);
}
/*
 *	crdwrite - Write data to card memory.
 *	Handles wrap around so that only one memory
 *	window is used.
 */
int
crdwrite(dev_t dev, struct uio *uio, int ioflag)
{
struct slot *sp = pccard_slots[minor(dev)];
unsigned char *p, c;
int	error = 0, win, count;
struct mem_desc *mp, oldmap;
unsigned int offs;

	if (sp == 0 || sp->state != filled)
		return(ENXIO);
	if (pccard_mem == 0)
		return(ENOMEM);
	for (win = 0; win < sp->cinfo->maxmem; win++)
		if ((sp->mem[win].flags & MDF_ACTIVE)==0)
			break;
	if (win >= sp->cinfo->maxmem)
		return(EBUSY);
	mp = &sp->mem[win];
	oldmap = *mp;
	mp->flags = sp->rwmem|MDF_ACTIVE;
#if 0
	printf("Wr at offs %d, size %d\n", (int)uio->uio_offset,
				uio->uio_resid);
#endif
	while (uio->uio_resid && error == 0)
		{
		mp->card = uio->uio_offset;
		mp->size = PCCARD_MEMSIZE;
		mp->start = (caddr_t)pccard_mem;
		if (error = sp->cinfo->mapmem(sp, win))
			break;
		offs = (unsigned int)uio->uio_offset & (PCCARD_MEMSIZE - 1);
		p = pccard_kmem + offs;
		count = MIN(PCCARD_MEMSIZE - offs, uio->uio_resid);
#if 0
	printf("Writing %d bytes to address 0x%x\n", count, p);
#endif
		error = uiomove(p, count, uio);
		}
/*
 *	Restore original map.
 */
	*mp = oldmap;
	sp->cinfo->mapmem(sp, win);

	return(error);
}
/*
 *	ioctl calls - allows setting/getting of memory and I/O
 *	descriptors, and assignment of drivers.
 */
int
crdioctl(dev_t dev, int cmd, caddr_t data, int fflag, struct proc *p)
{
int	s;
struct slot *sp = pccard_slots[minor(dev)];
struct mem_desc *mp;
struct io_desc *ip;

	if (sp == 0 && cmd != PIOCRWMEM)
		return(ENXIO);
	switch(cmd)
		{
	default:
		if (sp->cinfo->ioctl)
			return(sp->cinfo->ioctl(sp, cmd, data));
		return(EINVAL);
	case PIOCGSTATE:
		s = splhigh();
		((struct slotstate *)data)->state = sp->state;
		sp->laststate = sp->state;
		splx(s);
		((struct slotstate *)data)->maxmem = sp->cinfo->maxmem;
		((struct slotstate *)data)->maxio = sp->cinfo->maxio;
		((struct slotstate *)data)->irqs = sp->cinfo->irqs;
		break;
/*
 * Get memory context.
 */
	case PIOCGMEM:
		s = ((struct mem_desc *)data)->window;
		if (s < 0 || s >= sp->cinfo->maxmem)
			return(EINVAL);
		mp = &sp->mem[s];
		((struct mem_desc *)data)->flags = mp->flags;
		((struct mem_desc *)data)->start = mp->start;
		((struct mem_desc *)data)->size = mp->size;
		((struct mem_desc *)data)->card = mp->card;
		break;
/*
 * Set memory context. If context already active, then unmap it.
 * It is hard to see how the parameters can be checked.
 * At the very least, we only allow root to set the context.
 */
	case PIOCSMEM:
		if (suser(p->p_ucred, &p->p_acflag))
			return(EPERM);
		if (sp->state != filled)
			return(ENXIO);
		s = ((struct mem_desc *)data)->window;
		if (s < 0 || s >= sp->cinfo->maxmem)
			return(EINVAL);
		sp->mem[s] = *((struct mem_desc *)data);
		return(sp->cinfo->mapmem(sp, s));
/*
 * Get I/O port context.
 */
	case PIOCGIO:
		s = ((struct io_desc *)data)->window;
		if (s < 0 || s >= sp->cinfo->maxio)
			return(EINVAL);
		ip = &sp->io[s];
		((struct io_desc *)data)->flags = ip->flags;
		((struct io_desc *)data)->start = ip->start;
		((struct io_desc *)data)->size = ip->size;
		break;
/*
 * Set I/O port context.
 */
	case PIOCSIO:
		if (suser(p->p_ucred, &p->p_acflag))
			return(EPERM);
		if (sp->state != filled)
			return(ENXIO);
		s = ((struct io_desc *)data)->window;
		if (s < 0 || s >= sp->cinfo->maxio)
			return(EINVAL);
		sp->io[s] = *((struct io_desc *)data);
		return(sp->cinfo->mapio(sp, s));
		break;
/*
 *	Set memory window flags for read/write interface.
 */
	case PIOCRWFLAG:
		sp->rwmem = *(int *)data;
		break;
/*
 *	Set the memory window to be used for the read/write
 *	interface.
 */
	case PIOCRWMEM:
		if (*(unsigned long *)data == 0)
			{
			if (pccard_mem)
				*(unsigned long *)data = pccard_mem;
			break;
			}
		if (suser(p->p_ucred, &p->p_acflag))
			return(EPERM);
/*
 *	Validate the memory by checking it against the
 *	I/O memory range. It must also start on an aligned block size.
 */
		if (invalid_io_memory(*(unsigned long *)data, PCCARD_MEMSIZE))
			return(EINVAL);
		if (*(unsigned long *)data & (PCCARD_MEMSIZE-1))
			return(EINVAL);
/*
 *	Map it to kernel VM.
 */
		pccard_mem = *(unsigned long *)data;
		pccard_kmem = (unsigned char *)(pccard_mem
				+ atdevbase - 0xA0000);
		break;
/*
 *	Set power values
 */
	case PIOCSPOW:
		sp->pwr = *(struct power *)data;
		return(sp->cinfo->power(sp));
/*
 *	Allocate a driver to this slot.
 */
	case PIOCSDRV:
		if (suser(p->p_ucred, &p->p_acflag))
			return(EPERM);
		return(allocate_driver(sp, (struct drv_desc *)data));
		}
	return(0);
}
/*
 *	select - Selects on exceptions will return true
 *	when a change in card status occurs.
 */
int
crdselect(dev_t dev, int rw, struct proc *p)
{
int s;
struct slot *sp = pccard_slots[minor(dev)];

	switch (rw) {
	case FREAD:
		return 1;
	case FWRITE:
		return 1;
/*
 *	select for exception - card event.
 */
	case 0:
		s = splhigh();
		if (sp == 0 || sp->laststate != sp->state)
			{
			splx(s);
			return(1);
			}
		selrecord(p, &sp->selp);
		splx(s);
	}
	return(0);
}
/*
 *	invalid_io_memory - verify that the ISA I/O memory block
 *	is a valid and unallocated address.
 *	A simple check of the range is done, and then a
 *	search of the current devices is done to check for
 *	overlapping regions.
 */
static int
invalid_io_memory(unsigned long adr, int size)
{
	if (adr < 0xC0000 || (adr+size) > 0x100000)
		return(1);
	return(0);
}
static struct pccard_drv *
find_driver(char *name)
{
struct pccard_drv *dp;

	for (dp = drivers; dp; dp = dp->next)
		if (strcmp(dp->name, name)==0)
			return(dp);
	return(0);
}

#endif /* NCRD */