Goto sanos source index

//
// pnpbios.c
//
// PnP BIOS
//
// Copyright (C) 2002 Michael Ringgaard. 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. Neither the name of the project nor the names of its contributors
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission. 
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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 <os/krnl.h>

#pragma warning(disable: 4731)

struct pnp_bios_expansion_header pnpbios;
struct fullptr pnp_thunk_entrypoint;

struct {
  unsigned char type_code[3];
  char *name;
} pnp_typecodes[] = {
  {{0x01, 0x00, 0x00}, "SCSI controller"},
  {{0x01, 0x01, 0x00}, "IDE controller"},
  {{0x01, 0x02, 0x00}, "Floppy disk controller"},
  {{0x01, 0x03, 0x00}, "IPI controller"},
  {{0x01, 0x80, 0x00}, "Mass storage controller"},
  
  {{0x02, 0x00, 0x00}, "Ethernet controller"},
  {{0x02, 0x01, 0x00}, "Token Ring network controller"},
  {{0x02, 0x02, 0x00}, "FDDI network controller"},
  {{0x02, 0x80, 0x00}, "Network controller"},

  {{0x03, 0x00, 0x00}, "VGA controller"},
  {{0x03, 0x00, 0x01}, "VESA SVGA controller"},
  {{0x03, 0x01, 0x00}, "XGA controller"},
  {{0x03, 0x80, 0x00}, "Display controller"},
  
  {{0x04, 0x00, 0x00}, "Video controller"},
  {{0x04, 0x01, 0x00}, "Audio controller"},
  {{0x04, 0x80, 0x00}, "Multi-media controller"},
  
  {{0x05, 0x00, 0x00}, "RAM memory"},
  {{0x05, 0x01, 0x00}, "Flash memory"},
  {{0x05, 0x80, 0x00}, "Memory"},

  {{0x06, 0x00, 0x00}, "Host bridge"},
  {{0x06, 0x01, 0x00}, "ISA bridge"},
  {{0x06, 0x02, 0x00}, "EISA bridge"},
  {{0x06, 0x03, 0x00}, "MicroChannel bridge"},
  {{0x06, 0x04, 0x00}, "PCI bridge"},
  {{0x06, 0x05, 0x00}, "PCMCIA bridge"},
  {{0x06, 0x80, 0x00}, "Bridge"},
 
  {{0x07, 0x00, 0x00}, "RS-232 port"},
  {{0x07, 0x00, 0x01}, "RS-232 port (16450-compatible)"},
  {{0x07, 0x00, 0x02}, "RS-232 port (16550-compatible)"},
  {{0x07, 0x01, 0x00}, "Parallel port"},
  {{0x07, 0x01, 0x01}, "Bidirectional parallel port"},
  {{0x07, 0x01, 0x02}, "ECP parallel port"},
  {{0x07, 0x80, 0x00}, "Communication device"},
  
  {{0x08, 0x00, 0x00}, "8259 PIC"},
  {{0x08, 0x00, 0x01}, "ISA PIC"},
  {{0x08, 0x00, 0x02}, "EISA PIC"},
  {{0x08, 0x01, 0x00}, "DMA controller"},
  {{0x08, 0x01, 0x01}, "ISA DMA controller"},
  {{0x08, 0x01, 0x02}, "EISA DMA controller"},
  {{0x08, 0x02, 0x00}, "System timer"},
  {{0x08, 0x02, 0x01}, "ISA system timer"},
  {{0x08, 0x02, 0x02}, "EISA system timer"},
  {{0x08, 0x03, 0x00}, "Real time tlock"},
  {{0x08, 0x03, 0x01}, "ISA real time clock"},
  {{0x08, 0x80, 0x00}, "System peripheral"},
  {{0x08, 0x80, 0xFF}, "System board"},

  {{0x09, 0x00, 0x00}, "Keyboard controller"},
  {{0x09, 0x01, 0x00}, "Digitizer pen"},
  {{0x09, 0x02, 0x00}, "Mouse controller"},
  {{0x09, 0x80, 0x00}, "Input device"},

  {{0x0A, 0x00, 0x00}, "Docking station"},
  {{0x0A, 0x80, 0x00}, "Docking station"},
  
  {{0x0B, 0x00, 0x00}, "386-based processor"},
  {{0x0B, 0x01, 0x00}, "486-based processor"},
  {{0x0B, 0x02, 0x00}, "Pentium-based processor"},
  {{0x0B, 0x80, 0x00}, "Processsor"},

  {{0xFF, 0xFF, 0xFF}, NULL},
};

