Logo Search packages:      
Sourcecode: pcmcia-cs version File versions  Download package

pnp_bios.c

/*
 * PnP bios services
 * 
 * Originally (C) 1998 Christian Schmidt (chr.schmidt@tu-bs.de)
 * Modifications (c) 1998 Tom Lees <tom@lpsg.demon.co.uk>
 * Minor reorganizations by David Hinds <dahinds@users.sourceforge.net>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2, or (at your option) any
 * later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *   Reference:
 *   Compaq Computer Corporation, Phoenix Technologies Ltd., Intel 
 *   Corporation. 
 *   Plug and Play BIOS Specification, Version 1.0A, May 5, 1994
 *   Plug and Play BIOS Clarification Paper, October 6, 1994
 *
 */

#define __NO_VERSION__

#include <linux/types.h>
#include <linux/module.h>
#include <linux/linkage.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/pnp_bios.h>
#include <linux/spinlock.h>
#include <linux/sched.h>
#include <asm/page.h>
#include <asm/system.h>
#include <asm/desc.h>

/* PnP bios signature: "$PnP" */
#define PNP_SIGNATURE   (('$' << 0) + ('P' << 8) + ('n' << 16) + ('P' << 24))

#ifdef MODULE
static struct desc_struct *gdt;
#endif

/*
 * This is the standard structure used to identify the entry point
 * to the Plug and Play bios
 */
#pragma pack(1)
union pnpbios {
      struct {
            u32 signature;    /* "$PnP" */
            u8 version;   /* in BCD */
            u8 length;    /* length in bytes, currently 21h */
            u16 control;        /* system capabilities */
            u8 checksum;        /* all bytes must add up to 0 */

            u32 eventflag;    /* phys. address of the event flag */
            u16 rmoffset;     /* real mode entry point */
            u16 rmcseg;
            u16 pm16offset;   /* 16 bit protected mode entry */
            u32 pm16cseg;
            u32 deviceID;       /* EISA encoded system ID or 0 */
            u16 rmdseg;   /* real mode data segment */
            u32 pm16dseg;       /* 16 bit pm data segment base */
      } fields;
      char chars[0x21];   /* To calculate the checksum */
};
#pragma pack()

/*
 * Local Variables
 */
static struct {
      u32   offset;
      u16   segment;
} pnp_bios_callpoint;

static union pnpbios * pnp_bios_inst_struc = NULL;

/* The PnP entries in the GDT */
#define PNP_GDT         0x0038
#define PNP_CS32  (PNP_GDT+0x00)    /* segment for calling fn */
#define PNP_CS16  (PNP_GDT+0x08)    /* code segment for bios */
#define PNP_DS          (PNP_GDT+0x10)    /* data segment for bios */
#define PNP_TS1         (PNP_GDT+0x18)    /* transfer data segment */
#define PNP_TS2         (PNP_GDT+0x20)    /* another data segment */

static struct desc_struct saved_gdt[5];
static struct desc_struct pnp_gdt[] = {
      { 0, 0x00c09a00 },      /* 32-bit code */
      { 0, 0x00809a00 },      /* 16-bit code */
      { 0, 0x00809200 },      /* 16-bit data */
      { 0, 0x00809200 },      /* 16-bit data */
      { 0, 0x00809200 } /* 16-bit data */
};

/*
 * GDT abuse: since we want this to work when loaded as a module on a
 * normal kernel, we drop the PnP GDT entries on top of the APM stuff
 * and protect it with a spin lock (
 */

static long gdt_flags;
#ifdef USE_SPIN_LOCKS
static spinlock_t gdt_lock = SPIN_LOCK_UNLOCKED;
#endif

static void push_pnp_gdt(void)
{
      spin_lock_irqsave(&gdt_lock, gdt_flags);
      memcpy(saved_gdt, &gdt[PNP_GDT >> 3], sizeof(saved_gdt));
      memcpy(&gdt[PNP_GDT >> 3], pnp_gdt, sizeof(pnp_gdt));
}

