Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

View file

@ -0,0 +1,42 @@
#
# Plug and Play BIOS configuration
#
config PNPBIOS
bool "Plug and Play BIOS support"
depends on ISA && X86
default n
---help---
Linux uses the PNPBIOS as defined in "Plug and Play BIOS
Specification Version 1.0A May 5, 1994" to autodetect built-in
mainboard resources (e.g. parallel port resources).
Some features (e.g. event notification, docking station information,
ISAPNP services) are not currently implemented.
If you would like the kernel to detect and allocate resources to
your mainboard devices (on some systems they are disabled by the
BIOS) say Y here. Also the PNPBIOS can help prevent resource
conflicts between mainboard devices and other bus devices.
Note: ACPI is expected to supersede PNPBIOS some day, currently it
co-exists nicely. If you have a non-ISA system that supports ACPI,
you probably don't need PNPBIOS support.
config PNPBIOS_PROC_FS
bool "Plug and Play BIOS /proc interface"
depends on PNPBIOS && PROC_FS
---help---
If you say Y here and to "/proc file system support", you will be
able to directly access the PNPBIOS. This includes resource
allocation, ESCD, and other PNPBIOS services. Using this
interface is potentially dangerous because the PNPBIOS driver will
not be notified of any resource changes made by writing directly.
Also some buggy systems will fault when accessing certain features
in the PNPBIOS /proc interface (e.g. "boot" configs).
See the latest pcmcia-cs (stand-alone package) for a nice set of
PNPBIOS /proc interface tools (lspnp and setpnp).
Unless you are debugging or have other specific reasons, it is
recommended that you say N here.

View file

@ -0,0 +1,8 @@
#
# Makefile for the kernel PNPBIOS driver.
#
obj-y := pnp.o
pnp-y := core.o bioscalls.o rsparser.o
pnp-$(CONFIG_PNPBIOS_PROC_FS) += proc.o

View file