static unsigned char pnp_bios_thunk[] = {
  0x52,                // push edx
  0x51,                // push ecx
  0x53,                // push ebx
  0x50,                // push eax
  0x66, 0x9A, 0,0,0,0, // call word pnpseg:pnpofs
  0x83, 0xC4, 0x10,    // add  esp, 16
  0xCB                 // retf 0
};

static int pnp_bios_call(int func, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7) {
  int status;

  __asm {
    CLI
    push ebp
    push edi
    push esi
    push ds
    push es
    push fs
    push gs
    pushf

    mov eax, [arg1]
    shl eax, 16
    or eax, [func]

    mov ebx, [arg3]
    shl ebx, 16
    or ebx, [arg2]

    mov ecx, [arg5]
    shl ecx, 16
    or ecx, [arg4]

    mov edx, [arg7]
    shl edx, 16
    or edx, [arg6]

    call fword ptr [pnp_thunk_entrypoint]

    popf
    pop gs
    pop fs
    pop es
    pop ds
    pop esi
    pop edi
    pop ebp

    STI
    and eax, 0x0000FFFF
    mov [status], eax
  }

  return status;
}

//
// Call PnP BIOS with function 0x00, "Get number of system device nodes"
//

static int pnp_bios_dev_node_info(struct pnp_dev_node_info *data) {
  int status;

  set_gdt_entry(GDT_AUX1, (unsigned long) data, sizeof(struct pnp_dev_node_info), D_DATA | D_DPL0 | D_WRITE | D_PRESENT, 0);

  status = pnp_bios_call(PNP_GET_NUM_SYS_DEV_NODES, 0, SEL_AUX1, 2, SEL_AUX1, SEL_PNPDATA, 0, 0);
  data->no_nodes &= 0xFF;
  return status;
}

// 
// Call PnP BIOS with function 0x01, "Get system device node"
//
// Input:
//  *nodenum = desired node, 
//  boot = whether to get nonvolatile boot (!=0) or volatile current (0) config
//
// Output: 
//   *nodenum=next node or 0xff if no more nodes
//

static int pnp_bios_get_dev_node(unsigned char *nodenum, char boot, struct pnp_bios_node *data) {
  int status;

  set_gdt_entry(GDT_AUX1, (unsigned long) nodenum, sizeof(char), D_DATA | D_DPL0 | D_WRITE | D_PRESENT, 0);
  set_gdt_entry(GDT_AUX2, (unsigned long) data, 64 * 1024, D_DATA | D_DPL0 | D_WRITE | D_PRESENT, 0);

  status = pnp_bios_call(PNP_GET_SYS_DEV_NODE, 0, SEL_AUX1, 0, SEL_AUX2, boot ? 2 : 1, SEL_PNPDATA, 0);
  return status;
}

//
// Call PnP BIOS with function 0x40, "Get isa pnp configuration structure"
//

static int pnp_bios_isapnp_config(struct pnp_isa_config_struc *data) {
  int status;

  set_gdt_entry(GDT_AUX1, (unsigned long) data, sizeof(struct pnp_isa_config_struc), D_DATA | D_DPL0 | D_WRITE | D_PRESENT, 0);

  status = pnp_bios_call(PNP_GET_PNP_ISA_CONFIG_STRUC, 0, SEL_AUX1, SEL_PNPDATA, 0, 0, 0, 0);
  return status;
}

//
// Call PnP BIOS with function 0x41, "Get ESCD info"
//