static void pop_pnp_gdt(void)
{
      memcpy(pnp_gdt, &gdt[PNP_GDT >> 3], sizeof(pnp_gdt));
      memcpy(&gdt[PNP_GDT >> 3], saved_gdt, sizeof(saved_gdt));
      spin_unlock_irqrestore(&gdt_lock, gdt_flags);
}

/* 
 * These are some opcodes for a "static asmlinkage"
 * As this code is *not* executed inside the linux kernel segment, but in a
 * alias at offset 0, we need a far return that can not be compiled by
 * default (please, prove me wrong! this is *really* ugly!) 
 * This is the only way to get the bios to return into the kernel code,
 * because the bios code runs in 16 bit protected mode and therefore can only
 * return to the caller if the call is within the first 64kB, and the linux
 * kernel begins at offset 1MB...
 */
static u8 pnp_bios_callfunc[] =
{
      0x52,                           /* push edx */
      0x51,                           /* push ecx */
      0x53,                           /* push ebx */
      0x50,                           /* push eax */
      0x66, 0x9a, 0, 0,               /* call far pnp_cs16:0, offset */
      (PNP_CS16) & 0xff, (PNP_CS16) >> 8,   /* becomes fixed up later */
      0x83, 0xc4, 0x10,               /* add esp, 16 */
      0xcb};                                /* retf */

#define Q_SET_SEL(selname, address, size) \
set_base (gdt [(selname) >> 3], __va((u32)(address))); \
set_limit (gdt [(selname) >> 3], size)

#define Q2_SET_SEL(selname, address, size) \
set_base (gdt [(selname) >> 3], (u32)(address)); \
set_limit (gdt [(selname) >> 3], size)

/*
 * Callable Functions
 */
#define PNP_GET_NUM_SYS_DEV_NODES       0x00
#define PNP_GET_SYS_DEV_NODE            0x01
#define PNP_SET_SYS_DEV_NODE            0x02
#define PNP_GET_EVENT                   0x03
#define PNP_SEND_MESSAGE                0x04
#define PNP_GET_DOCKING_STATION_INFORMATION 0x05
#define PNP_SET_STATIC_ALLOCED_RES_INFO 0x09
#define PNP_GET_STATIC_ALLOCED_RES_INFO 0x0a
#define PNP_GET_APM_ID_TABLE            0x0b
#define PNP_GET_PNP_ISA_CONFIG_STRUC    0x40
#define PNP_GET_ESCD_INFO               0x41
#define PNP_READ_ESCD                   0x42
#define PNP_WRITE_ESCD                  0x43

/*
 * Call pnp bios with function 0x00, "get number of system device nodes"
 */
int pnp_bios_dev_node_info(struct pnp_dev_node_info *data)
{
      u16 status;
      if (!pnp_bios_present ())
            return PNP_FUNCTION_NOT_SUPPORTED;
      push_pnp_gdt();
      Q2_SET_SEL(PNP_TS1, data, sizeof(struct pnp_dev_node_info));
      __asm__ __volatile__
          ("lcall %%cs:" SYMBOL_NAME_STR(pnp_bios_callpoint) "\n\t"
           :"=a"(status)
           :"a"(PNP_GET_NUM_SYS_DEV_NODES),
            "b"((2 << 16) | PNP_TS1),
            "c"((PNP_DS << 16) | PNP_TS1)
           :"memory");
      data->no_nodes &= 0xff;
      pop_pnp_gdt();
      return status;
}

/* 
 * Call pnp bios with function 0x01, "get system device node"
 * Input:  *nodenum=desired node, 
 *         static=1: config (dynamic) config, else boot (static) config,
 * Output: *nodenum=next node or 0xff if no more nodes
 */
