diff options
Diffstat (limited to 'drivers/pnp/isapnp')
-rw-r--r-- | drivers/pnp/isapnp/Kconfig | 12 | ||||
-rw-r--r-- | drivers/pnp/isapnp/Makefile | 8 | ||||
-rw-r--r-- | drivers/pnp/isapnp/compat.c | 66 | ||||
-rw-r--r-- | drivers/pnp/isapnp/core.c | 1069 | ||||
-rw-r--r-- | drivers/pnp/isapnp/proc.c | 86 |
5 files changed, 1241 insertions, 0 deletions
diff --git a/drivers/pnp/isapnp/Kconfig b/drivers/pnp/isapnp/Kconfig new file mode 100644 index 0000000000..d0479a5631 --- /dev/null +++ b/drivers/pnp/isapnp/Kconfig @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# ISA Plug and Play configuration +# +config ISAPNP + bool "ISA Plug and Play support" + depends on ISA || COMPILE_TEST + help + Say Y here if you would like support for ISA Plug and Play devices. + Some information is in <file:Documentation/driver-api/isapnp.rst>. + + If unsure, say Y. diff --git a/drivers/pnp/isapnp/Makefile b/drivers/pnp/isapnp/Makefile new file mode 100644 index 0000000000..a0e0c0aecc --- /dev/null +++ b/drivers/pnp/isapnp/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for the kernel ISAPNP driver. +# +obj-y += pnp.o +pnp-y := core.o compat.o + +pnp-$(CONFIG_PROC_FS) += proc.o diff --git a/drivers/pnp/isapnp/compat.c b/drivers/pnp/isapnp/compat.c new file mode 100644 index 0000000000..d60d9e377d --- /dev/null +++ b/drivers/pnp/isapnp/compat.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * compat.c - A series of functions to make it easier to convert drivers that use + * the old isapnp APIs. If possible use the new APIs instead. + * + * Copyright 2002 Adam Belay <ambx1@neo.rr.com> + */ + +#include <linux/module.h> +#include <linux/isapnp.h> +#include <linux/string.h> + +static void pnp_convert_id(char *buf, unsigned short vendor, + unsigned short device) +{ + sprintf(buf, "%c%c%c%x%x%x%x", + 'A' + ((vendor >> 2) & 0x3f) - 1, + 'A' + (((vendor & 3) << 3) | ((vendor >> 13) & 7)) - 1, + 'A' + ((vendor >> 8) & 0x1f) - 1, + (device >> 4) & 0x0f, device & 0x0f, + (device >> 12) & 0x0f, (device >> 8) & 0x0f); +} + +struct pnp_dev *pnp_find_dev(struct pnp_card *card, unsigned short vendor, + unsigned short function, struct pnp_dev *from) +{ + char id[8]; + char any[8]; + + pnp_convert_id(id, vendor, function); + pnp_convert_id(any, ISAPNP_ANY_ID, ISAPNP_ANY_ID); + if (card == NULL) { /* look for a logical device from all cards */ + struct list_head *list; + + list = pnp_global.next; + if (from) + list = from->global_list.next; + + while (list != &pnp_global) { + struct pnp_dev *dev = global_to_pnp_dev(list); + + if (compare_pnp_id(dev->id, id) || + (memcmp(id, any, 7) == 0)) + return dev; + list = list->next; + } + } else { + struct list_head *list; + + list = card->devices.next; + if (from) { + list = from->card_list.next; + if (from->card != card) /* something is wrong */ + return NULL; + } + while (list != &card->devices) { + struct pnp_dev *dev = card_to_pnp_dev(list); + + if (compare_pnp_id(dev->id, id)) + return dev; + list = list->next; + } + } + return NULL; +} +EXPORT_SYMBOL(pnp_find_dev); diff --git a/drivers/pnp/isapnp/core.c b/drivers/pnp/isapnp/core.c new file mode 100644 index 0000000000..c43d8ad025 --- /dev/null +++ b/drivers/pnp/isapnp/core.c @@ -0,0 +1,1069 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ISA Plug & Play support + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + * + * Changelog: + * 2000-01-01 Added quirks handling for buggy hardware + * Peter Denison <peterd@pnd-pc.demon.co.uk> + * 2000-06-14 Added isapnp_probe_devs() and isapnp_activate_dev() + * Christoph Hellwig <hch@infradead.org> + * 2001-06-03 Added release_region calls to correspond with + * request_region calls when a failure occurs. Also + * added KERN_* constants to printk() calls. + * 2001-11-07 Added isapnp_{,un}register_driver calls along the lines + * of the pci driver interface + * Kai Germaschewski <kai.germaschewski@gmx.de> + * 2002-06-06 Made the use of dma channel 0 configurable + * Gerald Teschl <gerald.teschl@univie.ac.at> + * 2002-10-06 Ported to PnP Layer - Adam Belay <ambx1@neo.rr.com> + * 2003-08-11 Resource Management Updates - Adam Belay <ambx1@neo.rr.com> + */ + +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/isapnp.h> +#include <linux/mutex.h> +#include <asm/io.h> + +#include "../base.h" + +#if 0 +#define ISAPNP_REGION_OK +#endif + +int isapnp_disable; /* Disable ISA PnP */ +static int isapnp_rdp; /* Read Data Port */ +static int isapnp_reset = 1; /* reset all PnP cards (deactivate) */ +static int isapnp_verbose = 1; /* verbose mode */ + +module_param(isapnp_disable, int, 0); +MODULE_PARM_DESC(isapnp_disable, "ISA Plug & Play disable"); +module_param(isapnp_rdp, int, 0); +MODULE_PARM_DESC(isapnp_rdp, "ISA Plug & Play read data port"); +module_param(isapnp_reset, int, 0); +MODULE_PARM_DESC(isapnp_reset, "ISA Plug & Play reset all cards"); +module_param(isapnp_verbose, int, 0); +MODULE_PARM_DESC(isapnp_verbose, "ISA Plug & Play verbose mode"); + +#define _PIDXR 0x279 +#define _PNPWRP 0xa79 + +/* short tags */ +#define _STAG_PNPVERNO 0x01 +#define _STAG_LOGDEVID 0x02 +#define _STAG_COMPATDEVID 0x03 +#define _STAG_IRQ 0x04 +#define _STAG_DMA 0x05 +#define _STAG_STARTDEP 0x06 +#define _STAG_ENDDEP 0x07 +#define _STAG_IOPORT 0x08 +#define _STAG_FIXEDIO 0x09 +#define _STAG_VENDOR 0x0e +#define _STAG_END 0x0f +/* long tags */ +#define _LTAG_MEMRANGE 0x81 +#define _LTAG_ANSISTR 0x82 +#define _LTAG_UNICODESTR 0x83 +#define _LTAG_VENDOR 0x84 +#define _LTAG_MEM32RANGE 0x85 +#define _LTAG_FIXEDMEM32RANGE 0x86 + +/* Logical device control and configuration registers */ + +#define ISAPNP_CFG_ACTIVATE 0x30 /* byte */ +#define ISAPNP_CFG_MEM 0x40 /* 4 * dword */ +#define ISAPNP_CFG_PORT 0x60 /* 8 * word */ +#define ISAPNP_CFG_IRQ 0x70 /* 2 * word */ +#define ISAPNP_CFG_DMA 0x74 /* 2 * byte */ + +/* + * Sizes of ISAPNP logical device configuration register sets. + * See PNP-ISA-v1.0a.pdf, Appendix A. + */ +#define ISAPNP_MAX_MEM 4 +#define ISAPNP_MAX_PORT 8 +#define ISAPNP_MAX_IRQ 2 +#define ISAPNP_MAX_DMA 2 + +static unsigned char isapnp_checksum_value; +static DEFINE_MUTEX(isapnp_cfg_mutex); +static int isapnp_csn_count; + +/* some prototypes */ + +static inline void write_data(unsigned char x) +{ + outb(x, _PNPWRP); +} + +static inline void write_address(unsigned char x) +{ + outb(x, _PIDXR); + udelay(20); +} + +static inline unsigned char read_data(void) +{ + unsigned char val = inb(isapnp_rdp); + return val; +} + +unsigned char isapnp_read_byte(unsigned char idx) +{ + write_address(idx); + return read_data(); +} + +static unsigned short isapnp_read_word(unsigned char idx) +{ + unsigned short val; + + val = isapnp_read_byte(idx); + val = (val << 8) + isapnp_read_byte(idx + 1); + return val; +} + +void isapnp_write_byte(unsigned char idx, unsigned char val) +{ + write_address(idx); + write_data(val); +} + +static void isapnp_write_word(unsigned char idx, unsigned short val) +{ + isapnp_write_byte(idx, val >> 8); + isapnp_write_byte(idx + 1, val); +} + +static void isapnp_key(void) +{ + unsigned char code = 0x6a, msb; + int i; + + mdelay(1); + write_address(0x00); + write_address(0x00); + + write_address(code); + + for (i = 1; i < 32; i++) { + msb = ((code & 0x01) ^ ((code & 0x02) >> 1)) << 7; + code = (code >> 1) | msb; + write_address(code); + } +} + +/* place all pnp cards in wait-for-key state */ +static void isapnp_wait(void) +{ + isapnp_write_byte(0x02, 0x02); +} + +static void isapnp_wake(unsigned char csn) +{ + isapnp_write_byte(0x03, csn); +} + +static void isapnp_device(unsigned char logdev) +{ + isapnp_write_byte(0x07, logdev); +} + +static void isapnp_activate(unsigned char logdev) +{ + isapnp_device(logdev); + isapnp_write_byte(ISAPNP_CFG_ACTIVATE, 1); + udelay(250); +} + +static void isapnp_deactivate(unsigned char logdev) +{ + isapnp_device(logdev); + isapnp_write_byte(ISAPNP_CFG_ACTIVATE, 0); + udelay(500); +} + +static void __init isapnp_peek(unsigned char *data, int bytes) +{ + int i, j; + unsigned char d = 0; + + for (i = 1; i <= bytes; i++) { + for (j = 0; j < 20; j++) { + d = isapnp_read_byte(0x05); + if (d & 1) + break; + udelay(100); + } + if (!(d & 1)) { + if (data != NULL) + *data++ = 0xff; + continue; + } + d = isapnp_read_byte(0x04); /* PRESDI */ + isapnp_checksum_value += d; + if (data != NULL) + *data++ = d; + } +} + +#define RDP_STEP 32 /* minimum is 4 */ + +static int isapnp_next_rdp(void) +{ + int rdp = isapnp_rdp; + static int old_rdp = 0; + + if (old_rdp) { + release_region(old_rdp, 1); + old_rdp = 0; + } + while (rdp <= 0x3ff) { + /* + * We cannot use NE2000 probe spaces for ISAPnP or we + * will lock up machines. + */ + if ((rdp < 0x280 || rdp > 0x380) + && request_region(rdp, 1, "ISAPnP")) { + isapnp_rdp = rdp; + old_rdp = rdp; + return 0; + } + rdp += RDP_STEP; + } + return -1; +} + +/* Set read port address */ +static inline void isapnp_set_rdp(void) +{ + isapnp_write_byte(0x00, isapnp_rdp >> 2); + udelay(100); +} + +/* + * Perform an isolation. The port selection code now tries to avoid + * "dangerous to read" ports. + */ +static int __init isapnp_isolate_rdp_select(void) +{ + isapnp_wait(); + isapnp_key(); + + /* Control: reset CSN and conditionally everything else too */ + isapnp_write_byte(0x02, isapnp_reset ? 0x05 : 0x04); + mdelay(2); + + isapnp_wait(); + isapnp_key(); + isapnp_wake(0x00); + + if (isapnp_next_rdp() < 0) { + isapnp_wait(); + return -1; + } + + isapnp_set_rdp(); + udelay(1000); + write_address(0x01); + udelay(1000); + return 0; +} + +/* + * Isolate (assign uniqued CSN) to all ISA PnP devices. + */ +static int __init isapnp_isolate(void) +{ + unsigned char checksum = 0x6a; + unsigned char chksum = 0x00; + unsigned char bit = 0x00; + int data; + int csn = 0; + int i; + int iteration = 1; + + isapnp_rdp = 0x213; + if (isapnp_isolate_rdp_select() < 0) + return -1; + + while (1) { + for (i = 1; i <= 64; i++) { + data = read_data() << 8; + udelay(250); + data = data | read_data(); + udelay(250); + if (data == 0x55aa) + bit = 0x01; + checksum = + ((((checksum ^ (checksum >> 1)) & 0x01) ^ bit) << 7) + | (checksum >> 1); + bit = 0x00; + } + for (i = 65; i <= 72; i++) { + data = read_data() << 8; + udelay(250); + data = data | read_data(); + udelay(250); + if (data == 0x55aa) + chksum |= (1 << (i - 65)); + } + if (checksum != 0x00 && checksum == chksum) { + csn++; + + isapnp_write_byte(0x06, csn); + udelay(250); + iteration++; + isapnp_wake(0x00); + isapnp_set_rdp(); + udelay(1000); + write_address(0x01); + udelay(1000); + goto __next; + } + if (iteration == 1) { + isapnp_rdp += RDP_STEP; + if (isapnp_isolate_rdp_select() < 0) + return -1; + } else if (iteration > 1) { + break; + } +__next: + if (csn == 255) + break; + checksum = 0x6a; + chksum = 0x00; + bit = 0x00; + } + isapnp_wait(); + isapnp_csn_count = csn; + return csn; +} + +/* + * Read one tag from stream. + */ +static int __init isapnp_read_tag(unsigned char *type, unsigned short *size) +{ + unsigned char tag, tmp[2]; + + isapnp_peek(&tag, 1); + if (tag == 0) /* invalid tag */ + return -1; + if (tag & 0x80) { /* large item */ + *type = tag; + isapnp_peek(tmp, 2); + *size = (tmp[1] << 8) | tmp[0]; + } else { + *type = (tag >> 3) & 0x0f; + *size = tag & 0x07; + } + if (*type == 0xff && *size == 0xffff) /* probably invalid data */ + return -1; + return 0; +} + +/* + * Skip specified number of bytes from stream. + */ +static void __init isapnp_skip_bytes(int count) +{ + isapnp_peek(NULL, count); +} + +/* + * Parse logical device tag. + */ +static struct pnp_dev *__init isapnp_parse_device(struct pnp_card *card, + int size, int number) +{ + unsigned char tmp[6]; + struct pnp_dev *dev; + u32 eisa_id; + char id[8]; + + isapnp_peek(tmp, size); + eisa_id = tmp[0] | tmp[1] << 8 | tmp[2] << 16 | tmp[3] << 24; + pnp_eisa_id_to_string(eisa_id, id); + + dev = pnp_alloc_dev(&isapnp_protocol, number, id); + if (!dev) + return NULL; + + dev->card = card; + dev->capabilities |= PNP_CONFIGURABLE; + dev->capabilities |= PNP_READ; + dev->capabilities |= PNP_WRITE; + dev->capabilities |= PNP_DISABLE; + pnp_init_resources(dev); + return dev; +} + +/* + * Add IRQ resource to resources list. + */ +static void __init isapnp_parse_irq_resource(struct pnp_dev *dev, + unsigned int option_flags, + int size) +{ + unsigned char tmp[3]; + unsigned long bits; + pnp_irq_mask_t map; + unsigned char flags = IORESOURCE_IRQ_HIGHEDGE; + + isapnp_peek(tmp, size); + bits = (tmp[1] << 8) | tmp[0]; + + bitmap_zero(map.bits, PNP_IRQ_NR); + bitmap_copy(map.bits, &bits, 16); + + if (size > 2) + flags = tmp[2]; + + pnp_register_irq_resource(dev, option_flags, &map, flags); +} + +/* + * Add DMA resource to resources list. + */ +static void __init isapnp_parse_dma_resource(struct pnp_dev *dev, + unsigned int option_flags, + int size) +{ + unsigned char tmp[2]; + + isapnp_peek(tmp, size); + pnp_register_dma_resource(dev, option_flags, tmp[0], tmp[1]); +} + +/* + * Add port resource to resources list. + */ +static void __init isapnp_parse_port_resource(struct pnp_dev *dev, + unsigned int option_flags, + int size) +{ + unsigned char tmp[7]; + resource_size_t min, max, align, len; + unsigned char flags; + + isapnp_peek(tmp, size); + min = (tmp[2] << 8) | tmp[1]; + max = (tmp[4] << 8) | tmp[3]; + align = tmp[5]; + len = tmp[6]; + flags = tmp[0] ? IORESOURCE_IO_16BIT_ADDR : 0; + pnp_register_port_resource(dev, option_flags, + min, max, align, len, flags); +} + +/* + * Add fixed port resource to resources list. + */ +static void __init isapnp_parse_fixed_port_resource(struct pnp_dev *dev, + unsigned int option_flags, + int size) +{ + unsigned char tmp[3]; + resource_size_t base, len; + + isapnp_peek(tmp, size); + base = (tmp[1] << 8) | tmp[0]; + len = tmp[2]; + pnp_register_port_resource(dev, option_flags, base, base, 0, len, + IORESOURCE_IO_FIXED); +} + +/* + * Add memory resource to resources list. + */ +static void __init isapnp_parse_mem_resource(struct pnp_dev *dev, + unsigned int option_flags, + int size) +{ + unsigned char tmp[9]; + resource_size_t min, max, align, len; + unsigned char flags; + + isapnp_peek(tmp, size); + min = ((tmp[2] << 8) | tmp[1]) << 8; + max = ((tmp[4] << 8) | tmp[3]) << 8; + align = (tmp[6] << 8) | tmp[5]; + len = ((tmp[8] << 8) | tmp[7]) << 8; + flags = tmp[0]; + pnp_register_mem_resource(dev, option_flags, + min, max, align, len, flags); +} + +/* + * Add 32-bit memory resource to resources list. + */ +static void __init isapnp_parse_mem32_resource(struct pnp_dev *dev, + unsigned int option_flags, + int size) +{ + unsigned char tmp[17]; + resource_size_t min, max, align, len; + unsigned char flags; + + isapnp_peek(tmp, size); + min = (tmp[4] << 24) | (tmp[3] << 16) | (tmp[2] << 8) | tmp[1]; + max = (tmp[8] << 24) | (tmp[7] << 16) | (tmp[6] << 8) | tmp[5]; + align = (tmp[12] << 24) | (tmp[11] << 16) | (tmp[10] << 8) | tmp[9]; + len = (tmp[16] << 24) | (tmp[15] << 16) | (tmp[14] << 8) | tmp[13]; + flags = tmp[0]; + pnp_register_mem_resource(dev, option_flags, + min, max, align, len, flags); +} + +/* + * Add 32-bit fixed memory resource to resources list. + */ +static void __init isapnp_parse_fixed_mem32_resource(struct pnp_dev *dev, + unsigned int option_flags, + int size) +{ + unsigned char tmp[9]; + resource_size_t base, len; + unsigned char flags; + + isapnp_peek(tmp, size); + base = (tmp[4] << 24) | (tmp[3] << 16) | (tmp[2] << 8) | tmp[1]; + len = (tmp[8] << 24) | (tmp[7] << 16) | (tmp[6] << 8) | tmp[5]; + flags = tmp[0]; + pnp_register_mem_resource(dev, option_flags, base, base, 0, len, flags); +} + +/* + * Parse card name for ISA PnP device. + */ +static void __init +isapnp_parse_name(char *name, unsigned int name_max, unsigned short *size) +{ + if (name[0] == '\0') { + unsigned short size1 = + *size >= name_max ? (name_max - 1) : *size; + isapnp_peek(name, size1); + name[size1] = '\0'; + *size -= size1; + + /* clean whitespace from end of string */ + while (size1 > 0 && name[--size1] == ' ') + name[size1] = '\0'; + } +} + +/* + * Parse resource map for logical device. + */ +static int __init isapnp_create_device(struct pnp_card *card, + unsigned short size) +{ + int number = 0, skip = 0, priority, compat = 0; + unsigned char type, tmp[17]; + unsigned int option_flags; + struct pnp_dev *dev; + u32 eisa_id; + char id[8]; + + if ((dev = isapnp_parse_device(card, size, number++)) == NULL) + return 1; + option_flags = 0; + pnp_add_card_device(card, dev); + + while (1) { + if (isapnp_read_tag(&type, &size) < 0) + return 1; + if (skip && type != _STAG_LOGDEVID && type != _STAG_END) + goto __skip; + switch (type) { + case _STAG_LOGDEVID: + if (size >= 5 && size <= 6) { + if ((dev = + isapnp_parse_device(card, size, + number++)) == NULL) + return 1; + size = 0; + skip = 0; + option_flags = 0; + pnp_add_card_device(card, dev); + } else { + skip = 1; + } + compat = 0; + break; + case _STAG_COMPATDEVID: + if (size == 4 && compat < DEVICE_COUNT_COMPATIBLE) { + isapnp_peek(tmp, 4); + eisa_id = tmp[0] | tmp[1] << 8 | + tmp[2] << 16 | tmp[3] << 24; + pnp_eisa_id_to_string(eisa_id, id); + pnp_add_id(dev, id); + compat++; + size = 0; + } + break; + case _STAG_IRQ: + if (size < 2 || size > 3) + goto __skip; + isapnp_parse_irq_resource(dev, option_flags, size); + size = 0; + break; + case _STAG_DMA: + if (size != 2) + goto __skip; + isapnp_parse_dma_resource(dev, option_flags, size); + size = 0; + break; + case _STAG_STARTDEP: + if (size > 1) + goto __skip; + priority = PNP_RES_PRIORITY_ACCEPTABLE; + if (size > 0) { + isapnp_peek(tmp, size); + priority = tmp[0]; + size = 0; + } + option_flags = pnp_new_dependent_set(dev, priority); + break; + case _STAG_ENDDEP: + if (size != 0) + goto __skip; + option_flags = 0; + break; + case _STAG_IOPORT: + if (size != 7) + goto __skip; + isapnp_parse_port_resource(dev, option_flags, size); + size = 0; + break; + case _STAG_FIXEDIO: + if (size != 3) + goto __skip; + isapnp_parse_fixed_port_resource(dev, option_flags, + size); + size = 0; + break; + case _STAG_VENDOR: + break; + case _LTAG_MEMRANGE: + if (size != 9) + goto __skip; + isapnp_parse_mem_resource(dev, option_flags, size); + size = 0; + break; + case _LTAG_ANSISTR: + isapnp_parse_name(dev->name, sizeof(dev->name), &size); + break; + case _LTAG_UNICODESTR: + /* silently ignore */ + /* who use unicode for hardware identification? */ + break; + case _LTAG_VENDOR: + break; + case _LTAG_MEM32RANGE: + if (size != 17) + goto __skip; + isapnp_parse_mem32_resource(dev, option_flags, size); + size = 0; + break; + case _LTAG_FIXEDMEM32RANGE: + if (size != 9) + goto __skip; + isapnp_parse_fixed_mem32_resource(dev, option_flags, + size); + size = 0; + break; + case _STAG_END: + if (size > 0) + isapnp_skip_bytes(size); + return 1; + default: + dev_err(&dev->dev, "unknown tag %#x (card %i), " + "ignored\n", type, card->number); + } +__skip: + if (size > 0) + isapnp_skip_bytes(size); + } + return 0; +} + +/* + * Parse resource map for ISA PnP card. + */ +static void __init isapnp_parse_resource_map(struct pnp_card *card) +{ + unsigned char type, tmp[17]; + unsigned short size; + + while (1) { + if (isapnp_read_tag(&type, &size) < 0) + return; + switch (type) { + case _STAG_PNPVERNO: + if (size != 2) + goto __skip; + isapnp_peek(tmp, 2); + card->pnpver = tmp[0]; + card->productver = tmp[1]; + size = 0; + break; + case _STAG_LOGDEVID: + if (size >= 5 && size <= 6) { + if (isapnp_create_device(card, size) == 1) + return; + size = 0; + } + break; + case _STAG_VENDOR: + break; + case _LTAG_ANSISTR: + isapnp_parse_name(card->name, sizeof(card->name), + &size); + break; + case _LTAG_UNICODESTR: + /* silently ignore */ + /* who use unicode for hardware identification? */ + break; + case _LTAG_VENDOR: + break; + case _STAG_END: + if (size > 0) + isapnp_skip_bytes(size); + return; + default: + dev_err(&card->dev, "unknown tag %#x, ignored\n", + type); + } +__skip: + if (size > 0) + isapnp_skip_bytes(size); + } +} + +/* + * Build device list for all present ISA PnP devices. + */ +static int __init isapnp_build_device_list(void) +{ + int csn; + unsigned char header[9]; + struct pnp_card *card; + u32 eisa_id; + char id[8]; + + isapnp_wait(); + isapnp_key(); + for (csn = 1; csn <= isapnp_csn_count; csn++) { + isapnp_wake(csn); + isapnp_peek(header, 9); + eisa_id = header[0] | header[1] << 8 | + header[2] << 16 | header[3] << 24; + pnp_eisa_id_to_string(eisa_id, id); + card = pnp_alloc_card(&isapnp_protocol, csn, id); + if (!card) + continue; + + INIT_LIST_HEAD(&card->devices); + card->serial = + (header[7] << 24) | (header[6] << 16) | (header[5] << 8) | + header[4]; + isapnp_checksum_value = 0x00; + isapnp_parse_resource_map(card); + if (isapnp_checksum_value != 0x00) + dev_err(&card->dev, "invalid checksum %#x\n", + isapnp_checksum_value); + card->checksum = isapnp_checksum_value; + + pnp_add_card(card); + } + isapnp_wait(); + return 0; +} + +/* + * Basic configuration routines. + */ + +int isapnp_present(void) +{ + struct pnp_card *card; + + pnp_for_each_card(card) { + if (card->protocol == &isapnp_protocol) + return 1; + } + return 0; +} + +int isapnp_cfg_begin(int csn, int logdev) +{ + if (csn < 1 || csn > isapnp_csn_count || logdev > 10) + return -EINVAL; + mutex_lock(&isapnp_cfg_mutex); + isapnp_wait(); + isapnp_key(); + isapnp_wake(csn); +#if 0 + /* to avoid malfunction when the isapnptools package is used */ + /* we must set RDP to our value again */ + /* it is possible to set RDP only in the isolation phase */ + /* Jens Thoms Toerring <Jens.Toerring@physik.fu-berlin.de> */ + isapnp_write_byte(0x02, 0x04); /* clear CSN of card */ + mdelay(2); /* is this necessary? */ + isapnp_wake(csn); /* bring card into sleep state */ + isapnp_wake(0); /* bring card into isolation state */ + isapnp_set_rdp(); /* reset the RDP port */ + udelay(1000); /* delay 1000us */ + isapnp_write_byte(0x06, csn); /* reset CSN to previous value */ + udelay(250); /* is this necessary? */ +#endif + if (logdev >= 0) + isapnp_device(logdev); + return 0; +} + +int isapnp_cfg_end(void) +{ + isapnp_wait(); + mutex_unlock(&isapnp_cfg_mutex); + return 0; +} + +/* + * Initialization. + */ + +EXPORT_SYMBOL(isapnp_protocol); +EXPORT_SYMBOL(isapnp_present); +EXPORT_SYMBOL(isapnp_cfg_begin); +EXPORT_SYMBOL(isapnp_cfg_end); +EXPORT_SYMBOL(isapnp_write_byte); + +static int isapnp_get_resources(struct pnp_dev *dev) +{ + int i, ret; + + pnp_dbg(&dev->dev, "get resources\n"); + pnp_init_resources(dev); + isapnp_cfg_begin(dev->card->number, dev->number); + dev->active = isapnp_read_byte(ISAPNP_CFG_ACTIVATE); + if (!dev->active) + goto __end; + + for (i = 0; i < ISAPNP_MAX_PORT; i++) { + ret = isapnp_read_word(ISAPNP_CFG_PORT + (i << 1)); + pnp_add_io_resource(dev, ret, ret, + ret == 0 ? IORESOURCE_DISABLED : 0); + } + for (i = 0; i < ISAPNP_MAX_MEM; i++) { + ret = isapnp_read_word(ISAPNP_CFG_MEM + (i << 3)) << 8; + pnp_add_mem_resource(dev, ret, ret, + ret == 0 ? IORESOURCE_DISABLED : 0); + } + for (i = 0; i < ISAPNP_MAX_IRQ; i++) { + ret = isapnp_read_word(ISAPNP_CFG_IRQ + (i << 1)) >> 8; + pnp_add_irq_resource(dev, ret, + ret == 0 ? IORESOURCE_DISABLED : 0); + } + for (i = 0; i < ISAPNP_MAX_DMA; i++) { + ret = isapnp_read_byte(ISAPNP_CFG_DMA + i); + pnp_add_dma_resource(dev, ret, + ret == 4 ? IORESOURCE_DISABLED : 0); + } + +__end: + isapnp_cfg_end(); + return 0; +} + +static int isapnp_set_resources(struct pnp_dev *dev) +{ + struct resource *res; + int tmp; + + pnp_dbg(&dev->dev, "set resources\n"); + isapnp_cfg_begin(dev->card->number, dev->number); + dev->active = 1; + for (tmp = 0; tmp < ISAPNP_MAX_PORT; tmp++) { + res = pnp_get_resource(dev, IORESOURCE_IO, tmp); + if (pnp_resource_enabled(res)) { + pnp_dbg(&dev->dev, " set io %d to %#llx\n", + tmp, (unsigned long long) res->start); + isapnp_write_word(ISAPNP_CFG_PORT + (tmp << 1), + res->start); + } + } + for (tmp = 0; tmp < ISAPNP_MAX_IRQ; tmp++) { + res = pnp_get_resource(dev, IORESOURCE_IRQ, tmp); + if (pnp_resource_enabled(res)) { + int irq = res->start; + if (irq == 2) + irq = 9; + pnp_dbg(&dev->dev, " set irq %d to %d\n", tmp, irq); + isapnp_write_byte(ISAPNP_CFG_IRQ + (tmp << 1), irq); + } + } + for (tmp = 0; tmp < ISAPNP_MAX_DMA; tmp++) { + res = pnp_get_resource(dev, IORESOURCE_DMA, tmp); + if (pnp_resource_enabled(res)) { + pnp_dbg(&dev->dev, " set dma %d to %lld\n", + tmp, (unsigned long long) res->start); + isapnp_write_byte(ISAPNP_CFG_DMA + tmp, res->start); + } + } + for (tmp = 0; tmp < ISAPNP_MAX_MEM; tmp++) { + res = pnp_get_resource(dev, IORESOURCE_MEM, tmp); + if (pnp_resource_enabled(res)) { + pnp_dbg(&dev->dev, " set mem %d to %#llx\n", + tmp, (unsigned long long) res->start); + isapnp_write_word(ISAPNP_CFG_MEM + (tmp << 3), + (res->start >> 8) & 0xffff); + } + } + /* FIXME: We aren't handling 32bit mems properly here */ + isapnp_activate(dev->number); + isapnp_cfg_end(); + return 0; +} + +static int isapnp_disable_resources(struct pnp_dev *dev) +{ + if (!dev->active) + return -EINVAL; + isapnp_cfg_begin(dev->card->number, dev->number); + isapnp_deactivate(dev->number); + dev->active = 0; + isapnp_cfg_end(); + return 0; +} + +struct pnp_protocol isapnp_protocol = { + .name = "ISA Plug and Play", + .get = isapnp_get_resources, + .set = isapnp_set_resources, + .disable = isapnp_disable_resources, +}; + +static int __init isapnp_init(void) +{ + int cards; + struct pnp_card *card; + struct pnp_dev *dev; + + if (isapnp_disable) { + printk(KERN_INFO "isapnp: ISA Plug & Play support disabled\n"); + return 0; + } +#ifdef CONFIG_PPC + if (check_legacy_ioport(_PIDXR) || check_legacy_ioport(_PNPWRP)) + return -EINVAL; +#endif +#ifdef ISAPNP_REGION_OK + if (!request_region(_PIDXR, 1, "isapnp index")) { + printk(KERN_ERR "isapnp: Index Register 0x%x already used\n", + _PIDXR); + return -EBUSY; + } +#endif + if (!request_region(_PNPWRP, 1, "isapnp write")) { + printk(KERN_ERR + "isapnp: Write Data Register 0x%x already used\n", + _PNPWRP); +#ifdef ISAPNP_REGION_OK + release_region(_PIDXR, 1); +#endif + return -EBUSY; + } + + if (pnp_register_protocol(&isapnp_protocol) < 0) + return -EBUSY; + + /* + * Print a message. The existing ISAPnP code is hanging machines + * so let the user know where. + */ + + printk(KERN_INFO "isapnp: Scanning for PnP cards...\n"); + if (isapnp_rdp >= 0x203 && isapnp_rdp <= 0x3ff) { + isapnp_rdp |= 3; + if (!request_region(isapnp_rdp, 1, "isapnp read")) { + printk(KERN_ERR + "isapnp: Read Data Register 0x%x already used\n", + isapnp_rdp); +#ifdef ISAPNP_REGION_OK + release_region(_PIDXR, 1); +#endif + release_region(_PNPWRP, 1); + return -EBUSY; + } + isapnp_set_rdp(); + } + if (isapnp_rdp < 0x203 || isapnp_rdp > 0x3ff) { + cards = isapnp_isolate(); + if (cards < 0 || (isapnp_rdp < 0x203 || isapnp_rdp > 0x3ff)) { +#ifdef ISAPNP_REGION_OK + release_region(_PIDXR, 1); +#endif + release_region(_PNPWRP, 1); + printk(KERN_INFO + "isapnp: No Plug & Play device found\n"); + return 0; + } + request_region(isapnp_rdp, 1, "isapnp read"); + } + isapnp_build_device_list(); + cards = 0; + + protocol_for_each_card(&isapnp_protocol, card) { + cards++; + if (isapnp_verbose) { + dev_info(&card->dev, "card '%s'\n", + card->name[0] ? card->name : "unknown"); + if (isapnp_verbose < 2) + continue; + card_for_each_dev(card, dev) { + dev_info(&card->dev, "device '%s'\n", + dev->name[0] ? dev->name : "unknown"); + } + } + } + if (cards) + printk(KERN_INFO + "isapnp: %i Plug & Play card%s detected total\n", cards, + cards > 1 ? "s" : ""); + else + printk(KERN_INFO "isapnp: No Plug & Play card found\n"); + + isapnp_proc_init(); + return 0; +} + +device_initcall(isapnp_init); + +/* format is: noisapnp */ + +static int __init isapnp_setup_disable(char *str) +{ + isapnp_disable = 1; + return 1; +} + +__setup("noisapnp", isapnp_setup_disable); + +/* format is: isapnp=rdp,reset,skip_pci_scan,verbose */ + +static int __init isapnp_setup_isapnp(char *str) +{ + (void)((get_option(&str, &isapnp_rdp) == 2) && + (get_option(&str, &isapnp_reset) == 2) && + (get_option(&str, &isapnp_verbose) == 2)); + return 1; +} + +__setup("isapnp=", isapnp_setup_isapnp); diff --git a/drivers/pnp/isapnp/proc.c b/drivers/pnp/isapnp/proc.c new file mode 100644 index 0000000000..55ae72a281 --- /dev/null +++ b/drivers/pnp/isapnp/proc.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ISA Plug & Play support + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +#include <linux/module.h> +#include <linux/isapnp.h> +#include <linux/proc_fs.h> +#include <linux/init.h> +#include <linux/uaccess.h> + +extern struct pnp_protocol isapnp_protocol; + +static struct proc_dir_entry *isapnp_proc_bus_dir = NULL; + +static loff_t isapnp_proc_bus_lseek(struct file *file, loff_t off, int whence) +{ + return fixed_size_llseek(file, off, whence, 256); +} + +static ssize_t isapnp_proc_bus_read(struct file *file, char __user * buf, + size_t nbytes, loff_t * ppos) +{ + struct pnp_dev *dev = pde_data(file_inode(file)); + int pos = *ppos; + int cnt, size = 256; + + if (pos >= size) + return 0; + if (nbytes >= size) + nbytes = size; + if (pos + nbytes > size) + nbytes = size - pos; + cnt = nbytes; + + if (!access_ok(buf, cnt)) + return -EINVAL; + + isapnp_cfg_begin(dev->card->number, dev->number); + for (; pos < 256 && cnt > 0; pos++, buf++, cnt--) { + unsigned char val; + val = isapnp_read_byte(pos); + __put_user(val, buf); + } + isapnp_cfg_end(); + + *ppos = pos; + return nbytes; +} + +static const struct proc_ops isapnp_proc_bus_proc_ops = { + .proc_lseek = isapnp_proc_bus_lseek, + .proc_read = isapnp_proc_bus_read, +}; + +static int isapnp_proc_attach_device(struct pnp_dev *dev) +{ + struct pnp_card *bus = dev->card; + char name[16]; + + if (!bus->procdir) { + sprintf(name, "%02x", bus->number); + bus->procdir = proc_mkdir(name, isapnp_proc_bus_dir); + if (!bus->procdir) + return -ENOMEM; + } + sprintf(name, "%02x", dev->number); + dev->procent = proc_create_data(name, S_IFREG | S_IRUGO, bus->procdir, + &isapnp_proc_bus_proc_ops, dev); + if (!dev->procent) + return -ENOMEM; + proc_set_size(dev->procent, 256); + return 0; +} + +int __init isapnp_proc_init(void) +{ + struct pnp_dev *dev; + + isapnp_proc_bus_dir = proc_mkdir("bus/isapnp", NULL); + protocol_for_each_dev(&isapnp_protocol, dev) { + isapnp_proc_attach_device(dev); + } + return 0; +} |