static int pnp_bios_escd_info(struct escd_info_struc *data) {
  int status;

  set_gdt_entry(GDT_AUX1, (unsigned long) data, sizeof(struct escd_info_struc), D_DATA | D_DPL0 | D_WRITE | D_PRESENT, 0);

  status = pnp_bios_call(PNP_GET_ESCD_INFO, 0, SEL_AUX1, 2, SEL_AUX1, 4, SEL_AUX1, SEL_PNPDATA);
  return status;
}

//
// Call PnP BIOS function 0x42, "read ESCD"
// nvram_base is determined by calling escd_info
//

static int pnp_bios_read_escd(void *data, void *nvram_base) {
  int status;

  set_gdt_entry(GDT_AUX1, (unsigned long) data, 64 * 1024, D_DATA | D_DPL0 | D_WRITE | D_PRESENT, 0);
  set_gdt_entry(GDT_AUX2, (unsigned long) nvram_base, 64 * 1024, D_DATA | D_DPL0 | D_WRITE | D_PRESENT, 0);

  status = pnp_bios_call(PNP_READ_ESCD, 0, SEL_AUX1, SEL_AUX2, SEL_PNPDATA, 0, 0, 0);
  return status;
}

static void pnpid32_to_pnpid(unsigned long id, char *str) {
  const char *hex = "0123456789ABCDEF";

  id = ntohl(id);
  str[0] = '@' + (char) ((id >> 26) & 0x1F);
  str[1] = '@' + (char) ((id >> 21) & 0x1F);
  str[2] = '@' + (char) ((id >> 16) & 0x1F);
  str[3] = hex[(id >> 12) & 0x0F];
  str[4] = hex[(id >> 8) & 0x0F];
  str[5] = hex[(id >> 4) & 0x0F];
  str[6] = hex[(id >> 0) & 0x0F];
  str[7] = '\0';

  return;
}

static char *get_device_type_name(unsigned char type_code[3]) {
  int i = 0;

  while (pnp_typecodes[i].name != NULL) {
    if (pnp_typecodes[i].type_code[0] == type_code[0] &&
        pnp_typecodes[i].type_code[1] == type_code[1] &&
        pnp_typecodes[i].type_code[2] == type_code[2]) {
      return pnp_typecodes[i].name;
    }

    i++;
  }

  return "Unknown";
}

static void extract_node_resource_data(struct pnp_bios_node *node, struct unit *unit) {
  unsigned char *p = node->data;
  unsigned char *end = node->data + node->size;
  unsigned char *lastp = NULL;

  while (p < end)  {
    if(p == lastp) break;

    if(p[0] & 0x80)  {
      // Large item
      switch (p[0] & 0x7F) {
        case 0x01: { // Memory
          int addr = *(short *) &p[4];
          int len = *(short *) &p[10];
          add_resource(unit, RESOURCE_MEM, 0, addr, len);
          break;
        }
        
        case 0x02: { // Device name
          int len = *(short *) &p[1];
          unit->productname = (char *) kmalloc(len + 1);
          memcpy(unit->productname, p + 3, len);
          unit->productname[len] = 0;
          break;
        }

        case 0x05: { // 32-bit memory
          int addr = *(int *) &p[4];
          int len = *(int *) &p[16];
          add_resource(unit, RESOURCE_MEM, 0, addr, len);
          break;
        }

        case 0x06: { // Fixed location 32-bit memory
          int addr = *(int *) &p[4];
          int len = *(int *) &p[8];
          add_resource(unit, RESOURCE_MEM, 0, addr, len);
          break;
        }

        //default:
          //kprintf("tag:%x ", p[0]);
      }

      lastp = p + 3;
      p = p + p[1] + (p[2] << 8) + 3;
      continue;
    }

     // Test for end tag
    if ((p[0] >> 3) == 0x0F) break;
                  
    switch (p[0] >> 3) {
      case 0x04: { // IRQ
        int i, mask, irq = -1;
        mask = p[1] + (p[2] << 8);
        for (i = 0; i < 16; i++, mask = mask >> 1) if (mask & 0x01) irq = i;
        if (irq != -1) add_resource(unit, RESOURCE_IRQ, 0, irq, 1);
        break;
      }

      case 0x05: { // DMA
        int i, mask, dma = -1;
        mask = p[1];
        for (i = 0; i < 8;i++, mask = mask>>1) if (mask & 0x01) dma = i;
        if (dma != -1) add_resource(unit, RESOURCE_DMA, 0, dma, 1);
        break;
      }

      case 0x08: { // I/O
        int io = p[2] + (p[3] << 8);
        int len = p[7];
        if (len != 0) add_resource(unit, RESOURCE_IO, 0, io, len);
        break;
      }

      case 0x09: { // Fixed location io
        int io = p[1] + (p[2] << 8);
        int len = p[3];
        add_resource(unit, RESOURCE_IO, 0, io, len);
        break;
      }

      //default:
        //kprintf("tag:%x ", p[0]);
    }

    lastp = p + 1;
    p = p + (p[0] & 0x07) + 1;
  }
}

