Goto sanos source index

//
// dev.c
//
// Device Manager
//
// 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>

struct unit *units;
struct bus *buses;

struct binding *bindtab;
int num_bindings;

struct dev *devtab[MAX_DEVS];
unsigned int num_devs = 0;

static int units_proc(struct proc_file *pf, void *arg);
static int devices_proc(struct proc_file *pf, void *arg);
static int devstat_proc(struct proc_file *pf, void *arg);

static char *busnames[] = {"HOST", "PCI", "ISA", "?", "?"};
static char *devtypenames[] = {"?", "stream", "block", "packet"};

struct bus *add_bus(struct unit *self, unsigned long bustype, unsigned long busno) {
  struct bus *bus;
  struct bus *b;

  // Create new bus
  bus = (struct bus *) kmalloc(sizeof(struct bus));
  if (!bus) return NULL;
  memset(bus, 0, sizeof(struct bus));

  bus->self = self;
  if (self) bus->parent = self->bus;
  bus->bustype = bustype;
  bus->busno = busno;

  // Add bus as a bridge on the parent bus
  if (bus->parent) {
    if (bus->parent->bridges) {
      b = bus->parent->bridges;
      while (b->sibling) b = b->sibling;
      b->sibling = bus;
    } else {
      bus->parent->bridges = bus;
    }
  }

  // Add bus to bus list
  if (buses) {
    b = buses;
    while (b->next) b = b->next;
    b->next = bus;
  } else {
    buses = bus;
  }

  return bus;
}

struct unit *add_unit(struct bus *bus, unsigned long classcode, unsigned long unitcode, unsigned long unitno) {
  struct unit *unit;
  struct unit *u;

  // Create new unit
  unit = (struct unit *) kmalloc(sizeof(struct unit));
  if (!unit) return NULL;
  memset(unit, 0, sizeof(struct unit));

  unit->bus = bus;
  unit->classcode = classcode;
  unit->unitcode = unitcode;
  unit->unitno = unitno;

  unit->classname = "";
  unit->vendorname = "";
  unit->productname = "";

  // Add unit to bus
  if (bus) {
    if (bus->units) {
      u = bus->units;
      while (u->sibling) u = u->sibling;
      u->sibling = unit;
    } else {
      bus->units = unit;
    }
  }

  // Add unit to unit list
  if (units) {
    u = units;
    while (u->next) u = u->next;
    u->next = unit;
  } else {
    units = unit;
  }

  return unit;
}

struct resource *add_resource(struct unit *unit, unsigned short type, unsigned short flags, unsigned long start, unsigned long len) {
  struct resource *res;
  struct resource *r;

  // Create new resource
  res = (struct resource *) kmalloc(sizeof(struct resource));
  if (!res) return NULL;
  memset(res, 0, sizeof(struct resource));

  res->type = type;
  res->flags = flags;
  res->start = start;
  res->len = len;

  // Add resource to unit resource list
  if (unit->resources) {
    r = unit->resources;
    while (r->next) r = r->next;
    r->next = res;
  } else {
    unit->resources = res;
  }

  return res;
}

struct resource *get_unit_resource(struct unit *unit, int type, int num) {
  struct resource *res = unit->resources;

  while (res) {
    if (res->type == type) {
      if (num == 0) return res;
      num--;
    }
    res = res->next;
  }

  return NULL;
}

int get_unit_irq(struct unit *unit) {
  struct resource *res = get_unit_resource(unit, RESOURCE_IRQ, 0);
  
  if (res) return res->start;
  return -1;
}

int get_unit_iobase(struct unit *unit) {
  struct resource *res = get_unit_resource(unit, RESOURCE_IO, 0);
  
  if (res) return res->start;
  return -1;
}

void *get_unit_membase(struct unit *unit) {
  struct resource *res = get_unit_resource(unit, RESOURCE_MEM, 0);
  
  if (res) return (void *) (res->start);
  return NULL;
}

char *get_unit_name(struct unit *unit) {
  if (unit->productname && *unit->productname) return unit->productname;
  if (unit->classname && *unit->classname) return unit->classname;
  return "unknown";
}

struct unit *lookup_unit(struct unit *start, unsigned long unitcode, unsigned long unitmask) {
  struct unit *unit;
  
  if (start) {
    unit = start->next;
  } else {
    unit = units;
  }

