/* * PnP bios services * * Originally (C) 1998 Christian Schmidt (chr.schmidt@tu-bs.de) * Modifications (c) 1998 Tom Lees * Minor reorganizations by David Hinds * * 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 * */ #include #define __NO_VERSION__ #include #include #include #include #include #include #include #include #include /* 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 asmlinkage 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 asmlinkage 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; } }