static void build_sys_devlist(struct bus *bus) {
  struct pnp_dev_node_info info;
  int status;
  int i;
  int nodenum = 0;
  int nodes_fetched = 0;
  struct pnp_bios_node *node;
  struct unit *unit;
  unsigned long classcode;
  unsigned long unitcode;

  status = pnp_bios_dev_node_info(&info);
  if (status != 0) return;

  node = kmalloc(info.max_node_size);
  if (!node) return;

  for (i = 0; i < 0xFF && nodenum != 0xFF; i++)  {
    int thisnodenum = nodenum;

    if (pnp_bios_get_dev_node((unsigned char *) &nodenum, (char) 0 /*1*/, node)) {
      kprintf(KERN_ERR  "pnpbios: PnP BIOS reported error on attempt to get dev node.\n");
      break;
    }

    // The BIOS returns with nodenum = the next node number
    if (nodenum < thisnodenum)  {
      kprintf("pnpbios: Node number is out of sequence.\n");
      break;
    }

    nodes_fetched++;

    classcode = (node->type_code[0] << 16) + (node->type_code[1] << 8) + node->type_code[0];
    unitcode = node->eisa_id;
    unit = add_unit(bus, classcode, unitcode, nodenum + 16);
    unit->classname = get_device_type_name(node->type_code);

    extract_node_resource_data(node, unit);
  }

  kfree(node);
}

int enum_pnp_mem(struct unit *unit, unsigned char *b) {       
  int i = 0;
  unsigned long int start, len, more = 1;

  //static char *datasizes[] = {"(byte)", "(word)", "(dword)", "(rsv)"};
  //static char *decodeszs[] = {"(20bit)", "(24bit)", "(32bit)", "(rsv)"};
  //static char *memtypes[] = {"(sys)", "(exp)", "(vir)", "(oth)"};

  while (more)  {
    unsigned int ram, cached, wt_wb_cache, shared, datasize, decodesz, memtyp;

    ram = b[i] & 0x01;
    cached = b[i] & 0x02;
    wt_wb_cache = b[i] & 0x04;
    memtyp = (b[i] >> 3) & 0x03;
    shared = b[i] & 0x10;
    // bit 6: reserved
    more = b[i] & 0x80;
    datasize = b[i + 1] & 0x03;
    decodesz =(b[i + 1] >> 2) & 0x03;
    // bit 4-7: reserved

    start = (b[i + 2] + b[i + 3] * 256 + b[i + 4] * 256 * 256) * 256;
    len = (b[i + 5] + b[i + 6] * 256) * 1024;
    if (len == 0) len = 64 * 1024 * 1024;

    add_resource(unit, RESOURCE_MEM, 0, start, len);
 
    i += 7;
  }

  return i;
}

int enum_pnp_irq(struct unit *unit, unsigned char *b) {
  int i = 0, more = 1;

  while (more) {
    int irq, shared, mode;

    more = b[i] & 0x80;
    irq = b[i] & 0x0f;
    shared = b[i] & 0x40;
    mode = b[i] & 0x20;
    // b[i] & 0x10 must be 0
    // b[i + 1] set to 0 (reserved)

    add_resource(unit, RESOURCE_IRQ, 0, irq, 1);

    i += 2;
  }

  return i;
}