  while (unit) {
    if ((unit->unitcode & unitmask) == unitcode) return unit;
    unit = unit->next;
  }

  return NULL;
}

struct unit *lookup_unit_by_subunit(struct unit *start, unsigned long subunitcode, unsigned long subunitmask) {
  struct unit *unit;
  
  if (start) {
    unit = start->next;
  } else {
    unit = units;
  }

  while (unit) {
    if ((unit->subunitcode & subunitmask) == subunitcode) return unit;
    unit = unit->next;
  }

  return NULL;
}

struct unit *lookup_unit_by_class(struct unit *start, unsigned long classcode, unsigned long classmask) {
  struct unit *unit;
  
  if (start) {
    unit = start->next;
  } else {
    unit = units;
  }

  while (unit) {
    if ((unit->classcode & classmask) == classcode) return unit;
    unit = unit->next;
  }

  return NULL;
}

struct board *lookup_board(struct board *board_tbl, struct unit *unit) {
  int i = 0;

  while (board_tbl[i].vendorname != NULL) {
    if (unit->bus->bustype == board_tbl[i].bustype &&
        (unit->unitcode & board_tbl[i].unitmask) == board_tbl[i].unitcode &&
        (unit->subunitcode & board_tbl[i].subsystemmask) == board_tbl[i].subsystemcode &&
        (unit->revision & board_tbl[i].revisionmask) == board_tbl[i].revisioncode)
      break;

    i++;
  }

  if (board_tbl[i].vendorname == NULL) return NULL;
  return &board_tbl[i];
}

void enum_host_bus() {
  struct bus *host_bus;
  struct unit *pci_host_bridge;
  unsigned long unitcode;
  struct bus *pci_root_bus;
  struct unit *isa_bridge;
  struct bus *isa_bus;

  // Create host bus
  host_bus = add_bus(NULL, BUSTYPE_HOST, 0);

  unitcode = get_pci_hostbus_unitcode();
  if (unitcode) {
    // Enumerate PCI buses
    pci_host_bridge = add_unit(host_bus, PCI_HOST_BRIDGE, unitcode, 0);
    pci_root_bus = add_bus(pci_host_bridge, BUSTYPE_PCI, 0);

    enum_pci_bus(pci_root_bus);
  } else {
    // Enumerate ISA bus using PnP
    isa_bridge = add_unit(host_bus, PCI_ISA_BRIDGE, 0, 0);
    isa_bus = add_bus(isa_bridge, BUSTYPE_ISA, 0);

    enum_isapnp(isa_bus);
  }
}

static void parse_bindings() {
  struct section *sect;
  struct property *prop;
  struct binding *bind;
  char buf[128];
  char *p;
  char *q;
  int n;

  // Parse driver bindings
  sect = find_section(krnlcfg, "bindings");
  if (!sect) return;
  num_bindings = get_section_size(sect);
  if (!num_bindings) return;

  bindtab = (struct binding *) kmalloc(num_bindings * sizeof(struct binding));
  memset(bindtab, 0, num_bindings * sizeof(struct binding));

  n = 0;
  prop = sect->properties;
  while (prop) {
    bind = &bindtab[n];

    strcpy(buf, prop->name);
    
    p = q = buf;
    while (*q && *q != ' ') q++;
    if (!*q) continue;
    *q++ = 0;

    if (strcmp(p, "pci") == 0) {
      bind->bustype = BUSTYPE_PCI;
    } else if (strcmp(p, "isa") == 0) {
      bind->bustype = BUSTYPE_ISA;
    }

    while (*q == ' ') q++;
    p = q;
    while (*q && *q != ' ') q++;
    if (!*q) continue;
    *q++ = 0;

    if (strcmp(p, "class") == 0) {
      bind->bindtype = BIND_BY_CLASSCODE;
    } else if (strcmp(p, "unit") == 0) {
      bind->bindtype = BIND_BY_UNITCODE;
    } else if (strcmp(p, "subunit") == 0) {
      bind->bindtype = BIND_BY_SUBUNITCODE;
    }

    while (*q == ' ') q++;

    while (*q) {
      unsigned long digit;
      unsigned long mask;

      mask = 0xF;
      if (*q >= '0' && *q <= '9') {
        digit = *q - '0';
      } else if (*q >= 'A' && *q <= 'F') {
        digit = *q - 'A' + 10;
      } else if (*q >= 'a' && *q <= 'f') {
        digit = *q - 'a' + 10;
      } else {
        digit = 0;
        mask = 0;
      }

      bind->code = (bind->code << 4) | digit;
      bind->mask = (bind->mask << 4) | mask;
      q++;
    }

    bind->module = prop->value;

    prop = prop->next;
    n++;
  }
}