@ -0,0 +1,490 @@
/*
* bioscalls.c - the lowlevel layer of the PnPBIOS driver
*/
#include <linux/types.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/linkage.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/pnp.h>
#include <linux/mm.h>
#include <linux/smp.h>
#include <linux/kmod.h>
#include <linux/completion.h>
#include <linux/spinlock.h>
#include <asm/page.h>
#include <asm/desc.h>
#include <asm/byteorder.h>
#include "pnpbios.h"
__visible struct {
u16 offset;
u16 segment;
} pnp_bios_callpoint;
/*
* 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 3GB...
*/
asmlinkage __visible void pnp_bios_callfunc(void);
__asm__(".text \n"
__ALIGN_STR "\n"
".globl pnp_bios_callfunc\n"
"pnp_bios_callfunc:\n"
" pushl %edx \n"
" pushl %ecx \n"
" pushl %ebx \n"
" pushl %eax \n"
" lcallw *pnp_bios_callpoint\n"
" addl $16, %esp \n"
" lret \n"
".previous \n");
#define Q2_SET_SEL(cpu, selname, address, size) \
do { \
struct desc_struct *gdt = get_cpu_gdt_table((cpu)); \
set_desc_base(&gdt[(selname) >> 3], (u32)(address)); \
set_desc_limit(&gdt[(selname) >> 3], (size) - 1); \
} while(0)
static struct desc_struct bad_bios_desc = GDT_ENTRY_INIT(0x4092,
(unsigned long)__va(0x400UL), PAGE_SIZE - 0x400 - 1);
/*
* At some point we want to use this stack frame pointer to unwind
* after PnP BIOS oopses.
*/
__visible u32 pnp_bios_fault_esp;
__visible u32 pnp_bios_fault_eip;
__visible u32 pnp_bios_is_utter_crap = 0;
static spinlock_t pnp_bios_lock;
/*
* Support Functions
*/
static inline u16 call_pnp_bios(u16 func, u16 arg1, u16 arg2, u16 arg3,
u16 arg4, u16 arg5, u16 arg6, u16 arg7,
void *ts1_base, u32 ts1_size,
void *ts2_base, u32 ts2_size)
{
unsigned long flags;
u16 status;
struct desc_struct save_desc_40;
int cpu;
/*
* PnP BIOSes are generally not terribly re-entrant.
* Also, don't rely on them to save everything correctly.
*/
if (pnp_bios_is_utter_crap)
return PNP_FUNCTION_NOT_SUPPORTED;
cpu = get_cpu();
save_desc_40 = get_cpu_gdt_table(cpu)[0x40 / 8];
get_cpu_gdt_table(cpu)[0x40 / 8] = bad_bios_desc;
/* On some boxes IRQ's during PnP BIOS calls are deadly. */
spin_lock_irqsave(&pnp_bios_lock, flags);
/* The lock prevents us bouncing CPU here */
if (ts1_size)
Q2_SET_SEL(smp_processor_id(), PNP_TS1, ts1_base, ts1_size);
if (ts2_size)
Q2_SET_SEL(smp_processor_id(), PNP_TS2, ts2_base, ts2_size);
__asm__ __volatile__("pushl %%ebp\n\t"
"pushl %%edi\n\t"
"pushl %%esi\n\t"
"pushl %%ds\n\t"
"pushl %%es\n\t"
"pushl %%fs\n\t"
"pushl %%gs\n\t"
"pushfl\n\t"
"movl %%esp, pnp_bios_fault_esp\n\t"
"movl $1f, pnp_bios_fault_eip\n\t"
"lcall %5,%6\n\t"
"1:popfl\n\t"
"popl %%gs\n\t"
"popl %%fs\n\t"
"popl %%es\n\t"
"popl %%ds\n\t"
"popl %%esi\n\t"
"popl %%edi\n\t"
"popl %%ebp\n\t":"=a"(status)
:"0"((func) | (((u32) arg1) << 16)),
"b"((arg2) | (((u32) arg3) << 16)),
"c"((arg4) | (((u32) arg5) << 16)),
"d"((arg6) | (((u32) arg7) << 16)),
"i"(PNP_CS32), "i"(0)
:"memory");
spin_unlock_irqrestore(&pnp_bios_lock, flags);
get_cpu_gdt_table(cpu)[0x40 / 8] = save_desc_40;
put_cpu();
/* If we get here and this is set then the PnP BIOS faulted on us. */
if (pnp_bios_is_utter_crap) {
printk(KERN_ERR
"PnPBIOS: Warning! Your PnP BIOS caused a fatal error. Attempting to continue\n");
printk(KERN_ERR
"PnPBIOS: You may need to reboot with the \"pnpbios=off\" option to operate stably\n");
printk(KERN_ERR
"PnPBIOS: Check with your vendor for an updated BIOS\n");
}
return status;
}
void pnpbios_print_status(const char *module, u16 status)
{
switch (status) {
case PNP_SUCCESS:
printk(KERN_ERR "PnPBIOS: %s: function successful\n", module);
break;
case PNP_NOT_SET_STATICALLY:
printk(KERN_ERR "PnPBIOS: %s: unable to set static resources\n",
module);
break;
case PNP_UNKNOWN_FUNCTION:
printk(KERN_ERR "PnPBIOS: %s: invalid function number passed\n",
module);
break;
case PNP_FUNCTION_NOT_SUPPORTED:
printk(KERN_ERR
"PnPBIOS: %s: function not supported on this system\n",
module);
break;
case PNP_INVALID_HANDLE:
printk(KERN_ERR "PnPBIOS: %s: invalid handle\n", module);
break;
case PNP_BAD_PARAMETER:
printk(KERN_ERR "PnPBIOS: %s: invalid parameters were passed\n",
module);
break;
case PNP_SET_FAILED:
printk(KERN_ERR "PnPBIOS: %s: unable to set resources\n",
module);
break;
case PNP_EVENTS_NOT_PENDING:
printk(KERN_ERR "PnPBIOS: %s: no events are pending\n", module);
break;
case PNP_SYSTEM_NOT_DOCKED:
printk(KERN_ERR "PnPBIOS: %s: the system is not docked\n",
module);
break;
case PNP_NO_ISA_PNP_CARDS:
printk(KERN_ERR
"PnPBIOS: %s: no isapnp cards are installed on this system\n",
module);
break;
case PNP_UNABLE_TO_DETERMINE_DOCK_CAPABILITIES:
printk(KERN_ERR
"PnPBIOS: %s: cannot determine the capabilities of the docking station\n",
module);
break;
case PNP_CONFIG_CHANGE_FAILED_NO_BATTERY:
printk(KERN_ERR
"PnPBIOS: %s: unable to undock, the system does not have a battery\n",
module);
break;
case PNP_CONFIG_CHANGE_FAILED_RESOURCE_CONFLICT:
printk(KERN_ERR
"PnPBIOS: %s: could not dock due to resource conflicts\n",
module);
break;
case PNP_BUFFER_TOO_SMALL:
printk(KERN_ERR "PnPBIOS: %s: the buffer passed is too small\n",
module);
break;
case PNP_USE_ESCD_SUPPORT:
printk(KERN_ERR "PnPBIOS: %s: use ESCD instead\n", module);
break;
case PNP_MESSAGE_NOT_SUPPORTED:
printk(KERN_ERR "PnPBIOS: %s: the message is unsupported\n",
module);
break;
case PNP_HARDWARE_ERROR:
printk(KERN_ERR "PnPBIOS: %s: a hardware failure has occurred\n",
module);
break;
default:
printk(KERN_ERR "PnPBIOS: %s: unexpected status 0x%x\n", module,
status);
break;
}
}
/*
* PnP BIOS Low Level Calls
*/
#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"
*/
static int __pnp_bios_dev_node_info(struct pnp_dev_node_info *data)
{
u16 status;
if (!pnp_bios_present())
return PNP_FUNCTION_NOT_SUPPORTED;
status = call_pnp_bios(PNP_GET_NUM_SYS_DEV_NODES, 0, PNP_TS1, 2,
PNP_TS1, PNP_DS, 0, 0, data,
sizeof(struct pnp_dev_node_info), NULL, 0);
data->no_nodes &= 0xff;
return status;
}
int pnp_bios_dev_node_info(struct pnp_dev_node_info *data)
{
int status = __pnp_bios_dev_node_info(data);
if (status)
pnpbios_print_status("dev_node_info", status);
return status;
}
/*
* Note that some PnP BIOSes (e.g., on Sony Vaio laptops) die a horrible
* death if they are asked to access the "current" configuration.
* Therefore, if it's a matter of indifference, it's better to call
* get_dev_node() and set_dev_node() with boot=1 rather than with boot=0.
*/
/*
* 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(u8 *nodenum, char boot,
struct pnp_bios_node *data)
{
u16 status;
u16 tmp_nodenum;
if (!pnp_bios_present())
return PNP_FUNCTION_NOT_SUPPORTED;
if (!boot && pnpbios_dont_use_current_config)
return PNP_FUNCTION_NOT_SUPPORTED;
tmp_nodenum = *nodenum;
status = call_pnp_bios(PNP_GET_SYS_DEV_NODE, 0, PNP_TS1, 0, PNP_TS2,
boot ? 2 : 1, PNP_DS, 0, &tmp_nodenum,
sizeof(tmp_nodenum), data, 65536);
*nodenum = tmp_nodenum;
return status;
}
int pnp_bios_get_dev_node(u8 *nodenum, char boot, struct pnp_bios_node *data)
{
int status;
status = __pnp_bios_get_dev_node(nodenum, boot, data);
if (status)
pnpbios_print_status("get_dev_node", status);
return status;
}
/*
* Call PnP BIOS with function 0x02, "set system device node"
* Input: *nodenum = desired node,
* boot = whether to set nonvolatile boot (!=0)
* or volatile current (0) config
*/
static int __pnp_bios_set_dev_node(u8 nodenum, char boot,
struct pnp_bios_node *data)
{
u16 status;
if (!pnp_bios_present())
return PNP_FUNCTION_NOT_SUPPORTED;
if (!boot && pnpbios_dont_use_current_config)
return PNP_FUNCTION_NOT_SUPPORTED;
status = call_pnp_bios(PNP_SET_SYS_DEV_NODE, nodenum, 0, PNP_TS1,
boot ? 2 : 1, PNP_DS, 0, 0, data, 65536, NULL,
0);
return status;
}
int pnp_bios_set_dev_node(u8 nodenum, char boot, struct pnp_bios_node *data)
{
int status;
status = __pnp_bios_set_dev_node(nodenum, boot, data);
if (status) {
pnpbios_print_status("set_dev_node", status);
return status;
}
if (!boot) { /* Update devlist */
status = pnp_bios_get_dev_node(&nodenum, boot, data);
if (status)
return status;
}
return status;
}
/*
* Call PnP BIOS with function 0x05, "get docking station information"
*/
int pnp_bios_dock_station_info(struct pnp_docking_station_info *data)
{
u16 status;
if (!pnp_bios_present())
return PNP_FUNCTION_NOT_SUPPORTED;
status = call_pnp_bios(PNP_GET_DOCKING_STATION_INFORMATION, 0, PNP_TS1,
PNP_DS, 0, 0, 0, 0, data,
sizeof(struct pnp_docking_station_info), NULL,
0);
return status;
}
/*
* Call PnP BIOS with function 0x0a, "get statically allocated resource
* information"
*/
static int __pnp_bios_get_stat_res(char *info)
{
u16 status;
if (!pnp_bios_present())
return PNP_FUNCTION_NOT_SUPPORTED;
status = call_pnp_bios(PNP_GET_STATIC_ALLOCED_RES_INFO, 0, PNP_TS1,
PNP_DS, 0, 0, 0, 0, info, 65536, NULL, 0);
return status;
}
int pnp_bios_get_stat_res(char *info)
{
int status;
status = __pnp_bios_get_stat_res(info);
if (status)
pnpbios_print_status("get_stat_res", status);
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)
{
u16 status;
if (!pnp_bios_present())
return PNP_FUNCTION_NOT_SUPPORTED;
status = call_pnp_bios(PNP_GET_PNP_ISA_CONFIG_STRUC, 0, PNP_TS1, PNP_DS,
0, 0, 0, 0, data,
sizeof(struct pnp_isa_config_struc), NULL, 0);
return status;
}
int pnp_bios_isapnp_config(struct pnp_isa_config_struc *data)
{
int status;
status = __pnp_bios_isapnp_config(data);
if (status)
pnpbios_print_status("isapnp_config", status);
return status;
}
/*
* Call PnP BIOS with function 0x41, "get ESCD info"
*/
static int __pnp_bios_escd_info(struct escd_info_struc *data)
{
u16 status;
if (!pnp_bios_present())
return ESCD_FUNCTION_NOT_SUPPORTED;
status = call_pnp_bios(PNP_GET_ESCD_INFO, 0, PNP_TS1, 2, PNP_TS1, 4,
PNP_TS1, PNP_DS, data,
sizeof(struct escd_info_struc), NULL, 0);
return status;
}
int pnp_bios_escd_info(struct escd_info_struc *data)
{
int status;
status = __pnp_bios_escd_info(data);
if (status)
pnpbios_print_status("escd_info", status);
return status;
}
/*
* Call PnP BIOS function 0x42, "read ESCD"
* nvram_base is determined by calling escd_info
*/
static int __pnp_bios_read_escd(char *data, u32 nvram_base)
{
u16 status;
if (!pnp_bios_present())
return ESCD_FUNCTION_NOT_SUPPORTED;
status = call_pnp_bios(PNP_READ_ESCD, 0, PNP_TS1, PNP_TS2, PNP_DS, 0, 0,
0, data, 65536, __va(nvram_base), 65536);
return status;
}
int pnp_bios_read_escd(char *data, u32 nvram_base)
{
int status;
status = __pnp_bios_read_escd(data, nvram_base);
if (status)
pnpbios_print_status("read_escd", status);
return status;
}
void pnpbios_calls_init(union pnp_bios_install_struct *header)
{
int i;
spin_lock_init(&pnp_bios_lock);
pnp_bios_callpoint.offset = header->fields.pm16offset;
pnp_bios_callpoint.segment = PNP_CS16;
for_each_possible_cpu(i) {
struct desc_struct *gdt = get_cpu_gdt_table(i);
if (!gdt)
continue;
set_desc_base(&gdt[GDT_ENTRY_PNPBIOS_CS32],
(unsigned long)&pnp_bios_callfunc);
set_desc_base(&gdt[GDT_ENTRY_PNPBIOS_CS16],
(unsigned long)__va(header->fields.pm16cseg));
set_desc_base(&gdt[GDT_ENTRY_PNPBIOS_DS],
(unsigned long)__va(header->fields.pm16dseg));
}
}

590
drivers/pnp/pnpbios/core.c Normal file
View file