int enum_pnp_dma(struct unit *unit, unsigned char *b) {
  int i = 0, more = 1;

  //static char *transfers[] = {"8bit","(16bit)","(32bit)","(16bit w/count)"};
  //static char *timings[] = {"ISA", "(Type A)", "(Type B)", "(Type C)"};

  while (more) {
    int dma, shared, timing, transfer;

    more = b[i] & 0x80;
    dma = b[i] & 0x07;
    shared = b[i] & 0x40;
    // b[i] & 0x38 must be 0
    transfer = (b[i + 1] >> 2) & 0x03;
    timing = (b[i + 1] >> 4) & 0x03;
    // b[i + 2] & 0xc3 set to 0 (reserved)
    
    add_resource(unit, RESOURCE_DMA, 0, dma, 1);

    i += 2;
  }

  return i;
}

int enum_pnp_io(struct unit *unit, unsigned char *b) {
  int i = 0,more = 1;

  while (more) {
    unsigned int io, len, shared;

    more = b[i] & 0x80;
    len = (b[i] & 0x1f) + 1;
    shared = b[i] & 0x40;
    // b[i] & 0x20 set 0 (reserved)
    io = b[i + 1] + b[i + 2] * 256;

    add_resource(unit, RESOURCE_IO, 0, io, len);

    i += 3;
  }

  return i;
}

static int enum_pnp_board(struct bus *bus, unsigned char *b) {
  int size, len, off, type, o, slot, unitcode;
  struct unit *unit;

  size = b[0] + 256 * b[1];
  if (size == 0) return 0;

  slot = b[2];
  // b[3] reserved
  unitcode = *(unsigned long *) &b[4];
  // b[8] and b[9] probably EISASLOTINFO: 0x60 0x40 or 0x40 0x40 mostly ?!
  // b[10] and b[11] are 0

  // We are only interested in PnP ISA devices (slot 1 to 15)
  if (slot < 1 || slot > 15) return size;

  // Allocate new unit
  unit = add_unit(bus, 0, unitcode, slot);
  unit->classname = "PNPISA";

  // Enumerate resouces
  off = 12;
  while (off + 4 < size) {
    len = b[off];
    if (len == 0) break;
    type = b[off + 4];

    //kprintf(" Function (len=%d,type=0x%02x)\n", len, type);
    //if (b[off + 1] != 0) kprintf("escd: b[0x%x]=0x%x, expected 0\n", off + 1, b[off + 1]);
    //if (b[off + 2] != 1) kprintf("escd: b[0x%x]=0x%x, expected 1\n", off + 2, b[off + 2]);
    //if (b[off + 3] != 0) kprintf("escd: b[0x%x]=0x%x, expected 0\n", off + 3, b[off + 3]);
    
    if (type == 0xc0) {
      //showfreeform(&b[off + 4]);
      break;
    } else {
      o = off + 5;
      //if (type & 0x80) kprintf(" Device/Function disabled.\n");
      if (type & 0x02) o += enum_pnp_mem(unit, &b[o]);
      if (type & 0x04) o += enum_pnp_irq(unit, &b[o]);
      if (type & 0x08) o += enum_pnp_dma(unit, &b[o]);
      if (type & 0x10) o += enum_pnp_io(unit, &b[o]);
    }

    off += len + 2;
  }

  return size;
}

static void parse_escd(struct bus *bus, unsigned char *b, int maxlen) {
  int off, len, size, major, minor, brdcnt, i;

  if (memcmp("ACFG", &b[2], 4) != 0)  {
    kprintf(KERN_WARNING "escd: signature does not match ACFG, is: %4s\n", &b[2]);
    return;
  }

  size = b[0] + b[1] * 256;
  minor = b[6];
  major = b[7];
  brdcnt = b[8];

  //kprintf("escd: size=%d, version=%d.%d, boardcount=%d\n", size, major, minor, brdcnt);

  off = 12;
  for (i = 0; i < brdcnt; i++) {
    len = enum_pnp_board(bus, &b[off]);
    off += len;
    if (off > size) {
      kprintf(KERN_WARNING "escd: offset exceeded size (%d)\n", off);
      break;
    }
  }
}