static struct binding *find_binding(struct unit *unit) {
  int n;

  for (n = 0; n < num_bindings; n++) {
    struct binding *bind = &bindtab[n];

    if (bind->bustype == unit->bus->bustype) {
      if (bind->bindtype == BIND_BY_CLASSCODE) {
        if ((unit->classcode & bind->mask) == bind->code) return bind;
      }

      if (bind->bindtype == BIND_BY_UNITCODE) {
        if ((unit->unitcode & bind->mask) == bind->code) return bind;
      }

      if (bind->bindtype == BIND_BY_SUBUNITCODE) {
        if ((unit->subunitcode & bind->mask) == bind->code) return bind;
      }
    }
  }

  return NULL;
}

static int initialize_driver(struct unit *unit, char *driverstr) {
  char *buf;
  char *p;
  char *modname;
  char *entryname;
  char *opts;
  hmodule_t hmod;
  int rc;
  int (*entry)(struct unit *unit, char *opts);

  p = buf = kmalloc(strlen(driverstr) + 1);
  if (!buf) return -ENOMEM;
  memcpy(buf, driverstr, strlen(driverstr) + 1);

  modname = p;
  entryname = strchr(p, '!');
  if (entryname) {
    *entryname++ = 0;
    p = entryname;
  } else {
    entryname = "install";
  }

  opts = strchr(p, ':');
  if (opts) {
    *opts++ = 0;
  } else {
    opts = NULL;
  }

  hmod = load(modname, 0);
  if (!hmod) {
    kprintf(KERN_ERR "dev: unable to load module %s\n", modname);
    kfree(buf);
    return -ENOEXEC;
  }

  entry = resolve(hmod, entryname);
  if (!entry) {
    kprintf(KERN_ERR "dev: unable to find entry %s in module %s\n", entryname, modname);
    unload(hmod);
    kfree(buf);
    return -ENOEXEC;
  }

  rc = entry(unit, opts);
  if (rc < 0) {
    kprintf(KERN_ERR "dev: initialization of %s!%s failed with error %d\n", modname, entryname, rc);
    // NB: it is not always safe to unload the driver module after failure
    //unload(hmod);
    kfree(buf);
    return rc;
  }

  kfree(buf);
  return 0;
}

static void install_driver(struct unit *unit, struct binding *bind) {
  int rc;

  rc = initialize_driver(unit, bind->module);
  if (rc < 0) {
    kprintf(KERN_ERR "dev: driver '%s' failed with error %d for unit %08X '%s'\n", bind->module, rc, unit->unitcode, get_unit_name(unit));
  }
}

static void bind_units()
{
  struct unit *unit = units;

  while (unit) {
    if (unit->dev == NULL) {
      struct binding *bind = find_binding(unit);
      if (bind) install_driver(unit, bind);
    }
    unit = unit->next;
  }
}

static void install_legacy_drivers() {
  struct section *sect;
  struct property *prop;
  int buflen;
  char *buf;
  int rc;

  sect = find_section(krnlcfg, "drivers");
  if (!sect) return;

  prop = sect->properties;
  while (prop) {
    buflen = strlen(prop->name) + 1;
    if (prop->value) buflen += strlen(prop->value) + 1;
    buf = kmalloc(buflen);
    
    if (buf) {
      strcpy(buf, prop->name);
      if (prop->value) {
        strcat(buf, ":");
        strcat(buf, prop->value);
      }

      rc = initialize_driver(NULL, buf);
      if (rc < 0) {
        kprintf(KERN_ERR "dev: error %d initializing driver %s\n", rc, prop->name);
      }

      kfree(buf);
    }

    prop = prop->next;
  }
}

void install_drivers() {
  dev_t console;

  // Register /proc/units
  register_proc_inode("units", units_proc, NULL);
  register_proc_inode("devices", devices_proc, NULL);
  register_proc_inode("devstat", devstat_proc, NULL);

  // Parse driver binding database
  parse_bindings();

  // Match bindings to units
  bind_units();

  // Install legacy drivers
  install_legacy_drivers();

  // Make sure we have a console device
  console = dev_open("console");
  if (console == NODEV) {
    initialize_driver(NULL, "krnl.dll!console");
  } else {
    dev_close(console);
  }
}