@ -0,0 +1,590 @@
/*
* pnpbios -- PnP BIOS driver
*
* This driver provides access to Plug-'n'-Play services provided by
* the PnP BIOS firmware, described in the following documents:
* Plug and Play BIOS Specification, Version 1.0A, 5 May 1994
* Plug and Play BIOS Clarification Paper, 6 October 1994
* Compaq Computer Corporation, Phoenix Technologies Ltd., Intel Corp.
*
* Originally (C) 1998 Christian Schmidt <schmidt@digadd.de>
* Modifications (C) 1998 Tom Lees <tom@lpsg.demon.co.uk>
* Minor reorganizations by David Hinds <dahinds@users.sourceforge.net>
* Further modifications (C) 2001, 2002 by:
* Alan Cox <alan@redhat.com>
* Thomas Hood
* Brian Gerst <bgerst@didntduck.org>
*
* Ported to the PnP Layer and several additional improvements (C) 2002
* by Adam Belay <ambx1@neo.rr.com>
*
* 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
*/
/* Change Log
*
* Adam Belay - <ambx1@neo.rr.com> - March 16, 2003
* rev 1.01 Only call pnp_bios_dev_node_info once
* Added pnpbios_print_status
* Added several new error messages and info messages
* Added pnpbios_interface_attach_device
* integrated core and proc init system
* Introduced PNPMODE flags
* Removed some useless includes
*/
#include <linux/types.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/linkage.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/pnp.h>
#include <linux/mm.h>
#include <linux/smp.h>
#include <linux/slab.h>
#include <linux/completion.h>
#include <linux/spinlock.h>
#include <linux/dmi.h>
#include <linux/delay.h>
#include <linux/acpi.h>
#include <linux/freezer.h>
#include <linux/kthread.h>
#include <asm/page.h>
#include <asm/desc.h>
#include <asm/byteorder.h>
#include "../base.h"
#include "pnpbios.h"
/*
*
* PnP BIOS INTERFACE
*
*/
static union pnp_bios_install_struct *pnp_bios_install = NULL;
int pnp_bios_present(void)
{
return (pnp_bios_install != NULL);
}
struct pnp_dev_node_info node_info;
/*
*
* DOCKING FUNCTIONS
*
*/
static struct completion unload_sem;
/*
* (Much of this belongs in a shared routine somewhere)
*/
static int pnp_dock_event(int dock, struct pnp_docking_station_info *info)
{
char *argv[3], **envp, *buf, *scratch;
int i = 0, value;
if (!(envp = kcalloc(20, sizeof(char *), GFP_KERNEL)))
return -ENOMEM;
if (!(buf = kzalloc(256, GFP_KERNEL))) {
kfree(envp);
return -ENOMEM;
}
/* FIXME: if there are actual users of this, it should be
* integrated into the driver core and use the usual infrastructure
* like sysfs and uevents
*/
argv[0] = "/sbin/pnpbios";
argv[1] = "dock";
argv[2] = NULL;
/* minimal command environment */
envp[i++] = "HOME=/";
envp[i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
#ifdef DEBUG
/* hint that policy agent should enter no-stdout debug mode */
envp[i++] = "DEBUG=kernel";
#endif
/* extensible set of named bus-specific parameters,
* supporting multiple driver selection algorithms.
*/
scratch = buf;
/* action: add, remove */
envp[i++] = scratch;
scratch += sprintf(scratch, "ACTION=%s", dock ? "add" : "remove") + 1;
/* Report the ident for the dock */
envp[i++] = scratch;
scratch += sprintf(scratch, "DOCK=%x/%x/%x",
info->location_id, info->serial, info->capabilities);
envp[i] = NULL;
value = call_usermodehelper(argv [0], argv, envp, UMH_WAIT_EXEC);
kfree(buf);
kfree(envp);
return 0;
}
/*
* Poll the PnP docking at regular intervals
*/
static int pnp_dock_thread(void *unused)
{
static struct pnp_docking_station_info now;
int docked = -1, d = 0;
set_freezable();
while (1) {
int status;
/*
* Poll every 2 seconds
*/
msleep_interruptible(2000);
if (try_to_freeze())
continue;
status = pnp_bios_dock_station_info(&now);
switch (status) {
/*
* No dock to manage
*/
case PNP_FUNCTION_NOT_SUPPORTED:
complete_and_exit(&unload_sem, 0);
case PNP_SYSTEM_NOT_DOCKED:
d = 0;
break;
case PNP_SUCCESS:
d = 1;
break;
default:
pnpbios_print_status("pnp_dock_thread", status);
continue;
}
if (d != docked) {
if (pnp_dock_event(d, &now) == 0) {
docked = d;
#if 0
printk(KERN_INFO
"PnPBIOS: Docking station %stached\n",
docked ? "at" : "de");
#endif
}
}
}
complete_and_exit(&unload_sem, 0);
}
static int pnpbios_get_resources(struct pnp_dev *dev)
{
u8 nodenum = dev->number;
struct pnp_bios_node *node;
if (!pnpbios_is_dynamic(dev))
return -EPERM;
pnp_dbg(&dev->dev, "get resources\n");
node = kzalloc(node_info.max_node_size, GFP_KERNEL);
if (!node)
return -1;
if (pnp_bios_get_dev_node(&nodenum, (char)PNPMODE_DYNAMIC, node)) {
kfree(node);
return -ENODEV;
}
pnpbios_read_resources_from_node(dev, node);
dev->active = pnp_is_active(dev);
kfree(node);
return 0;
}
static int pnpbios_set_resources(struct pnp_dev *dev)
{
u8 nodenum = dev->number;
struct pnp_bios_node *node;
int ret;
if (!pnpbios_is_dynamic(dev))
return -EPERM;
pnp_dbg(&dev->dev, "set resources\n");
node = kzalloc(node_info.max_node_size, GFP_KERNEL);
if (!node)
return -1;
if (pnp_bios_get_dev_node(&nodenum, (char)PNPMODE_DYNAMIC, node)) {
kfree(node);
return -ENODEV;
}
if (pnpbios_write_resources_to_node(dev, node) < 0) {
kfree(node);
return -1;
}
ret = pnp_bios_set_dev_node(node->handle, (char)PNPMODE_DYNAMIC, node);
kfree(node);
if (ret > 0)
ret = -1;
return ret;
}
static void pnpbios_zero_data_stream(struct pnp_bios_node *node)
{
unsigned char *p = (char *)node->data;
unsigned char *end = (char *)(node->data + node->size);
unsigned int len;
int i;
while ((char *)p < (char *)end) {
if (p[0] & 0x80) { /* large tag */
len = (p[2] << 8) | p[1];
p += 3;
} else {
if (((p[0] >> 3) & 0x0f) == 0x0f)
return;
len = p[0] & 0x07;
p += 1;
}
for (i = 0; i < len; i++)
p[i] = 0;
p += len;
}
printk(KERN_ERR
"PnPBIOS: Resource structure did not contain an end tag.\n");
}
static int pnpbios_disable_resources(struct pnp_dev *dev)
{
struct pnp_bios_node *node;
u8 nodenum = dev->number;
int ret;
if (dev->flags & PNPBIOS_NO_DISABLE || !pnpbios_is_dynamic(dev))
return -EPERM;
node = kzalloc(node_info.max_node_size, GFP_KERNEL);
if (!node)
return -ENOMEM;
if (pnp_bios_get_dev_node(&nodenum, (char)PNPMODE_DYNAMIC, node)) {
kfree(node);
return -ENODEV;
}
pnpbios_zero_data_stream(node);
ret = pnp_bios_set_dev_node(dev->number, (char)PNPMODE_DYNAMIC, node);
kfree(node);
if (ret > 0)
ret = -1;
return ret;
}
/* PnP Layer support */
struct pnp_protocol pnpbios_protocol = {
.name = "Plug and Play BIOS",
.get = pnpbios_get_resources,
.set = pnpbios_set_resources,
.disable = pnpbios_disable_resources,
};
static int __init insert_device(struct pnp_bios_node *node)
{
struct list_head *pos;
struct pnp_dev *dev;
char id[8];
int error;
/* check if the device is already added */
list_for_each(pos, &pnpbios_protocol.devices) {
dev = list_entry(pos, struct pnp_dev, protocol_list);
if (dev->number == node->handle)
return -EEXIST;
}
pnp_eisa_id_to_string(node->eisa_id & PNP_EISA_ID_MASK, id);
dev = pnp_alloc_dev(&pnpbios_protocol, node->handle, id);
if (!dev)
return -ENOMEM;
pnpbios_parse_data_stream(dev, node);
dev->active = pnp_is_active(dev);
dev->flags = node->flags;
if (!(dev->flags & PNPBIOS_NO_CONFIG))
dev->capabilities |= PNP_CONFIGURABLE;
if (!(dev->flags & PNPBIOS_NO_DISABLE) && pnpbios_is_dynamic(dev))
dev->capabilities |= PNP_DISABLE;
dev->capabilities |= PNP_READ;
if (pnpbios_is_dynamic(dev))
dev->capabilities |= PNP_WRITE;
if (dev->flags & PNPBIOS_REMOVABLE)
dev->capabilities |= PNP_REMOVABLE;
/* clear out the damaged flags */
if (!dev->active)
pnp_init_resources(dev);
error = pnp_add_device(dev);
if (error) {
put_device(&dev->dev);
return error;
}
pnpbios_interface_attach_device(node);
return 0;
}
static void __init build_devlist(void)
{
u8 nodenum;
unsigned int nodes_got = 0;
unsigned int devs = 0;
struct pnp_bios_node *node;
node = kzalloc(node_info.max_node_size, GFP_KERNEL);
if (!node)
return;
for (nodenum = 0; nodenum < 0xff;) {
u8 thisnodenum = nodenum;
/* eventually we will want to use PNPMODE_STATIC here but for now
* dynamic will help us catch buggy bioses to add to the blacklist.
*/
if (!pnpbios_dont_use_current_config) {
if (pnp_bios_get_dev_node
(&nodenum, (char)PNPMODE_DYNAMIC, node))
break;
} else {
if (pnp_bios_get_dev_node
(&nodenum, (char)PNPMODE_STATIC, node))
break;
}
nodes_got++;
if (insert_device(node) == 0)
devs++;
if (nodenum <= thisnodenum) {
printk(KERN_ERR
"PnPBIOS: build_devlist: Node number 0x%x is out of sequence following node 0x%x. Aborting.\n",
(unsigned int)nodenum,
(unsigned int)thisnodenum);
break;
}
}
kfree(node);
printk(KERN_INFO
"PnPBIOS: %i node%s reported by PnP BIOS; %i recorded by driver\n",
nodes_got, nodes_got != 1 ? "s" : "", devs);
}
/*
*
* INIT AND EXIT
*
*/
static int pnpbios_disabled;
int pnpbios_dont_use_current_config;
static int __init pnpbios_setup(char *str)
{
int invert;
while ((str != NULL) && (*str != '\0')) {
if (strncmp(str, "off", 3) == 0)
pnpbios_disabled = 1;
if (strncmp(str, "on", 2) == 0)
pnpbios_disabled = 0;
invert = (strncmp(str, "no-", 3) == 0);
if (invert)
str += 3;
if (strncmp(str, "curr", 4) == 0)
pnpbios_dont_use_current_config = invert;
str = strchr(str, ',');
if (str != NULL)
str += strspn(str, ", \t");
}
return 1;
}
__setup("pnpbios=", pnpbios_setup);
/* PnP BIOS signature: "$PnP" */
#define PNP_SIGNATURE (('$' << 0) + ('P' << 8) + ('n' << 16) + ('P' << 24))
static int __init pnpbios_probe_system(void)
{
union pnp_bios_install_struct *check;
u8 sum;
int length, i;
printk(KERN_INFO "PnPBIOS: Scanning system for PnP BIOS support...\n");
/*
* 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 (check = (union pnp_bios_install_struct *)__va(0xf0000);
check < (union pnp_bios_install_struct *)__va(0xffff0);
check = (void *)check + 16) {
if (check->fields.signature != PNP_SIGNATURE)
continue;
printk(KERN_INFO
"PnPBIOS: Found PnP BIOS installation structure at 0x%p\n",
check);
length = check->fields.length;
if (!length) {
printk(KERN_ERR
"PnPBIOS: installation structure is invalid, skipping\n");
continue;
}
for (sum = 0, i = 0; i < length; i++)
sum += check->chars[i];
if (sum) {
printk(KERN_ERR
"PnPBIOS: installation structure is corrupted, skipping\n");
continue;
}
if (check->fields.version < 0x10) {
printk(KERN_WARNING
"PnPBIOS: PnP BIOS version %d.%d is not supported\n",
check->fields.version >> 4,
check->fields.version & 15);
continue;
}
printk(KERN_INFO
"PnPBIOS: PnP BIOS version %d.%d, entry 0x%x:0x%x, dseg 0x%x\n",
check->fields.version >> 4, check->fields.version & 15,
check->fields.pm16cseg, check->fields.pm16offset,
check->fields.pm16dseg);
pnp_bios_install = check;
return 1;
}
printk(KERN_INFO "PnPBIOS: PnP BIOS support was not detected.\n");
return 0;
}
static int __init exploding_pnp_bios(const struct dmi_system_id *d)
{
printk(KERN_WARNING "%s detected. Disabling PnPBIOS\n", d->ident);
return 0;
}
static struct dmi_system_id pnpbios_dmi_table[] __initdata = {
{ /* PnPBIOS GPF on boot */
.callback = exploding_pnp_bios,
.ident = "Higraded P14H",
.matches = {
DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."),
DMI_MATCH(DMI_BIOS_VERSION, "07.00T"),
DMI_MATCH(DMI_SYS_VENDOR, "Higraded"),
DMI_MATCH(DMI_PRODUCT_NAME, "P14H"),
},
},
{ /* PnPBIOS GPF on boot */
.callback = exploding_pnp_bios,
.ident = "ASUS P4P800",
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK Computer Inc."),
DMI_MATCH(DMI_BOARD_NAME, "P4P800"),
},
},
{}
};
static int __init pnpbios_init(void)
{
int ret;
if (pnpbios_disabled || dmi_check_system(pnpbios_dmi_table) ||
paravirt_enabled()) {
printk(KERN_INFO "PnPBIOS: Disabled\n");
return -ENODEV;
}
#ifdef CONFIG_PNPACPI
if (!acpi_disabled && !pnpacpi_disabled) {
pnpbios_disabled = 1;
printk(KERN_INFO "PnPBIOS: Disabled by ACPI PNP\n");
return -ENODEV;
}
#endif /* CONFIG_ACPI */
/* scan the system for pnpbios support */
if (!pnpbios_probe_system())
return -ENODEV;
/* make preparations for bios calls */
pnpbios_calls_init(pnp_bios_install);
/* read the node info */
ret = pnp_bios_dev_node_info(&node_info);
if (ret) {
printk(KERN_ERR
"PnPBIOS: Unable to get node info. Aborting.\n");
return ret;
}
/* register with the pnp layer */
ret = pnp_register_protocol(&pnpbios_protocol);
if (ret) {
printk(KERN_ERR
"PnPBIOS: Unable to register driver. Aborting.\n");
return ret;
}
/* start the proc interface */
ret = pnpbios_proc_init();
if (ret)
printk(KERN_ERR "PnPBIOS: Failed to create proc interface.\n");
/* scan for pnpbios devices */
build_devlist();
pnp_platform_devices = 1;
return 0;
}
fs_initcall(pnpbios_init);
static int __init pnpbios_thread_init(void)
{
struct task_struct *task;
if (pnpbios_disabled)
return 0;
init_completion(&unload_sem);
task = kthread_run(pnp_dock_thread, NULL, "kpnpbiosd");
if (IS_ERR(task))
return PTR_ERR(task);
return 0;
}
/* Start the kernel thread later: */
module_init(pnpbios_thread_init);
EXPORT_SYMBOL(pnpbios_protocol);