static int build_isa_devlist(struct bus *bus) {
  int rc;
  struct pnp_isa_config_struc isacfg;
  struct escd_info_struc escdinfo;
  unsigned char *escd;
  void *nvbase;

  rc = pnp_bios_isapnp_config(&isacfg);
  if (rc != 0) return rc;

  rc = pnp_bios_escd_info(&escdinfo);
  if (rc != 0) return rc;

  escd = kmalloc(escdinfo.escd_size);
  if (!escd) return -ENOMEM;

  nvbase = iomap(escdinfo.nv_storage_base, 64 * 1024);
  if (!nvbase) return -EINVAL;

  rc = pnp_bios_read_escd(escd, nvbase);
  if (rc != 0) return rc;

  parse_escd(bus, escd, escdinfo.escd_size);

  iounmap(nvbase, 64 * 1024);
  kfree(escd);

  return 0;
}

int enum_isapnp(struct bus *bus) {
  int ofs;
  struct pnp_bios_expansion_header *hdr;
  unsigned char checksum;
  int i, length;

  // Map first 1MB physical memory
  for (i = 0; i < 256; i++) map_page((void *) PTOB(i), i, PT_WRITABLE | PT_PRESENT);

  // Search the defined area (0xf0000-0xffff0) for a valid PnP BIOS
  // structure and, if one is found, sets up the selectors and
  // entry points
  for (ofs = 0xF0000; ofs < 0xFFFF0; ofs += 16) {
    hdr = (struct pnp_bios_expansion_header *) ofs;

    if (hdr->signature != PNP_SIGNATURE) continue;
    length = hdr->length;
    if (!length) continue;

    checksum = 0;
    for (i = 0; i < length; i++) checksum += ((unsigned char *) hdr)[i];
    if (checksum) continue;

    //kprintf("pnpbios: PnP BIOS version %d.%d\n", hdr->version >> 4, hdr->version & 0x0F);

    memcpy(&pnpbios, hdr, sizeof(struct pnp_bios_expansion_header));

    set_gdt_entry(GDT_PNPTEXT, pnpbios.pm16cseg, 64 * 1024, D_CODE | D_DPL0 | D_READ | D_PRESENT, 0);
    set_gdt_entry(GDT_PNPDATA, pnpbios.pm16dseg, 64 * 1024, D_DATA | D_DPL0 | D_WRITE | D_PRESENT, 0);
    set_gdt_entry(GDT_PNPTHUNK, (unsigned long) pnp_bios_thunk, 1, D_CODE | D_DPL0 | D_READ | D_PRESENT, D_BIG | D_BIG_LIM);

    *((unsigned short *)(pnp_bios_thunk + 6)) = pnpbios.pm16offset;
    *((unsigned short *)(pnp_bios_thunk + 8)) = SEL_PNPTEXT;

    pnp_thunk_entrypoint.segment = SEL_PNPTHUNK;
    pnp_thunk_entrypoint.offset = 0;

    build_sys_devlist(bus);
    build_isa_devlist(bus);

    set_gdt_entry(GDT_PNPTEXT, 0, 0, 0, 0);
    set_gdt_entry(GDT_PNPDATA, 0, 0, 0, 0);
    set_gdt_entry(GDT_PNPTHUNK, 0, 0, 0, 0);

    for (i = 0; i < 256; i++) unmap_page((void *) PTOB(i));
    return 1;
  }

  for (i = 0; i < 256; i++) unmap_page((void *) PTOB(i));
  return 0;
}

int __declspec(dllexport) isapnp(struct unit *unit) {
  struct bus *isabus;

  isabus = add_bus(unit, BUSTYPE_ISA, 0);
  return enum_isapnp(isabus);
}