struct dev *device(dev_t devno) {
  if (devno < 0 || devno >= num_devs) return NULL;
  return devtab[devno];
}

dev_t dev_make(char *name, struct driver *driver, struct unit *unit, void *privdata) {
  struct dev *dev;
  dev_t devno;
  char *p;
  unsigned int n, m;
  int exists;

  if (num_devs == MAX_DEVS) panic("too many devices");

  dev = (struct dev *) kmalloc(sizeof(struct dev));
  if (!dev) return NODEV;
  memset(dev, 0, sizeof(struct dev));

  strcpy(dev->name, name);
  
  p = dev->name;
  while (p[0] && p[1]) p++;
  if (*p == '#') {
    n = 0;
    while (1) {
      sprintf(p, "%d", n);
      exists = 0;
      for (m = 0; m < num_devs; m++)  {
        if (strcmp(devtab[m]->name, dev->name) == 0) {
          exists = 1;
          break;
        }
      }

      if (!exists) break;
      n++;
    }
  }

  dev->driver = driver;
  dev->unit = unit;
  dev->privdata = privdata;
  dev->refcnt = 0;
  dev->mode = 0600;

  switch (dev->driver->type) {
    case DEV_TYPE_STREAM: dev->mode |= S_IFCHR; break;
    case DEV_TYPE_BLOCK: dev->mode |= S_IFBLK; break;
    case DEV_TYPE_PACKET: dev->mode |= S_IFPKT; break;
  }

  if (unit) unit->dev = dev;

  devno = num_devs++;
  devtab[devno] = dev;
  
  return devno;
}

dev_t devno(char *name) {
  dev_t devno;

  for (devno = 0; devno < num_devs; devno++) {
    if (strcmp(devtab[devno]->name, name) == 0) return devno;
  }
  return NODEV;
}

dev_t dev_open(char *name) {
  dev_t d = devno(name);
  if (d != NODEV) devtab[d]->refcnt++;
  return d;
}

int dev_close(dev_t devno) {
  if (devno < 0 || devno >= num_devs) return -ENODEV;
  if (devtab[devno]->refcnt == 0) return -EPERM;
  devtab[devno]->refcnt--;
  return 0;
}

int dev_ioctl(dev_t devno, int cmd, void *args, size_t size) {
  struct dev *dev;

  if (devno < 0 || devno >= num_devs) return -ENODEV;
  dev = devtab[devno];
  if (!dev->driver->ioctl) return -ENOSYS;

  return dev->driver->ioctl(dev, cmd, args, size);
}

int dev_read(dev_t devno, void *buffer, size_t count, blkno_t blkno, int flags) {
  struct dev *dev;

  if (devno < 0 || devno >= num_devs) return -ENODEV;
  dev = devtab[devno];
  if (!dev->driver->read) return -ENOSYS;
  dev->reads++;
  dev->input += count;

  return dev->driver->read(dev, buffer, count, blkno, flags);
}

int dev_write(dev_t devno, void *buffer, size_t count, blkno_t blkno, int flags) {
  struct dev *dev;

  if (devno < 0 || devno >= num_devs) return -ENODEV;
  dev = devtab[devno];
  if (!dev->driver->read) return -ENOSYS;
  dev->writes++;
  dev->output += count;

  return dev->driver->write(dev, buffer, count, blkno, flags);
}

int dev_attach(dev_t devno, struct netif *netif, int (*receive)(struct netif *netif, struct pbuf *p)) {
  struct dev *dev;

  if (devno < 0 || devno >= num_devs) return -ENODEV;
  dev = devtab[devno];
  if (!dev->driver->attach) return -ENOSYS;

  dev->netif = netif;
  dev->receive = receive;
  return dev->driver->attach(dev, &netif->hwaddr);
}

int dev_detach(dev_t devno) {
  struct dev *dev;
  int rc;

  if (devno < 0 || devno >= num_devs) return -ENODEV;
  dev = devtab[devno];

  if (dev->driver->detach) {
    rc = dev->driver->detach(dev);
  } else {
    rc = 0;
  }

  dev->netif = NULL;
  dev->receive = NULL;

  return rc;
}