int pnp_bios_get_dev_node(u8 *nodenum, char config, struct pnp_bios_node *data)
{
      u16 status;
      if (!pnp_bios_present ())
            return PNP_FUNCTION_NOT_SUPPORTED;
      push_pnp_gdt();
      Q2_SET_SEL(PNP_TS1, nodenum, sizeof(char));
      Q2_SET_SEL(PNP_TS2, data, 64 * 1024);
      __asm__ __volatile__
          ("lcall %%cs:" SYMBOL_NAME_STR(pnp_bios_callpoint) "\n\t"
           :"=a"(status)
           :"a"(PNP_GET_SYS_DEV_NODE),
            "b"(PNP_TS1),
            "c"(((config ? 1 : 2) <<16) | PNP_TS2),
            "d"(PNP_DS)
           :"memory");
      pop_pnp_gdt();
      return status;
}

/*
 * Call pnp bios with function 0x02, "set system device node"
 * Input: nodenum=desired node, 
 *        static=1: config (dynamic) config, else boot (static) config,
 */
int pnp_bios_set_dev_node(u8 nodenum, char config, struct pnp_bios_node *data)
{
      u16 status;
      if (!pnp_bios_present ())
            return PNP_FUNCTION_NOT_SUPPORTED;
      push_pnp_gdt();
      Q2_SET_SEL(PNP_TS1, data, /* *((u16 *) data)*/ 65536);
      __asm__ __volatile__
          ("lcall %%cs:" SYMBOL_NAME_STR(pnp_bios_callpoint) "\n\t"
           :"=a"(status)
           :"a"(((u32) nodenum << 16) | PNP_SET_SYS_DEV_NODE),
            "b"(PNP_TS1 << 16),
            "c"((PNP_DS << 16) | (config ? 1 : 2))
           :"memory");
      pop_pnp_gdt();
      return status;
}

/*
 * Call pnp bios with function 0x03, "get event"
 */
#if needed
int pnp_bios_get_event(u16 *event)
{
      u16 status;
      if (!pnp_bios_present ())
            return PNP_FUNCTION_NOT_SUPPORTED;
      push_pnp_gdt();
      Q2_SET_SEL(PNP_TS1, event, sizeof(u16));
      __asm__ __volatile__
          ("lcall %%cs:" SYMBOL_NAME_STR(pnp_bios_callpoint) "\n\t"
           :"=a"(status)
           :"a"(PNP_GET_EVENT),
            "b"((PNP_DS << 16) | PNP_TS1)
           :"memory");
      pop_pnp_gdt();
      return status;
}
#endif

/* 
 * Call pnp bios with function 0x04, "send message"
 */
#if needed
int pnp_bios_send_message(u16 message)
{
      u16 status;
      if (!pnp_bios_present ())
            return PNP_FUNCTION_NOT_SUPPORTED;
      push_pnp_gdt();
      __asm__ __volatile__
          ("lcall %%cs:" SYMBOL_NAME_STR(pnp_bios_callpoint) "\n\t"
           :"=a"(status)
           :"a"(((u32) message << 16) | PNP_SEND_MESSAGE),
            "b"(PNP_DS)
           :"memory");
      pop_pnp_gdt();
      return status;
}
#endif

/*
 * Call pnp bios with function 0x05, "get docking station information"
 */
#if needed
int pnp_bios_dock_station_info(struct pnp_docking_station_info *data)
{
      u16 status;
      if (!pnp_bios_present ())
            return PNP_FUNCTION_NOT_SUPPORTED;
      push_pnp_gdt();
      Q2_SET_SEL(PNP_TS1, data, sizeof(struct pnp_docking_station_info));
      __asm__ __volatile__
          ("lcall %%cs:" SYMBOL_NAME_STR(pnp_bios_callpoint) "\n\t"
           :"=a"(status)
           :"a"(PNP_GET_DOCKING_STATION_INFORMATION),
            "b"((PNP_TS1 << 16) | PNP_DS)
           :"memory");
      pop_pnp_gdt();
      return status;
}
#endif

/*
 * Call pnp bios with function 0x09, "set statically allocated resource
 * information"
 */