View file

@ -0,0 +1,182 @@
/*
* pnpbios.h - contains local definitions
*/
/*
* Include file for the interface to a PnP BIOS
*
* Original BIOS code (C) 1998 Christian Schmidt (chr.schmidt@tu-bs.de)
* PnP handler parts (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
*/
/*
* Return codes
*/
#define PNP_SUCCESS 0x00
#define PNP_NOT_SET_STATICALLY 0x7f
#define PNP_UNKNOWN_FUNCTION 0x81
#define PNP_FUNCTION_NOT_SUPPORTED 0x82
#define PNP_INVALID_HANDLE 0x83
#define PNP_BAD_PARAMETER 0x84
#define PNP_SET_FAILED 0x85
#define PNP_EVENTS_NOT_PENDING 0x86
#define PNP_SYSTEM_NOT_DOCKED 0x87
#define PNP_NO_ISA_PNP_CARDS 0x88
#define PNP_UNABLE_TO_DETERMINE_DOCK_CAPABILITIES 0x89
#define PNP_CONFIG_CHANGE_FAILED_NO_BATTERY 0x8a
#define PNP_CONFIG_CHANGE_FAILED_RESOURCE_CONFLICT 0x8b
#define PNP_BUFFER_TOO_SMALL 0x8c
#define PNP_USE_ESCD_SUPPORT 0x8d
#define PNP_MESSAGE_NOT_SUPPORTED 0x8e
#define PNP_HARDWARE_ERROR 0x8f
#define ESCD_SUCCESS 0x00
#define ESCD_IO_ERROR_READING 0x55
#define ESCD_INVALID 0x56
#define ESCD_BUFFER_TOO_SMALL 0x59
#define ESCD_NVRAM_TOO_SMALL 0x5a
#define ESCD_FUNCTION_NOT_SUPPORTED 0x81
/*
* Events that can be received by "get event"
*/
#define PNPEV_ABOUT_TO_CHANGE_CONFIG 0x0001
#define PNPEV_DOCK_CHANGED 0x0002
#define PNPEV_SYSTEM_DEVICE_CHANGED 0x0003
#define PNPEV_CONFIG_CHANGED_FAILED 0x0004
#define PNPEV_UNKNOWN_SYSTEM_EVENT 0xffff
/* 0x8000 through 0xfffe are OEM defined */
/*
* Messages that should be sent through "send message"
*/
#define PNPMSG_OK 0x00
#define PNPMSG_ABORT 0x01
#define PNPMSG_UNDOCK_DEFAULT_ACTION 0x40
#define PNPMSG_POWER_OFF 0x41
#define PNPMSG_PNP_OS_ACTIVE 0x42
#define PNPMSG_PNP_OS_INACTIVE 0x43
/*
* Plug and Play BIOS flags
*/
#define PNPBIOS_NO_DISABLE 0x0001
#define PNPBIOS_NO_CONFIG 0x0002
#define PNPBIOS_OUTPUT 0x0004
#define PNPBIOS_INPUT 0x0008
#define PNPBIOS_BOOTABLE 0x0010
#define PNPBIOS_DOCK 0x0020
#define PNPBIOS_REMOVABLE 0x0040
#define pnpbios_is_static(x) (((x)->flags & 0x0100) == 0x0000)
#define pnpbios_is_dynamic(x) ((x)->flags & 0x0080)
/*
* Function Parameters
*/
#define PNPMODE_STATIC 1
#define PNPMODE_DYNAMIC 0
/* 0x8000 through 0xffff are OEM defined */
#pragma pack(1)
struct pnp_dev_node_info {
__u16 no_nodes;
__u16 max_node_size;
};
struct pnp_docking_station_info {
__u32 location_id;
__u32 serial;
__u16 capabilities;
};
struct pnp_isa_config_struc {
__u8 revision;
__u8 no_csns;
__u16 isa_rd_data_port;
__u16 reserved;
};
struct escd_info_struc {
__u16 min_escd_write_size;
__u16 escd_size;
__u32 nv_storage_base;
};
struct pnp_bios_node {
__u16 size;
__u8 handle;
__u32 eisa_id;
__u8 type_code[3];
__u16 flags;
__u8 data[0];
};
#pragma pack()
/* non-exported */
extern struct pnp_dev_node_info node_info;
extern int pnp_bios_dev_node_info(struct pnp_dev_node_info *data);
extern int pnp_bios_get_dev_node(u8 *nodenum, char config,
struct pnp_bios_node *data);
extern int pnp_bios_set_dev_node(u8 nodenum, char config,
struct pnp_bios_node *data);
extern int pnp_bios_get_stat_res(char *info);
extern int pnp_bios_isapnp_config(struct pnp_isa_config_struc *data);
extern int pnp_bios_escd_info(struct escd_info_struc *data);
extern int pnp_bios_read_escd(char *data, u32 nvram_base);
extern int pnp_bios_dock_station_info(struct pnp_docking_station_info *data);
#pragma pack(1)
union pnp_bios_install_struct {
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()
extern int pnp_bios_present(void);
extern int pnpbios_dont_use_current_config;
extern int pnpbios_parse_data_stream(struct pnp_dev *dev, struct pnp_bios_node * node);
extern int pnpbios_read_resources_from_node(struct pnp_dev *dev, struct pnp_bios_node *node);
extern int pnpbios_write_resources_to_node(struct pnp_dev *dev, struct pnp_bios_node *node);
extern void pnpid32_to_pnpid(u32 id, char *str);
extern void pnpbios_print_status(const char * module, u16 status);
extern void pnpbios_calls_init(union pnp_bios_install_struct * header);
#ifdef CONFIG_PNPBIOS_PROC_FS
extern int pnpbios_interface_attach_device(struct pnp_bios_node * node);
extern int pnpbios_proc_init (void);
extern void pnpbios_proc_exit (void);
#else
static inline int pnpbios_interface_attach_device(struct pnp_bios_node * node) { return 0; }
static inline int pnpbios_proc_init (void) { return 0; }
static inline void pnpbios_proc_exit (void) { ; }
#endif /* CONFIG_PNPBIOS_PROC_FS */

350
drivers/pnp/pnpbios/proc.c Normal file
View file

@ -0,0 +1,350 @@
/*
* /proc/bus/pnp interface for Plug and Play devices
*
* Written by David Hinds, dahinds@users.sourceforge.net
* Modified by Thomas Hood
*
* The .../devices and .../<node> and .../boot/<node> files are
* utilized by the lspnp and setpnp utilities, supplied with the
* pcmcia-cs package.
* http://pcmcia-cs.sourceforge.net
*
* The .../escd file is utilized by the lsescd utility written by
* Gunther Mayer.
*
* The .../legacy_device_resources file is not used yet.
*
* The other files are human-readable.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/proc_fs.h>
#include <linux/pnp.h>
#include <linux/seq_file.h>
#include <linux/init.h>
#include <asm/uaccess.h>
#include "pnpbios.h"
static struct proc_dir_entry *proc_pnp = NULL;
static struct proc_dir_entry *proc_pnp_boot = NULL;
static int pnpconfig_proc_show(struct seq_file *m, void *v)
{
struct pnp_isa_config_struc pnps;
if (pnp_bios_isapnp_config(&pnps))
return -EIO;
seq_printf(m, "structure_revision %d\n"
"number_of_CSNs %d\n"
"ISA_read_data_port 0x%x\n",
pnps.revision, pnps.no_csns, pnps.isa_rd_data_port);
return 0;
}
static int pnpconfig_proc_open(struct inode *inode, struct file *file)
{
return single_open(file, pnpconfig_proc_show, NULL);
}
static const struct file_operations pnpconfig_proc_fops = {
.owner = THIS_MODULE,
.open = pnpconfig_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int escd_info_proc_show(struct seq_file *m, void *v)
{
struct escd_info_struc escd;
if (pnp_bios_escd_info(&escd))
return -EIO;
seq_printf(m, "min_ESCD_write_size %d\n"
"ESCD_size %d\n"
"NVRAM_base 0x%x\n",
escd.min_escd_write_size,
escd.escd_size, escd.nv_storage_base);
return 0;
}
static int escd_info_proc_open(struct inode *inode, struct file *file)
{
return single_open(file, escd_info_proc_show, NULL);
}
static const struct file_operations escd_info_proc_fops = {
.owner = THIS_MODULE,
.open = escd_info_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
#define MAX_SANE_ESCD_SIZE (32*1024)
static int escd_proc_show(struct seq_file *m, void *v)
{
struct escd_info_struc escd;
char *tmpbuf;
int escd_size;
if (pnp_bios_escd_info(&escd))
return -EIO;
/* sanity check */
if (escd.escd_size > MAX_SANE_ESCD_SIZE) {
printk(KERN_ERR
"PnPBIOS: %s: ESCD size reported by BIOS escd_info call is too great\n", __func__);
return -EFBIG;
}
tmpbuf = kzalloc(escd.escd_size, GFP_KERNEL);
if (!tmpbuf)
return -ENOMEM;
if (pnp_bios_read_escd(tmpbuf, escd.nv_storage_base)) {
kfree(tmpbuf);
return -EIO;
}
escd_size =
(unsigned char)(tmpbuf[0]) + (unsigned char)(tmpbuf[1]) * 256;
/* sanity check */
if (escd_size > MAX_SANE_ESCD_SIZE) {
printk(KERN_ERR "PnPBIOS: %s: ESCD size reported by"
" BIOS read_escd call is too great\n", __func__);
kfree(tmpbuf);
return -EFBIG;
}
seq_write(m, tmpbuf, escd_size);
kfree(tmpbuf);
return 0;
}
static int escd_proc_open(struct inode *inode, struct file *file)
{
return single_open(file, escd_proc_show, NULL);
}
static const struct file_operations escd_proc_fops = {
.owner = THIS_MODULE,
.open = escd_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int pnp_legacyres_proc_show(struct seq_file *m, void *v)
{
void *buf;
buf = kmalloc(65536, GFP_KERNEL);
if (!buf)
return -ENOMEM;
if (pnp_bios_get_stat_res(buf)) {
kfree(buf);
return -EIO;
}
seq_write(m, buf, 65536);
kfree(buf);
return 0;
}
static int pnp_legacyres_proc_open(struct inode *inode, struct file *file)
{
return single_open(file, pnp_legacyres_proc_show, NULL);
}
static const struct file_operations pnp_legacyres_proc_fops = {
.owner = THIS_MODULE,
.open = pnp_legacyres_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int pnp_devices_proc_show(struct seq_file *m, void *v)
{
struct pnp_bios_node *node;
u8 nodenum;
node = kzalloc(node_info.max_node_size, GFP_KERNEL);
if (!node)
return -ENOMEM;
for (nodenum = 0; nodenum < 0xff;) {
u8 thisnodenum = nodenum;
if (pnp_bios_get_dev_node(&nodenum, PNPMODE_DYNAMIC, node))
break;
seq_printf(m, "%02x\t%08x\t%3phC\t%04x\n",
node->handle, node->eisa_id,
node->type_code, node->flags);
if (nodenum <= thisnodenum) {
printk(KERN_ERR
"%s Node number 0x%x is out of sequence following node 0x%x. Aborting.\n",
"PnPBIOS: proc_read_devices:",
(unsigned int)nodenum,
(unsigned int)thisnodenum);
break;
}
}
kfree(node);
return 0;
}
static int pnp_devices_proc_open(struct inode *inode, struct file *file)
{
return single_open(file, pnp_devices_proc_show, NULL);
}
static const struct file_operations pnp_devices_proc_fops = {
.owner = THIS_MODULE,
.open = pnp_devices_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int pnpbios_proc_show(struct seq_file *m, void *v)
{
void *data = m->private;
struct pnp_bios_node *node;
int boot = (long)data >> 8;
u8 nodenum = (long)data;
int len;
node = kzalloc(node_info.max_node_size, GFP_KERNEL);
if (!node)
return -ENOMEM;
if (pnp_bios_get_dev_node(&nodenum, boot, node)) {
kfree(node);
return -EIO;
}
len = node->size - sizeof(struct pnp_bios_node);
seq_write(m, node->data, len);
kfree(node);
return 0;
}
static int pnpbios_proc_open(struct inode *inode, struct file *file)
{
return single_open(file, pnpbios_proc_show, PDE_DATA(inode));
}
static ssize_t pnpbios_proc_write(struct file *file, const char __user *buf,
size_t count, loff_t *pos)
{
void *data = PDE_DATA(file_inode(file));
struct pnp_bios_node *node;
int boot = (long)data >> 8;
u8 nodenum = (long)data;
int ret = count;
node = kzalloc(node_info.max_node_size, GFP_KERNEL);
if (!node)
return -ENOMEM;
if (pnp_bios_get_dev_node(&nodenum, boot, node)) {
ret = -EIO;
goto out;
}
if (count != node->size - sizeof(struct pnp_bios_node)) {
ret = -EINVAL;
goto out;
}
if (copy_from_user(node->data, buf, count)) {
ret = -EFAULT;
goto out;
}
if (pnp_bios_set_dev_node(node->handle, boot, node) != 0) {
ret = -EINVAL;
goto out;
}
ret = count;
out:
kfree(node);
return ret;
}
static const struct file_operations pnpbios_proc_fops = {
.owner = THIS_MODULE,
.open = pnpbios_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
.write = pnpbios_proc_write,
};
int pnpbios_interface_attach_device(struct pnp_bios_node *node)
{
char name[3];
sprintf(name, "%02x", node->handle);
if (!proc_pnp)
return -EIO;
if (!pnpbios_dont_use_current_config) {
proc_create_data(name, 0644, proc_pnp, &pnpbios_proc_fops,
(void *)(long)(node->handle));
}
if (!proc_pnp_boot)
return -EIO;
if (proc_create_data(name, 0644, proc_pnp_boot, &pnpbios_proc_fops,
(void *)(long)(node->handle + 0x100)))
return 0;
return -EIO;
}
/*
* When this is called, pnpbios functions are assumed to
* work and the pnpbios_dont_use_current_config flag
* should already have been set to the appropriate value
*/
int __init pnpbios_proc_init(void)
{
proc_pnp = proc_mkdir("bus/pnp", NULL);
if (!proc_pnp)
return -EIO;
proc_pnp_boot = proc_mkdir("boot", proc_pnp);
if (!proc_pnp_boot)
return -EIO;
proc_create("devices", 0, proc_pnp, &pnp_devices_proc_fops);
proc_create("configuration_info", 0, proc_pnp, &pnpconfig_proc_fops);
proc_create("escd_info", 0, proc_pnp, &escd_info_proc_fops);
proc_create("escd", S_IRUSR, proc_pnp, &escd_proc_fops);
proc_create("legacy_device_resources", 0, proc_pnp, &pnp_legacyres_proc_fops);
return 0;
}
void __exit pnpbios_proc_exit(void)
{
int i;
char name[3];
if (!proc_pnp)
return;
for (i = 0; i < 0xff; i++) {
sprintf(name, "%02x", i);
if (!pnpbios_dont_use_current_config)
remove_proc_entry(name, proc_pnp);
remove_proc_entry(name, proc_pnp_boot);
}
remove_proc_entry("legacy_device_resources", proc_pnp);
remove_proc_entry("escd", proc_pnp);
remove_proc_entry("escd_info", proc_pnp);
remove_proc_entry("configuration_info", proc_pnp);
remove_proc_entry("devices", proc_pnp);
remove_proc_entry("boot", proc_pnp);
remove_proc_entry("bus/pnp", NULL);
}

View file

@ -0,0 +1,809 @@
/*
* rsparser.c - parses and encodes pnpbios resource data streams
*/
#include <linux/ctype.h>
#include <linux/pnp.h>
#include <linux/string.h>
#ifdef CONFIG_PCI
#include <linux/pci.h>
#else
inline void pcibios_penalize_isa_irq(int irq, int active)
{
}
#endif /* CONFIG_PCI */
#include "../base.h"
#include "pnpbios.h"
/* standard resource tags */
#define SMALL_TAG_PNPVERNO 0x01
#define SMALL_TAG_LOGDEVID 0x02
#define SMALL_TAG_COMPATDEVID 0x03
#define SMALL_TAG_IRQ 0x04
#define SMALL_TAG_DMA 0x05
#define SMALL_TAG_STARTDEP 0x06
#define SMALL_TAG_ENDDEP 0x07
#define SMALL_TAG_PORT 0x08
#define SMALL_TAG_FIXEDPORT 0x09
#define SMALL_TAG_VENDOR 0x0e
#define SMALL_TAG_END 0x0f
#define LARGE_TAG 0x80
#define LARGE_TAG_MEM 0x81
#define LARGE_TAG_ANSISTR 0x82
#define LARGE_TAG_UNICODESTR 0x83
#define LARGE_TAG_VENDOR 0x84
#define LARGE_TAG_MEM32 0x85
#define LARGE_TAG_FIXEDMEM32 0x86
/*
* Resource Data Stream Format:
*
* Allocated Resources (required)
* end tag ->
* Resource Configuration Options (optional)
* end tag ->
* Compitable Device IDs (optional)
* final end tag ->
*/
/*
* Allocated Resources
*/
static void pnpbios_parse_allocated_ioresource(struct pnp_dev *dev,
int start, int len)
{
int flags = 0;
int end = start + len - 1;
if (len <= 0 || end >= 0x10003)
flags |= IORESOURCE_DISABLED;
pnp_add_io_resource(dev, start, end, flags);
}
static void pnpbios_parse_allocated_memresource(struct pnp_dev *dev,
int start, int len)
{
int flags = 0;
int end = start + len - 1;
if (len <= 0)
flags |= IORESOURCE_DISABLED;
pnp_add_mem_resource(dev, start, end, flags);
}
static unsigned char *pnpbios_parse_allocated_resource_data(struct pnp_dev *dev,
unsigned char *p,
unsigned char *end)
{
unsigned int len, tag;
int io, size, mask, i, flags;
if (!p)
return NULL;
pnp_dbg(&dev->dev, "parse allocated resources\n");
pnp_init_resources(dev);
while ((char *)p < (char *)end) {
/* determine the type of tag */
if (p[0] & LARGE_TAG) { /* large tag */
len = (p[2] << 8) | p[1];
tag = p[0];
} else { /* small tag */
len = p[0] & 0x07;
tag = ((p[0] >> 3) & 0x0f);
}
switch (tag) {
case LARGE_TAG_MEM:
if (len != 9)
goto len_err;
io = *(short *)&p[4];
size = *(short *)&p[10];
pnpbios_parse_allocated_memresource(dev, io, size);
break;
case LARGE_TAG_ANSISTR:
/* ignore this for now */
break;
case LARGE_TAG_VENDOR:
/* do nothing */
break;
case LARGE_TAG_MEM32:
if (len != 17)
goto len_err;
io = *(int *)&p[4];
size = *(int *)&p[16];
pnpbios_parse_allocated_memresource(dev, io, size);
break;
case LARGE_TAG_FIXEDMEM32:
if (len != 9)
goto len_err;
io = *(int *)&p[4];
size = *(int *)&p[8];
pnpbios_parse_allocated_memresource(dev, io, size);
break;
case SMALL_TAG_IRQ:
if (len < 2 || len > 3)
goto len_err;
flags = 0;
io = -1;
mask = p[1] + p[2] * 256;
for (i = 0; i < 16; i++, mask = mask >> 1)
if (mask & 0x01)
io = i;
if (io != -1)
pcibios_penalize_isa_irq(io, 1);
else
flags = IORESOURCE_DISABLED;
pnp_add_irq_resource(dev, io, flags);
break;
case SMALL_TAG_DMA:
if (len != 2)
goto len_err;
flags = 0;
io = -1;
mask = p[1];
for (i = 0; i < 8; i++, mask = mask >> 1)
if (mask & 0x01)
io = i;
if (io == -1)
flags = IORESOURCE_DISABLED;
pnp_add_dma_resource(dev, io, flags);
break;
case SMALL_TAG_PORT:
if (len != 7)
goto len_err;
io = p[2] + p[3] * 256;
size = p[7];
pnpbios_parse_allocated_ioresource(dev, io, size);
break;
case SMALL_TAG_VENDOR:
/* do nothing */
break;
case SMALL_TAG_FIXEDPORT:
if (len != 3)
goto len_err;
io = p[1] + p[2] * 256;
size = p[3];
pnpbios_parse_allocated_ioresource(dev, io, size);
break;
case SMALL_TAG_END:
p = p + 2;
return (unsigned char *)p;
break;
default: /* an unknown tag */
len_err:
dev_err(&dev->dev, "unknown tag %#x length %d\n",
tag, len);
break;
}
/* continue to the next tag */
if (p[0] & LARGE_TAG)
p += len + 3;
else
p += len + 1;
}
dev_err(&dev->dev, "no end tag in resource structure\n");
return NULL;
}
/*
* Resource Configuration Options
*/
static __init void pnpbios_parse_mem_option(struct pnp_dev *dev,
unsigned char *p, int size,
unsigned int option_flags)
{
resource_size_t min, max, align, len;
unsigned char flags;
min = ((p[5] << 8) | p[4]) << 8;
max = ((p[7] << 8) | p[6]) << 8;
align = (p[9] << 8) | p[8];
len = ((p[11] << 8) | p[10]) << 8;
flags = p[3];
pnp_register_mem_resource(dev, option_flags, min, max, align, len,
flags);
}
static __init void pnpbios_parse_mem32_option(struct pnp_dev *dev,
unsigned char *p, int size,
unsigned int option_flags)
{
resource_size_t min, max, align, len;
unsigned char flags;
min = (p[7] << 24) | (p[6] << 16) | (p[5] << 8) | p[4];
max = (p[11] << 24) | (p[10] << 16) | (p[9] << 8) | p[8];
align = (p[15] << 24) | (p[14] << 16) | (p[13] << 8) | p[12];
len = (p[19] << 24) | (p[18] << 16) | (p[17] << 8) | p[16];
flags = p[3];
pnp_register_mem_resource(dev, option_flags, min, max, align, len,
flags);
}
static __init void pnpbios_parse_fixed_mem32_option(struct pnp_dev *dev,
unsigned char *p, int size,
unsigned int option_flags)
{
resource_size_t base, len;
unsigned char flags;
base = (p[7] << 24) | (p[6] << 16) | (p[5] << 8) | p[4];
len = (p[11] << 24) | (p[10] << 16) | (p[9] << 8) | p[8];
flags = p[3];
pnp_register_mem_resource(dev, option_flags, base, base, 0, len, flags);
}
static __init void pnpbios_parse_irq_option(struct pnp_dev *dev,
unsigned char *p, int size,
unsigned int option_flags)
{
unsigned long bits;
pnp_irq_mask_t map;
unsigned char flags = IORESOURCE_IRQ_HIGHEDGE;
bits = (p[2] << 8) | p[1];
bitmap_zero(map.bits, PNP_IRQ_NR);
bitmap_copy(map.bits, &bits, 16);
if (size > 2)
flags = p[3];
pnp_register_irq_resource(dev, option_flags, &map, flags);
}
static __init void pnpbios_parse_dma_option(struct pnp_dev *dev,
unsigned char *p, int size,
unsigned int option_flags)
{
pnp_register_dma_resource(dev, option_flags, p[1], p[2]);
}
static __init void pnpbios_parse_port_option(struct pnp_dev *dev,
unsigned char *p, int size,
unsigned int option_flags)
{
resource_size_t min, max, align, len;
unsigned char flags;
min = (p[3] << 8) | p[2];
max = (p[5] << 8) | p[4];
align = p[6];
len = p[7];
flags = p[1] ? IORESOURCE_IO_16BIT_ADDR : 0;
pnp_register_port_resource(dev, option_flags, min, max, align, len,
flags);
}
static __init void pnpbios_parse_fixed_port_option(struct pnp_dev *dev,
unsigned char *p, int size,
unsigned int option_flags)
{
resource_size_t base, len;
base = (p[2] << 8) | p[1];
len = p[3];
pnp_register_port_resource(dev, option_flags, base, base, 0, len,
IORESOURCE_IO_FIXED);
}
static __init unsigned char *
pnpbios_parse_resource_option_data(unsigned char *p, unsigned char *end,
struct pnp_dev *dev)
{
unsigned int len, tag;
int priority;
unsigned int option_flags;
if (!p)
return NULL;
pnp_dbg(&dev->dev, "parse resource options\n");
option_flags = 0;
while ((char *)p < (char *)end) {
/* determine the type of tag */
if (p[0] & LARGE_TAG) { /* large tag */
len = (p[2] << 8) | p[1];
tag = p[0];
} else { /* small tag */
len = p[0] & 0x07;
tag = ((p[0] >> 3) & 0x0f);
}
switch (tag) {
case LARGE_TAG_MEM:
if (len != 9)
goto len_err;
pnpbios_parse_mem_option(dev, p, len, option_flags);
break;
case LARGE_TAG_MEM32:
if (len != 17)
goto len_err;
pnpbios_parse_mem32_option(dev, p, len, option_flags);
break;
case LARGE_TAG_FIXEDMEM32:
if (len != 9)
goto len_err;
pnpbios_parse_fixed_mem32_option(dev, p, len,
option_flags);
break;
case SMALL_TAG_IRQ:
if (len < 2 || len > 3)
goto len_err;
pnpbios_parse_irq_option(dev, p, len, option_flags);
break;
case SMALL_TAG_DMA:
if (len != 2)
goto len_err;
pnpbios_parse_dma_option(dev, p, len, option_flags);
break;
case SMALL_TAG_PORT:
if (len != 7)
goto len_err;
pnpbios_parse_port_option(dev, p, len, option_flags);
break;
case SMALL_TAG_VENDOR:
/* do nothing */
break;
case SMALL_TAG_FIXEDPORT:
if (len != 3)
goto len_err;
pnpbios_parse_fixed_port_option(dev, p, len,
option_flags);
break;
case SMALL_TAG_STARTDEP:
if (len > 1)
goto len_err;
priority = PNP_RES_PRIORITY_ACCEPTABLE;
if (len > 0)
priority = p[1];
option_flags = pnp_new_dependent_set(dev, priority);
break;
case SMALL_TAG_ENDDEP:
if (len != 0)
goto len_err;
option_flags = 0;
break;
case SMALL_TAG_END:
return p + 2;
default: /* an unknown tag */
len_err:
dev_err(&dev->dev, "unknown tag %#x length %d\n",
tag, len);
break;
}
/* continue to the next tag */
if (p[0] & LARGE_TAG)
p += len + 3;
else
p += len + 1;
}
dev_err(&dev->dev, "no end tag in resource structure\n");
return NULL;
}
/*
* Compatible Device IDs
*/
static unsigned char *pnpbios_parse_compatible_ids(unsigned char *p,
unsigned char *end,
struct pnp_dev *dev)
{
int len, tag;
u32 eisa_id;
char id[8];
struct pnp_id *dev_id;
if (!p)
return NULL;
while ((char *)p < (char *)end) {
/* determine the type of tag */
if (p[0] & LARGE_TAG) { /* large tag */
len = (p[2] << 8) | p[1];
tag = p[0];
} else { /* small tag */
len = p[0] & 0x07;
tag = ((p[0] >> 3) & 0x0f);
}
switch (tag) {
case LARGE_TAG_ANSISTR:
strncpy(dev->name, p + 3,
len >= PNP_NAME_LEN ? PNP_NAME_LEN - 2 : len);
dev->name[len >=
PNP_NAME_LEN ? PNP_NAME_LEN - 1 : len] = '\0';
break;
case SMALL_TAG_COMPATDEVID: /* compatible ID */
if (len != 4)
goto len_err;
eisa_id = p[1] | p[2] << 8 | p[3] << 16 | p[4] << 24;
pnp_eisa_id_to_string(eisa_id & PNP_EISA_ID_MASK, id);
dev_id = pnp_add_id(dev, id);
if (!dev_id)
return NULL;
break;
case SMALL_TAG_END:
p = p + 2;
return (unsigned char *)p;
break;
default: /* an unknown tag */
len_err:
dev_err(&dev->dev, "unknown tag %#x length %d\n",
tag, len);
break;
}
/* continue to the next tag */
if (p[0] & LARGE_TAG)
p += len + 3;
else
p += len + 1;
}
dev_err(&dev->dev, "no end tag in resource structure\n");
return NULL;
}
/*
* Allocated Resource Encoding
*/
static void pnpbios_encode_mem(struct pnp_dev *dev, unsigned char *p,
struct resource *res)
{
unsigned long base;
unsigned long len;
if (pnp_resource_enabled(res)) {
base = res->start;
len = resource_size(res);
} else {
base = 0;
len = 0;
}
p[4] = (base >> 8) & 0xff;
p[5] = ((base >> 8) >> 8) & 0xff;
p[6] = (base >> 8) & 0xff;
p[7] = ((base >> 8) >> 8) & 0xff;
p[10] = (len >> 8) & 0xff;
p[11] = ((len >> 8) >> 8) & 0xff;
pnp_dbg(&dev->dev, " encode mem %#lx-%#lx\n", base, base + len - 1);
}
static void pnpbios_encode_mem32(struct pnp_dev *dev, unsigned char *p,
struct resource *res)
{
unsigned long base;
unsigned long len;
if (pnp_resource_enabled(res)) {
base = res->start;
len = resource_size(res);
} else {
base = 0;
len = 0;
}
p[4] = base & 0xff;
p[5] = (base >> 8) & 0xff;
p[6] = (base >> 16) & 0xff;
p[7] = (base >> 24) & 0xff;
p[8] = base & 0xff;
p[9] = (base >> 8) & 0xff;
p[10] = (base >> 16) & 0xff;
p[11] = (base >> 24) & 0xff;
p[16] = len & 0xff;
p[17] = (len >> 8) & 0xff;
p[18] = (len >> 16) & 0xff;
p[19] = (len >> 24) & 0xff;
pnp_dbg(&dev->dev, " encode mem32 %#lx-%#lx\n", base, base + len - 1);
}
static void pnpbios_encode_fixed_mem32(struct pnp_dev *dev, unsigned char *p,
struct resource *res)
{
unsigned long base;
unsigned long len;
if (pnp_resource_enabled(res)) {
base = res->start;
len = resource_size(res);
} else {
base = 0;
len = 0;
}
p[4] = base & 0xff;
p[5] = (base >> 8) & 0xff;
p[6] = (base >> 16) & 0xff;
p[7] = (base >> 24) & 0xff;
p[8] = len & 0xff;
p[9] = (len >> 8) & 0xff;
p[10] = (len >> 16) & 0xff;
p[11] = (len >> 24) & 0xff;
pnp_dbg(&dev->dev, " encode fixed_mem32 %#lx-%#lx\n", base,
base + len - 1);
}
static void pnpbios_encode_irq(struct pnp_dev *dev, unsigned char *p,
struct resource *res)
{
unsigned long map;
if (pnp_resource_enabled(res))
map = 1 << res->start;
else
map = 0;
p[1] = map & 0xff;
p[2] = (map >> 8) & 0xff;
pnp_dbg(&dev->dev, " encode irq mask %#lx\n", map);
}
static void pnpbios_encode_dma(struct pnp_dev *dev, unsigned char *p,
struct resource *res)
{
unsigned long map;
if (pnp_resource_enabled(res))
map = 1 << res->start;
else
map = 0;
p[1] = map & 0xff;
pnp_dbg(&dev->dev, " encode dma mask %#lx\n", map);
}
static void pnpbios_encode_port(struct pnp_dev *dev, unsigned char *p,
struct resource *res)
{
unsigned long base;
unsigned long len;
if (pnp_resource_enabled(res)) {
base = res->start;
len = resource_size(res);
} else {
base = 0;
len = 0;
}
p[2] = base & 0xff;
p[3] = (base >> 8) & 0xff;
p[4] = base & 0xff;
p[5] = (base >> 8) & 0xff;
p[7] = len & 0xff;
pnp_dbg(&dev->dev, " encode io %#lx-%#lx\n", base, base + len - 1);
}
static void pnpbios_encode_fixed_port(struct pnp_dev *dev, unsigned char *p,
struct resource *res)
{
unsigned long base = res->start;
unsigned long len = resource_size(res);
if (pnp_resource_enabled(res)) {
base = res->start;
len = resource_size(res);
} else {
base = 0;
len = 0;
}
p[1] = base & 0xff;
p[2] = (base >> 8) & 0xff;
p[3] = len & 0xff;
pnp_dbg(&dev->dev, " encode fixed_io %#lx-%#lx\n", base,
base + len - 1);
}
static unsigned char *pnpbios_encode_allocated_resource_data(struct pnp_dev
*dev,
unsigned char *p,
unsigned char *end)
{
unsigned int len, tag;
int port = 0, irq = 0, dma = 0, mem = 0;
if (!p)
return NULL;
while ((char *)p < (char *)end) {
/* determine the type of tag */
if (p[0] & LARGE_TAG) { /* large tag */
len = (p[2] << 8) | p[1];
tag = p[0];
} else { /* small tag */
len = p[0] & 0x07;
tag = ((p[0] >> 3) & 0x0f);
}
switch (tag) {
case LARGE_TAG_MEM:
if (len != 9)
goto len_err;
pnpbios_encode_mem(dev, p,
pnp_get_resource(dev, IORESOURCE_MEM, mem));
mem++;
break;
case LARGE_TAG_MEM32:
if (len != 17)
goto len_err;
pnpbios_encode_mem32(dev, p,
pnp_get_resource(dev, IORESOURCE_MEM, mem));
mem++;
break;
case LARGE_TAG_FIXEDMEM32:
if (len != 9)
goto len_err;
pnpbios_encode_fixed_mem32(dev, p,
pnp_get_resource(dev, IORESOURCE_MEM, mem));
mem++;
break;
case SMALL_TAG_IRQ:
if (len < 2 || len > 3)
goto len_err;
pnpbios_encode_irq(dev, p,
pnp_get_resource(dev, IORESOURCE_IRQ, irq));
irq++;
break;
case SMALL_TAG_DMA:
if (len != 2)
goto len_err;
pnpbios_encode_dma(dev, p,
pnp_get_resource(dev, IORESOURCE_DMA, dma));
dma++;
break;
case SMALL_TAG_PORT:
if (len != 7)
goto len_err;
pnpbios_encode_port(dev, p,
pnp_get_resource(dev, IORESOURCE_IO, port));
port++;
break;
case SMALL_TAG_VENDOR:
/* do nothing */
break;
case SMALL_TAG_FIXEDPORT:
if (len != 3)
goto len_err;
pnpbios_encode_fixed_port(dev, p,
pnp_get_resource(dev, IORESOURCE_IO, port));
port++;
break;
case SMALL_TAG_END:
p = p + 2;
return (unsigned char *)p;
break;
default: /* an unknown tag */
len_err:
dev_err(&dev->dev, "unknown tag %#x length %d\n",
tag, len);
break;
}
/* continue to the next tag */
if (p[0] & LARGE_TAG)
p += len + 3;
else
p += len + 1;
}
dev_err(&dev->dev, "no end tag in resource structure\n");
return NULL;
}
/*
* Core Parsing Functions
*/
int __init pnpbios_parse_data_stream(struct pnp_dev *dev,
struct pnp_bios_node *node)
{
unsigned char *p = (char *)node->data;
unsigned char *end = (char *)(node->data + node->size);
p = pnpbios_parse_allocated_resource_data(dev, p, end);
if (!p)
return -EIO;
p = pnpbios_parse_resource_option_data(p, end, dev);
if (!p)
return -EIO;
p = pnpbios_parse_compatible_ids(p, end, dev);
if (!p)
return -EIO;
return 0;
}
int pnpbios_read_resources_from_node(struct pnp_dev *dev,
struct pnp_bios_node *node)
{
unsigned char *p = (char *)node->data;
unsigned char *end = (char *)(node->data + node->size);
p = pnpbios_parse_allocated_resource_data(dev, p, end);
if (!p)
return -EIO;
return 0;
}
int pnpbios_write_resources_to_node(struct pnp_dev *dev,
struct pnp_bios_node *node)
{
unsigned char *p = (char *)node->data;
unsigned char *end = (char *)(node->data + node->size);
p = pnpbios_encode_allocated_resource_data(dev, p, end);
if (!p)
return -EIO;
return 0;
}