freebsd-dev/sys/pccard/pccard.c

1062 lines
25 KiB
C
Raw Normal View History

/*
* 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 <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/fcntl.h>
#include <sys/proc.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>
#include <sys/conf.h>
#ifdef DEVFS
#include <sys/devfsext.h>
#endif /*DEVFS*/
#include <sys/uio.h>
1997-09-14 03:19:42 +00:00
#include <sys/poll.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 */
1995-11-14 20:49:24 +00:00
#include <pccard/card.h>
#include <pccard/driver.h>
1995-11-14 20:49:24 +00:00
#include <pccard/slot.h>
#include <machine/clock.h>
#include <machine/md_var.h>
SYSCTL_NODE(_machdep, OID_AUTO, pccard, CTLFLAG_RW, 0, "pccard");
static int pcic_resume_reset =
#ifdef PCIC_RESUME_RESET
1;
#else
0;
#endif
SYSCTL_INT(_machdep_pccard, OID_AUTO, pcic_resume_reset, CTLFLAG_RW,
&pcic_resume_reset, 0, "");
#define PCCARD_MEMSIZE (4*1024)
#define MIN(a,b) ((a)<(b)?(a):(b))
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);
static void power_off_slot(void *);
#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(void *arg);
static int slot_resume(void *arg);
static struct apmhook s_hook[MAXSLOT]; /* APM suspend */
static struct apmhook r_hook[MAXSLOT]; /* APM resume */
#endif /* NAPM > 0 */
static struct slot *pccard_slots[MAXSLOT]; /* slot entries */
static struct slot *slot_list;
static struct slot_ctrl *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 */
static d_open_t crdopen;
static d_close_t crdclose;
static d_read_t crdread;
static d_write_t crdwrite;
static d_ioctl_t crdioctl;
1997-09-14 03:19:42 +00:00
static d_poll_t crdpoll;
#define CDEV_MAJOR 50
static struct cdevsw crd_cdevsw =
{ crdopen, crdclose, crdread, crdwrite, /*50*/
crdioctl, nostop, nullreset, nodevtotty,/* pcmcia */
1997-09-14 03:19:42 +00:00
crdpoll, nommap, NULL, "crd", NULL, -1 };
/*
* 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()
{
#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;
}
printf("pccard driver %s added\n", dp->name);
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_ctrl *cp)
{
struct slot *sp, *next, *last = 0;
struct slot_ctrl *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->ctrl == cp) {
pccard_slots[sp->slot] = 0;
if (sp->insert_seq)
1997-09-21 22:02:25 +00:00
untimeout(inserted, (void *)sp, sp->insert_ch);
/*
* 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;
}
}
/*
* Power off the slot.
* (doing it immediately makes the removal of some cards unstable)
*/
static void
power_off_slot(void *arg)
{
struct slot *sp = (struct slot *)arg;
sp->pwr_off_pending = 0;
/* Power off the slot. */
sp->ctrl->disable(sp);
}
/*
* 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) {
if (devp->running) {
int s = splhigh();
devp->drv->unload(devp);
devp->running = 0;
if (devp->isahd.id_irq && --sp->irqref == 0) {
printf("Return IRQ=%d\n",sp->irq);
sp->ctrl->mapirq(sp, 0);
INTRDIS(1<<sp->irq);
unregister_intr(sp->irq, slot_irq_handler);
if (devp->drv->imask)
INTRUNMASK(*devp->drv->imask,(1<<sp->irq));
/* Remove from the PCIC controller imask */
if (sp->ctrl->imask)
INTRUNMASK(*(sp->ctrl->imask), (1<<sp->irq));
sp->irq = 0;
}
splx(s);
}
}
/* Power off the slot 1/2 second after remove of the card */
1997-09-21 22:02:25 +00:00
sp->poff_ch = timeout(power_off_slot, (caddr_t)sp, hz / 2);
sp->pwr_off_pending = 1;
/* De-activate all contexts. */
for (i = 0; i < sp->ctrl->maxmem; i++)
if (sp->mem[i].flags & MDF_ACTIVE) {
sp->mem[i].flags = 0;
(void)sp->ctrl->mapmem(sp, i);
}
for (i = 0; i < sp->ctrl->maxio; i++)
if (sp->io[i].flags & IODF_ACTIVE) {
sp->io[i].flags = 0;
(void)sp->ctrl->mapio(sp, i);
}
}
/*
* APM hooks for suspending and resuming.
*/
#if NAPM > 0
static int
slot_suspend(void *arg)
{
struct slot *sp = arg;
struct pccard_dev *dp;
for (dp = sp->devices; dp; dp = dp->next)
(void) dp->drv->suspend(dp);
if (!sp->suspend_power)
sp->ctrl->disable(sp);
return (0);
}
static int
slot_resume(void *arg)
{
struct slot *sp = arg;
struct pccard_dev *dp;
if (pcic_resume_reset)
sp->ctrl->resume(sp);
if (!sp->suspend_power)
sp->ctrl->power(sp);
if (sp->irq)
sp->ctrl->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_ctrl *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));
1996-01-28 10:02:38 +00:00
#ifdef DEVFS
sp->devfs_token = devfs_add_devswf( &crd_cdevsw,
0, DV_CHR, 0, 0, 0600, "card%d", slotno);
#endif
if (cp->extra) {
MALLOC(sp->cdata, void *, cp->extra, M_DEVBUF, M_WAITOK);
bzero(sp->cdata, cp->extra);
}
sp->ctrl = 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(u_int imask, inthand2_t *hand, int unit,
u_int *maskp, u_int *pcic_imask)
{
1996-01-28 10:02:38 +00:00
int irq;
unsigned int mask;
for (irq = ICU_LEN; irq > 0; irq--) {
mask = 1ul << irq;
if (!(mask & imask))
continue;
1995-10-10 09:52:55 +00:00
if (maskp)
INTRMASK (*maskp, mask);
if (register_intr(irq, 0, 0, hand, maskp, unit)==0) {
/* add this to the PCIC controller's mask */
INTRMASK(*pcic_imask, (1 << irq));
INTREN (mask);
return(irq);
}
/* Well no luck, remove from mask again... */
if (maskp)
INTRUNMASK (*maskp, mask);
}
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->ctrl->irqs & drvp->irqmask)==0)
return(EINVAL);
if (sp->irq) {
if (((1 << sp->irq)&drvp->irqmask)==0)
return(EINVAL);
sp->irqref++;
irq = sp->irq;
} else {
/*
* Attempt to allocate an interrupt.
* XXX We lose at the moment if the second
* device relies on a different interrupt mask.
*/
irq = pccard_alloc_intr(drvp->irqmask,
slot_irq_handler, (int)sp,
dp->imask, sp->ctrl->imask);
if (irq < 0)
return(EINVAL);
sp->irq = irq;
sp->irqref = 1;
sp->ctrl->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_irq = irq;
devp->isahd.id_unit = drvp->unit;
devp->isahd.id_msize = drvp->memsize;
devp->isahd.id_iobase = drvp->iobase;
bcopy(drvp->misc, devp->misc, sizeof drvp->misc);
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 *devp)
{
struct slot *sp = devp->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 (devp->running) {
devp->drv->unload(devp);
devp->running = 0;
}
if (devp->isahd.id_irq && --sp->irqref == 0) {
printf("Return IRQ=%d\n",sp->irq);
sp->ctrl->mapirq(sp, 0);
INTRDIS(1<<sp->irq);
unregister_intr(sp->irq, slot_irq_handler);
if (devp->drv->imask)
INTRUNMASK(*devp->drv->imask,(1<<sp->irq));
/* Remove from PCIC controller imask */
if (sp->ctrl->imask)
INTRUNMASK(*(sp->ctrl->imask),(1<<sp->irq));
sp->irq = 0;
}
splx(s);
/*
* Remove from device list on this slot.
*/
if (sp->devices == devp)
sp->devices = devp->next;
else
for (list = sp->devices; list->next; list = list->next)
if (list->next == devp) {
list->next = devp->next;
break;
}
/*
* Finally, free the memory space.
*/
FREE(devp, M_DEVBUF);
}
/*
* card insert routine - Called from a timeout to debounce
* insertion events.
*/
static void
inserted(void *arg)
{
struct slot *sp = arg;
sp->state = filled;
/*
* Enable 5V to the card so that the CIS can be read.
*/
sp->pwr.vcc = 50;
sp->pwr.vpp = 0;
1997-09-21 22:02:25 +00:00
if (sp->pwr_off_pending) {
untimeout(power_off_slot, (caddr_t)sp, sp->poff_ch);
sp->ctrl->disable(sp);
1997-09-21 22:02:25 +00:00
}
sp->pwr_off_pending = 0;
sp->ctrl->power(sp);
printf("Card inserted, slot %d\n", sp->slot);
/*
* Now start resetting the card.
*/
sp->ctrl->reset(sp);
#if NAPM > 0
sp->suspend_power = 0;
#endif
}
/*
* Insert/Remove beep
*/
static int beepok = 0;
static void enable_beep(void *dummy)
{
beepok = 1;
}
/*
* 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_seq) {
sp->insert_seq = 0;
1997-09-21 22:02:25 +00:00
untimeout(inserted, (void *)sp, sp->insert_ch);
}
switch(event) {
case card_removed:
/*
* The slot and devices are disabled, but the
* data structures are not unlinked.
*/
if (sp->state == filled) {
s = splhigh();
disable_slot(sp);
sp->state = empty;
splx(s);
printf("Card removed, slot %d\n", sp->slot);
if (beepok)
sysbeep(PCCARD_BEEP_PITCH0, PCCARD_BEEP_DURATION0);
beepok = 0;
timeout(enable_beep, (void *)NULL, hz/5);
selwakeup(&sp->selp);
}
break;
case card_inserted:
sp->insert_seq = 1;
1997-09-21 22:02:25 +00:00
sp->insert_ch = timeout(inserted, (void *)sp, hz/4);
if (beepok)
sysbeep(PCCARD_BEEP_PITCH0, PCCARD_BEEP_DURATION0);
beepok = 0;
timeout(enable_beep, (void *)NULL, hz/5);
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.
*/
static 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.
*/
static 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.
*/
static 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->ctrl->maxmem; win++)
if ((sp->mem[win].flags & MDF_ACTIVE)==0)
break;
if (win >= sp->ctrl->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->ctrl->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->ctrl->mapmem(sp, win);
return(error);
}
/*
* crdwrite - Write data to card memory.
* Handles wrap around so that only one memory
* window is used.
*/
static int
crdwrite(dev_t dev, struct uio *uio, int ioflag)
{
1996-01-28 10:02:38 +00:00
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->ctrl->maxmem; win++)
if ((sp->mem[win].flags & MDF_ACTIVE)==0)
break;
if (win >= sp->ctrl->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->ctrl->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->ctrl->mapmem(sp, win);
return(error);
}
/*
* ioctl calls - allows setting/getting of memory and I/O
* descriptors, and assignment of drivers.
*/
static int
crdioctl(dev_t dev, int cmd, caddr_t data, int fflag, struct proc *p)
{
int s, err;
struct slot *sp = pccard_slots[minor(dev)];
struct mem_desc *mp;
struct io_desc *ip;
/* beep is disabled until the 1st call of crdioctl() */
enable_beep(NULL);
if (sp == 0 && cmd != PIOCRWMEM)
return(ENXIO);
switch(cmd) {
default:
if (sp->ctrl->ioctl)
return(sp->ctrl->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->ctrl->maxmem;
((struct slotstate *)data)->maxio = sp->ctrl->maxio;
((struct slotstate *)data)->irqs = sp->ctrl->irqs;
break;
/*
* Get memory context.
*/
case PIOCGMEM:
s = ((struct mem_desc *)data)->window;
if (s < 0 || s >= sp->ctrl->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->ctrl->maxmem)
return(EINVAL);
sp->mem[s] = *((struct mem_desc *)data);
return(sp->ctrl->mapmem(sp, s));
/*
* Get I/O port context.
*/
case PIOCGIO:
s = ((struct io_desc *)data)->window;
if (s < 0 || s >= sp->ctrl->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->ctrl->maxio)
return(EINVAL);
sp->io[s] = *((struct io_desc *)data);
return(sp->ctrl->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->ctrl->power(sp));
/*
* Allocate a driver to this slot.
*/
case PIOCSDRV:
if (suser(p->p_ucred, &p->p_acflag))
return(EPERM);
err = allocate_driver(sp, (struct drv_desc *)data);
if (!err)
sysbeep(PCCARD_BEEP_PITCH1, PCCARD_BEEP_DURATION1);
else
sysbeep(PCCARD_BEEP_PITCH2, PCCARD_BEEP_DURATION2);
return err;
}
return(0);
}
/*
1997-09-14 03:19:42 +00:00
* poll - Poll on exceptions will return true
* when a change in card status occurs.
*/
static int
1997-09-14 03:19:42 +00:00
crdpoll(dev_t dev, int events, struct proc *p)
{
int s;
struct slot *sp = pccard_slots[minor(dev)];
1997-09-14 03:19:42 +00:00
int revents = 0;
1997-09-14 03:19:42 +00:00
if (events & (POLLIN | POLLRDNORM))
revents |= events & (POLLIN | POLLRDNORM);
if (events & (POLLOUT | POLLWRNORM))
revents |= events & (POLLIN | POLLRDNORM);
s = splhigh();
/*
* select for exception - card event.
*/
1997-09-14 03:19:42 +00:00
if (events & POLLRDBAND)
if (sp == 0 || sp->laststate != sp->state)
revents |= POLLRDBAND;
if (revents == 0)
selrecord(p, &sp->selp);
1997-09-14 03:19:42 +00:00
splx(s);
return (revents);
}
/*
* 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);
}
static crd_devsw_installed = 0;
static void
crd_drvinit(void *unused)
{
dev_t dev;
if( ! crd_devsw_installed ) {
dev = makedev(CDEV_MAJOR, 0);
cdevsw_add(&dev,&crd_cdevsw, NULL);
crd_devsw_installed = 1;
}
}
SYSINIT(crddev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+CDEV_MAJOR,crd_drvinit,NULL)