int dev_transmit(dev_t devno, struct pbuf *p) {
  struct dev *dev;

  if (devno < 0 || devno >= num_devs) return -ENODEV;
  dev = devtab[devno];
  if (!dev->driver->transmit) return -ENOSYS;
  dev->writes++;
  dev->output += p->tot_len;

  return dev->driver->transmit(dev, p);
}

int dev_receive(dev_t devno, struct pbuf *p) {
  struct dev *dev;

  if (devno < 0 || devno >= num_devs) return -ENODEV;
  dev = devtab[devno];
  if (!dev->receive) return -ENOSYS;
  dev->reads++;
  dev->input += p->tot_len;

  return dev->receive(dev->netif, p);
}

int dev_setevt(dev_t devno, int events) {
  struct dev *dev;

  if (devno < 0 || devno >= num_devs) return -ENODEV;
  dev = devtab[devno];
  devfs_setevt(dev, events);
  return 0;
}

int dev_clrevt(dev_t devno, int events) {
  struct dev *dev;

  if (devno < 0 || devno >= num_devs) return -ENODEV;
  dev = devtab[devno];
  devfs_clrevt(dev, events);
  return 0;
}

static int units_proc(struct proc_file *pf, void *arg) {
  struct unit *unit;
  struct resource *res;
  int bustype;
  int busno;

  unit = units;
  while (unit) {
    if (unit->bus) {
      bustype = unit->bus->bustype;
      busno = unit->bus->busno;
    } else {
      bustype = BUSTYPE_HOST;
      busno = 0;
    }
    
    pprintf(pf, "%s unit %d.%d class %08X code %08X %s:\n", busnames[bustype], busno, unit->unitno,unit->classcode, unit->unitcode, get_unit_name(unit));

    if (unit->subunitcode != 0 || unit ->revision != 0) {
      pprintf(pf, "  subunitcode: %08X revision %d\n", unit->subunitcode, unit->revision);
    }

    res = unit->resources;
    while (res) {
      switch (res->type) {
        case RESOURCE_IO: 
          if (res->len == 1) { 
            pprintf(pf, "  io: 0x%03x", res->start);
          } else {
            pprintf(pf, "  io: 0x%03x-0x%03x", res->start, res->start + res->len - 1);
          }
          break;

        case RESOURCE_MEM:
          if (res->len == 1) {
            pprintf(pf, "  mem: 0x%08x", res->start);
          } else {
            pprintf(pf, "  mem: 0x%08x-0x%08x", res->start, res->start + res->len - 1);
          }
          break;

        case RESOURCE_IRQ:
          if (res->len == 1) {
            pprintf(pf, "  irq: %d", res->start);
          } else {
            pprintf(pf, "  irq: %d-%d", res->start, res->start + res->len - 1);
          }
          break;

        case RESOURCE_DMA:
          if (res->len == 1) {
            pprintf(pf, "  dma: %d", res->start);
          } else {
            pprintf(pf, "  dma: %d-%d", res->start, res->start + res->len - 1);
          }
          break;
      }

      pprintf(pf, "\n");

      res = res->next;
    }

    unit = unit->next;
  }

  return 0;
}

static int devices_proc(struct proc_file *pf, void *arg) {
  dev_t devno;
  struct dev *dev;

  pprintf(pf, "devno name     driver           type   unit\n");
  pprintf(pf, "----- -------- ---------------- ------ ------------------------------\n");

  for (devno = 0; devno < num_devs; devno++) {
    dev = devtab[devno];
    pprintf(pf, "%5d %-8s %-16s %-6s ", devno, dev->name, dev->driver->name, devtypenames[dev->driver->type]);
    if (dev->unit) {
      pprintf(pf, "%s unit %d.%d\n", busnames[dev->unit->bus->bustype], dev->unit->bus->busno, dev->unit->unitno);
    } else {
      pprintf(pf, "<none>\n");
    }
  }

  return 0;
}

static int devstat_proc(struct proc_file *pf, void *arg) {
  dev_t devno;
  struct dev *dev;

  pprintf(pf, "devno name        reads      input   writes     output\n");
  pprintf(pf, "----- -------- -------- ---------- -------- ----------\n");

  for (devno = 0; devno < num_devs; devno++) {
    dev = devtab[devno];
    pprintf(pf, "%5d %-8s%9d%11d%9d%11d\n", devno, dev->name, dev->reads, dev->input, dev->writes, dev->output);
  }

  return 0;
}