#if needed
int pnp_bios_set_stat_res(char *info)
{
      u16 status;
      if (!pnp_bios_present ())
            return PNP_FUNCTION_NOT_SUPPORTED;
      push_pnp_gdt();
      Q2_SET_SEL(PNP_TS1, info, *((u16 *) info));
      __asm__ __volatile__
          ("lcall %%cs:" SYMBOL_NAME_STR(pnp_bios_callpoint) "\n\t"
           :"=a"(status)
           :"a"(PNP_SET_STATIC_ALLOCED_RES_INFO),
            "b"((PNP_TS1 << 16) | PNP_DS)
           :"memory");
      pop_pnp_gdt();
      return status;
}
#endif

/*
 * Call pnp bios with function 0x0a, "get statically allocated resource
 * information"
 */
#if needed
int pnp_bios_get_stat_res(char *info)
{
      u16 status;
      if (!pnp_bios_present ())
            return PNP_FUNCTION_NOT_SUPPORTED;
      push_pnp_gdt();
      Q2_SET_SEL(PNP_TS1, info, 64 * 1024);
      __asm__ __volatile__
          ("lcall %%cs:" SYMBOL_NAME_STR(pnp_bios_callpoint) "\n\t"
           :"=a"(status)
           :"a"(PNP_GET_STATIC_ALLOCED_RES_INFO),
            "b"((PNP_TS1 << 16) | PNP_DS)
           :"memory");
      pop_pnp_gdt();
      return status;
}
#endif

/*
 * Call pnp bios with function 0x0b, "get APM id table"
 */
#if needed
int pnp_bios_apm_id_table(char *table, u16 *size)
{
      u16 status;
      if (!pnp_bios_present ())
            return PNP_FUNCTION_NOT_SUPPORTED;
      push_pnp_gdt();
      Q2_SET_SEL(PNP_TS1, table, *size);
      Q2_SET_SEL(PNP_TS2, size, sizeof(u16));
      __asm__ __volatile__
          ("lcall %%cs:" SYMBOL_NAME_STR(pnp_bios_callpoint) "\n\t"
           :"=a"(status)
           :"a"(PNP_GET_APM_ID_TABLE),
            "b"(PNP_TS2),
            "c"((PNP_DS << 16) | PNP_TS1)
           :"memory");
      pop_pnp_gdt();
      return status;
}
#endif

/*
 * Call pnp bios with function 0x40, "get isa pnp configuration structure"
 */
#if needed
int pnp_bios_isapnp_config(struct pnp_isa_config_struc *data)
{
      u16 status;
      if (!pnp_bios_present ())
            return PNP_FUNCTION_NOT_SUPPORTED;
      push_pnp_gdt();
      Q2_SET_SEL(PNP_TS1, data, sizeof(struct pnp_isa_config_struc));
      __asm__ __volatile__
          ("lcall %%cs:" SYMBOL_NAME_STR(pnp_bios_callpoint) "\n\t"
           :"=a"(status)
           :"a"(PNP_GET_PNP_ISA_CONFIG_STRUC),
            "b"((PNP_DS << 16) | PNP_TS1)
           :"memory");
      pop_pnp_gdt();
      return status;
}
#endif

/*
 * Call pnp bios with function 0x41, "get ESCD info"
 */
#if needed
int pnp_bios_escd_info(struct escd_info_struc *data)
{
      u16 status;
      if (!pnp_bios_present ())
            return ESCD_FUNCTION_NOT_SUPPORTED;
      push_pnp_gdt();
      Q2_SET_SEL(PNP_TS1, data, sizeof(struct escd_info_struc));
      __asm__ __volatile__
          ("lcall %%cs:" SYMBOL_NAME_STR(pnp_bios_callpoint) "\n\t"
           :"=a"(status)
           :"a"(PNP_GET_ESCD_INFO),
            "b"((2 << 16) | PNP_TS1),
            "c"((4 << 16) | PNP_TS1),
            "d"((PNP_DS << 16) | PNP_TS1)
           :"memory");
      pop_pnp_gdt();
      return status;
}
#endif

/*
 * Call pnp bios function 0x42, "read ESCD"
 * nvram_base is determined by calling escd_info
 */
#if needed
int pnp_bios_read_escd(char *data, u32 nvram_base)
{
      u16 status;
      if (!pnp_bios_present ())
            return ESCD_FUNCTION_NOT_SUPPORTED;
      push_pnp_gdt();
      Q2_SET_SEL(PNP_TS1, data, 64 * 1024);
      set_base(gdt[PNP_TS2 >> 3], nvram_base);
      set_limit(gdt[PNP_TS2 >> 3], 64 * 1024);
      __asm__ __volatile__
          ("lcall %%cs:" SYMBOL_NAME_STR(pnp_bios_callpoint) "\n\t"
           :"=a"(status)
           :"a"(PNP_READ_ESCD),
            "b"((PNP_TS2 << 16) | PNP_TS1),
            "c"(PNP_DS)
           :"memory");
      pop_pnp_gdt();
      return status;
}
#endif

/*
 * Call pnp bios function 0x43, "write ESCD"
 */
#if needed
int pnp_bios_write_escd(char *data, u32 nvram_base)
{
      u16 status;
      if (!pnp_bios_present ())
            return ESCD_FUNCTION_NOT_SUPPORTED;
      push_pnp_gdt();
      Q2_SET_SEL(PNP_TS1, data, 64 * 1024);
      set_base(gdt[PNP_TS2 >> 3], nvram_base);
      set_limit(gdt[PNP_TS2 >> 3], 64 * 1024);
      __asm__ __volatile__
          ("lcall %%cs:" SYMBOL_NAME_STR(pnp_bios_callpoint) "\n\t"
           :"=a"(status)
           :"a"(PNP_WRITE_ESCD),
            "b"((PNP_TS2 << 16) | PNP_TS1),
            "c"(PNP_DS)
           :"memory");
      pop_pnp_gdt();
      return status;
}
#endif

int pnp_bios_present(void)
{
  return (pnp_bios_inst_struc != NULL);
}

/* 
 * Searches the defined area (0xf0000-0xffff0) for a valid PnP BIOS
 * structure and, if found one, sets up the selectors and entry points
 */

void pnp_bios_init(void)
{
      union pnpbios *check;
      u8 sum;
      int i, length;

#ifdef MODULE
      struct Xgt_desc_struct my_gdt_descr;
      __asm__ __volatile__ ("sgdt %0" : : "m" (my_gdt_descr));
      gdt = (struct desc_struct *)my_gdt_descr.address;
#endif

      for (check = (union pnpbios *) __va(0xf0000);
           check < (union pnpbios *) __va(0xffff0);
           ((void *) (check)) += 16) {
            if (check->fields.signature != PNP_SIGNATURE)
                  continue;
            length = check->fields.length;
            if (!length)
                  continue;
            for (sum = 0, i = 0; i < length; i++)
                  sum += check->chars[i];
            if (sum)
                  continue;
            if (check->fields.version < 0x10) {
                  printk(KERN_WARNING "PnP: unsupported version %d.%d",
                         check->fields.version >> 4,
                         check->fields.version & 15);
                  continue;
            }
            printk(KERN_INFO "PnP: PNP BIOS installation structure at 0x%p\n",
                   check);
            printk(KERN_INFO "PnP: PNP BIOS version %d.%d, entry at %x:%x, dseg at %x\n",
                       check->fields.version >> 4, check->fields.version & 15,
                   check->fields.pm16cseg, check->fields.pm16offset,
                   check->fields.pm16dseg);
            push_pnp_gdt();
            Q2_SET_SEL(PNP_CS32, &pnp_bios_callfunc,
                     sizeof(pnp_bios_callfunc));
            Q_SET_SEL(PNP_CS16, check->fields.pm16cseg, 64 * 1024);
            Q_SET_SEL(PNP_DS, check->fields.pm16dseg, 64 * 1024);
            pop_pnp_gdt();
            pnp_bios_callfunc[6] = check->fields.pm16offset & 0xff;
            pnp_bios_callfunc[7] = check->fields.pm16offset >> 8;
            pnp_bios_callpoint.offset = 0;
            pnp_bios_callpoint.segment = PNP_CS32;
            pnp_bios_inst_struc = check;
            break;
      }
}

Generated by  Doxygen 1.6.0   Back to index