From a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 Mon Sep 17 00:00:00 2001 From: Jinke Han Date: Tue, 8 Sep 2026 15:37:42 +0800 Subject: x86/kprobes: Fix crash when probing CS CALL instructions When using eBPF to probe CS CALL instructions within a function, a crash can be triggered. The eBPF tool probes offset 257 of the __hrtimer_run_queues() function: <__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1) <__hrtimer_run_queues+254>: mov %r14,%rdi <__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12> <__hrtimer_run_queues+263>: mov %eax,%r12d <__hrtimer_run_queues+266>: xchg %ax,%ax <__hrtimer_run_queues+268>: mov %r13,%rdi Which triggers this crash: BUG: unable to handle page fault for address: 00000000000f41c9 #PF: supervisor write access in kernel mode #PF: error_code(0x0002) - not-present page PGD 0 P4D 0 Oops: 0002 [#1] SMP NOPTI CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P RIP: 0010:__hrtimer_run_queues+0x106/0x230 Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is at the 6th byte of the above CS CALL instruction. Since the CS CALL instruction occupies 6 bytes, the exception occurred in the middle of that call instruction. The root cause is that when using eBPF tools to probe in the middle of a function, a kprobe with INT3 is used as the underlying implementation. During single-step emulation of the original CALL instruction, int3_emulate_call() assumes that the probed CALL instruction is 5 bytes long. However, the actual CS-prefixed CALL instruction occupies 6 bytes, so it constructs an incorrect exception return address. When the CPU returns from the kprobe handler, the next instruction to be executed is at the address of the last byte of that CS CALL instruction. Coincidentally, starting from that address, the CPU fetches and decodes a completely different instruction, which ultimately triggers a kernel crash. Fix the issue by using the actual instruction length obtained from the instruction decoder when constructing the exception return address, rather than relying on the hardcoded CALL_INSN_SIZE macro. [ mingo: Refined the changelog ] Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step") Suggested-by: Masami Hiramatsu (Google) Signed-off-by: Jinke Han Signed-off-by: Ingo Molnar Reviewed-by: Masami Hiramatsu (Google) Acked-by: Yafang Shao Acked-by: Borislav Petkov Cc: Peter Zijlstra Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000 --- drivers/pnp/Kconfig | 48 ++ drivers/pnp/Makefile | 15 + drivers/pnp/base.h | 183 +++++++ drivers/pnp/card.c | 432 ++++++++++++++++ drivers/pnp/core.c | 224 ++++++++ drivers/pnp/driver.c | 341 +++++++++++++ drivers/pnp/interface.c | 470 +++++++++++++++++ drivers/pnp/isapnp/Kconfig | 12 + drivers/pnp/isapnp/Makefile | 8 + drivers/pnp/isapnp/compat.c | 66 +++ drivers/pnp/isapnp/core.c | 1071 +++++++++++++++++++++++++++++++++++++++ drivers/pnp/isapnp/proc.c | 86 ++++ drivers/pnp/manager.c | 429 ++++++++++++++++ drivers/pnp/pnpacpi/Kconfig | 7 + drivers/pnp/pnpacpi/Makefile | 7 + drivers/pnp/pnpacpi/core.c | 321 ++++++++++++ drivers/pnp/pnpacpi/pnpacpi.h | 12 + drivers/pnp/pnpacpi/rsparser.c | 948 ++++++++++++++++++++++++++++++++++ drivers/pnp/pnpbios/Kconfig | 43 ++ drivers/pnp/pnpbios/Makefile | 9 + drivers/pnp/pnpbios/bioscalls.c | 490 ++++++++++++++++++ drivers/pnp/pnpbios/core.c | 575 +++++++++++++++++++++ drivers/pnp/pnpbios/pnpbios.h | 168 ++++++ drivers/pnp/pnpbios/proc.c | 286 +++++++++++ drivers/pnp/pnpbios/rsparser.c | 810 +++++++++++++++++++++++++++++ drivers/pnp/quirks.c | 453 +++++++++++++++++ drivers/pnp/resource.c | 755 +++++++++++++++++++++++++++ drivers/pnp/support.c | 179 +++++++ drivers/pnp/system.c | 113 +++++ 29 files changed, 8561 insertions(+) create mode 100644 drivers/pnp/Kconfig create mode 100644 drivers/pnp/Makefile create mode 100644 drivers/pnp/base.h create mode 100644 drivers/pnp/card.c create mode 100644 drivers/pnp/core.c create mode 100644 drivers/pnp/driver.c create mode 100644 drivers/pnp/interface.c create mode 100644 drivers/pnp/isapnp/Kconfig create mode 100644 drivers/pnp/isapnp/Makefile create mode 100644 drivers/pnp/isapnp/compat.c create mode 100644 drivers/pnp/isapnp/core.c create mode 100644 drivers/pnp/isapnp/proc.c create mode 100644 drivers/pnp/manager.c create mode 100644 drivers/pnp/pnpacpi/Kconfig create mode 100644 drivers/pnp/pnpacpi/Makefile create mode 100644 drivers/pnp/pnpacpi/core.c create mode 100644 drivers/pnp/pnpacpi/pnpacpi.h create mode 100644 drivers/pnp/pnpacpi/rsparser.c create mode 100644 drivers/pnp/pnpbios/Kconfig create mode 100644 drivers/pnp/pnpbios/Makefile create mode 100644 drivers/pnp/pnpbios/bioscalls.c create mode 100644 drivers/pnp/pnpbios/core.c create mode 100644 drivers/pnp/pnpbios/pnpbios.h create mode 100644 drivers/pnp/pnpbios/proc.c create mode 100644 drivers/pnp/pnpbios/rsparser.c create mode 100644 drivers/pnp/quirks.c create mode 100644 drivers/pnp/resource.c create mode 100644 drivers/pnp/support.c create mode 100644 drivers/pnp/system.c (limited to 'drivers/pnp') diff --git a/drivers/pnp/Kconfig b/drivers/pnp/Kconfig new file mode 100644 index 000000000..a72141dff --- /dev/null +++ b/drivers/pnp/Kconfig @@ -0,0 +1,48 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Plug and Play configuration +# + +menuconfig PNP + bool "Plug and Play support" + depends on HAS_IOMEM + depends on ISA || ACPI + help + Plug and Play (PnP) is a standard for peripherals which allows those + peripherals to be configured by software, e.g. assign IRQ's or other + parameters. No jumpers on the cards are needed, instead the values + are provided to the cards from the BIOS, from the operating system, + or using a user-space utility. + + Say Y here if you would like Linux to configure your Plug and Play + devices. You should then also say Y to all of the protocols below. + Alternatively, you can say N here and configure your PnP devices + using user space utilities such as the isapnptools package. + + If unsure, say Y. + +config PNP_DEBUG_MESSAGES + default y + bool "PNP debugging messages" + depends on PNP + help + Say Y here if you want the PNP layer to be able to produce debugging + messages if needed. The messages can be enabled at boot-time with + the pnp.debug kernel parameter. + + This option allows you to save a bit of space if you do not want + the messages to even be built into the kernel. + + If you have any doubts about this, say Y here. + +if PNP + +comment "Protocols" + +source "drivers/pnp/isapnp/Kconfig" + +source "drivers/pnp/pnpbios/Kconfig" + +source "drivers/pnp/pnpacpi/Kconfig" + +endif # PNP diff --git a/drivers/pnp/Makefile b/drivers/pnp/Makefile new file mode 100644 index 000000000..71d532f18 --- /dev/null +++ b/drivers/pnp/Makefile @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the Linux Plug-and-Play Support. +# + +obj-y := pnp.o + +pnp-y := core.o card.o driver.o resource.o manager.o support.o interface.o quirks.o + +obj-$(CONFIG_PNPACPI) += pnpacpi/ +obj-$(CONFIG_PNPBIOS) += pnpbios/ +obj-$(CONFIG_ISAPNP) += isapnp/ + +# pnp_system_init goes after pnpacpi/pnpbios init +pnp-y += system.o diff --git a/drivers/pnp/base.h b/drivers/pnp/base.h new file mode 100644 index 000000000..b342570d0 --- /dev/null +++ b/drivers/pnp/base.h @@ -0,0 +1,183 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2008 Hewlett-Packard Development Company, L.P. + * Bjorn Helgaas + */ + +extern struct mutex pnp_lock; +extern const struct attribute_group *pnp_dev_groups[]; +extern const struct bus_type pnp_bus_type; + +int pnp_register_protocol(struct pnp_protocol *protocol); + +#define PNP_EISA_ID_MASK 0x7fffffff +void pnp_eisa_id_to_string(u32 id, char *str); +struct pnp_dev *pnp_alloc_dev(struct pnp_protocol *, int id, + const char *pnpid); +struct pnp_card *pnp_alloc_card(struct pnp_protocol *, int id, char *pnpid); + +int pnp_add_device(struct pnp_dev *dev); +struct pnp_id *pnp_add_id(struct pnp_dev *dev, const char *id); + +int pnp_add_card(struct pnp_card *card); +int pnp_add_card_device(struct pnp_card *card, struct pnp_dev *dev); + +struct pnp_port { + resource_size_t min; /* min base number */ + resource_size_t max; /* max base number */ + resource_size_t align; /* align boundary */ + resource_size_t size; /* size of range */ + unsigned char flags; /* port flags */ +}; + +#define PNP_IRQ_NR 256 +typedef struct { DECLARE_BITMAP(bits, PNP_IRQ_NR); } pnp_irq_mask_t; + +struct pnp_irq { + pnp_irq_mask_t map; /* bitmap for IRQ lines */ + unsigned char flags; /* IRQ flags */ +}; + +struct pnp_dma { + unsigned char map; /* bitmask for DMA channels */ + unsigned char flags; /* DMA flags */ +}; + +struct pnp_mem { + resource_size_t min; /* min base number */ + resource_size_t max; /* max base number */ + resource_size_t align; /* align boundary */ + resource_size_t size; /* size of range */ + unsigned char flags; /* memory flags */ +}; + +#define PNP_OPTION_DEPENDENT 0x80000000 +#define PNP_OPTION_SET_MASK 0xffff +#define PNP_OPTION_SET_SHIFT 12 +#define PNP_OPTION_PRIORITY_MASK 0xfff +#define PNP_OPTION_PRIORITY_SHIFT 0 + +#define PNP_RES_PRIORITY_PREFERRED 0 +#define PNP_RES_PRIORITY_ACCEPTABLE 1 +#define PNP_RES_PRIORITY_FUNCTIONAL 2 +#define PNP_RES_PRIORITY_INVALID PNP_OPTION_PRIORITY_MASK + +struct pnp_option { + struct list_head list; + unsigned int flags; /* independent/dependent, set, priority */ + + unsigned long type; /* IORESOURCE_{IO,MEM,IRQ,DMA} */ + union { + struct pnp_port port; + struct pnp_irq irq; + struct pnp_dma dma; + struct pnp_mem mem; + } u; +}; + +int pnp_register_irq_resource(struct pnp_dev *dev, unsigned int option_flags, + pnp_irq_mask_t *map, unsigned char flags); +int pnp_register_dma_resource(struct pnp_dev *dev, unsigned int option_flags, + unsigned char map, unsigned char flags); +int pnp_register_port_resource(struct pnp_dev *dev, unsigned int option_flags, + resource_size_t min, resource_size_t max, + resource_size_t align, resource_size_t size, + unsigned char flags); +int pnp_register_mem_resource(struct pnp_dev *dev, unsigned int option_flags, + resource_size_t min, resource_size_t max, + resource_size_t align, resource_size_t size, + unsigned char flags); + +static inline int pnp_option_is_dependent(struct pnp_option *option) +{ + return option->flags & PNP_OPTION_DEPENDENT ? 1 : 0; +} + +static inline unsigned int pnp_option_set(struct pnp_option *option) +{ + return (option->flags >> PNP_OPTION_SET_SHIFT) & PNP_OPTION_SET_MASK; +} + +static inline unsigned int pnp_option_priority(struct pnp_option *option) +{ + return (option->flags >> PNP_OPTION_PRIORITY_SHIFT) & + PNP_OPTION_PRIORITY_MASK; +} + +static inline unsigned int pnp_new_dependent_set(struct pnp_dev *dev, + int priority) +{ + unsigned int flags; + + if (priority > PNP_RES_PRIORITY_FUNCTIONAL) { + dev_warn(&dev->dev, "invalid dependent option priority %d " + "clipped to %d", priority, + PNP_RES_PRIORITY_INVALID); + priority = PNP_RES_PRIORITY_INVALID; + } + + flags = PNP_OPTION_DEPENDENT | + ((dev->num_dependent_sets & PNP_OPTION_SET_MASK) << + PNP_OPTION_SET_SHIFT) | + ((priority & PNP_OPTION_PRIORITY_MASK) << + PNP_OPTION_PRIORITY_SHIFT); + + dev->num_dependent_sets++; + + return flags; +} + +char *pnp_option_priority_name(struct pnp_option *option); +void dbg_pnp_show_option(struct pnp_dev *dev, struct pnp_option *option); + +void pnp_init_resources(struct pnp_dev *dev); + +void pnp_fixup_device(struct pnp_dev *dev); +void pnp_free_options(struct pnp_dev *dev); +int __pnp_add_device(struct pnp_dev *dev); + +int pnp_check_port(struct pnp_dev *dev, struct resource *res); +int pnp_check_mem(struct pnp_dev *dev, struct resource *res); +int pnp_check_irq(struct pnp_dev *dev, struct resource *res); +#ifdef CONFIG_ISA_DMA_API +int pnp_check_dma(struct pnp_dev *dev, struct resource *res); +#endif + +char *pnp_resource_type_name(struct resource *res); +void dbg_pnp_show_resources(struct pnp_dev *dev, char *desc); + +void pnp_free_resources(struct pnp_dev *dev); +unsigned long pnp_resource_type(struct resource *res); + +struct pnp_resource { + struct list_head list; + struct resource res; +}; + +void pnp_free_resource(struct pnp_resource *pnp_res); + +struct pnp_resource *pnp_add_resource(struct pnp_dev *dev, + struct resource *res); +struct pnp_resource *pnp_add_irq_resource(struct pnp_dev *dev, int irq, + int flags); +struct pnp_resource *pnp_add_dma_resource(struct pnp_dev *dev, int dma, + int flags); +struct pnp_resource *pnp_add_io_resource(struct pnp_dev *dev, + resource_size_t start, + resource_size_t end, int flags); +struct pnp_resource *pnp_add_mem_resource(struct pnp_dev *dev, + resource_size_t start, + resource_size_t end, int flags); +struct pnp_resource *pnp_add_bus_resource(struct pnp_dev *dev, + resource_size_t start, + resource_size_t end); + +extern int pnp_debug; + +#if defined(CONFIG_PNP_DEBUG_MESSAGES) +#define pnp_dbg(dev, format, arg...) \ + ({ if (pnp_debug) dev_printk(KERN_DEBUG, dev, format, ## arg); 0; }) +#else +#define pnp_dbg(dev, format, arg...) \ + ({ if (0) dev_printk(KERN_DEBUG, dev, format, ## arg); 0; }) +#endif diff --git a/drivers/pnp/card.c b/drivers/pnp/card.c new file mode 100644 index 000000000..df5e3d3cb --- /dev/null +++ b/drivers/pnp/card.c @@ -0,0 +1,432 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * card.c - contains functions for managing groups of PnP devices + * + * Copyright 2002 Adam Belay + */ + +#include +#include +#include +#include +#include +#include +#include "base.h" + +LIST_HEAD(pnp_cards); +static LIST_HEAD(pnp_card_drivers); + +static const struct pnp_card_device_id *match_card(struct pnp_card_driver *drv, + struct pnp_card *card) +{ + const struct pnp_card_device_id *drv_id = drv->id_table; + + while (*drv_id->id) { + if (compare_pnp_id(card->id, drv_id->id)) { + int i = 0; + + for (;;) { + int found; + struct pnp_dev *dev; + + if (i == PNP_MAX_DEVICES || + !*drv_id->devs[i].id) + return drv_id; + found = 0; + card_for_each_dev(card, dev) { + if (compare_pnp_id(dev->id, + drv_id->devs[i].id)) { + found = 1; + break; + } + } + if (!found) + break; + i++; + } + } + drv_id++; + } + return NULL; +} + +static void card_remove(struct pnp_dev *dev) +{ + dev->card_link = NULL; +} + +static void card_remove_first(struct pnp_dev *dev) +{ + struct pnp_card_driver *drv = to_pnp_card_driver(dev->driver); + + if (!dev->card || !drv) + return; + if (drv->remove) + drv->remove(dev->card_link); + drv->link.remove = &card_remove; + kfree(dev->card_link); + card_remove(dev); +} + +static int card_probe(struct pnp_card *card, struct pnp_card_driver *drv) +{ + const struct pnp_card_device_id *id; + struct pnp_card_link *clink; + struct pnp_dev *dev; + + if (!drv->probe) + return 0; + id = match_card(drv, card); + if (!id) + return 0; + + clink = kzalloc_obj(*clink); + if (!clink) + return 0; + clink->card = card; + clink->driver = drv; + clink->pm_state = PMSG_ON; + + if (drv->probe(clink, id) >= 0) + return 1; + + /* Recovery */ + card_for_each_dev(card, dev) { + if (dev->card_link == clink) + pnp_release_card_device(dev); + } + kfree(clink); + return 0; +} + +/** + * pnp_add_card_id - adds an EISA id to the specified card + * @id: pointer to a pnp_id structure + * @card: pointer to the desired card + */ +static struct pnp_id *pnp_add_card_id(struct pnp_card *card, char *id) +{ + struct pnp_id *dev_id, *ptr; + + dev_id = kzalloc_obj(struct pnp_id); + if (!dev_id) + return NULL; + + dev_id->id[0] = id[0]; + dev_id->id[1] = id[1]; + dev_id->id[2] = id[2]; + dev_id->id[3] = tolower(id[3]); + dev_id->id[4] = tolower(id[4]); + dev_id->id[5] = tolower(id[5]); + dev_id->id[6] = tolower(id[6]); + dev_id->id[7] = '\0'; + + dev_id->next = NULL; + ptr = card->id; + while (ptr && ptr->next) + ptr = ptr->next; + if (ptr) + ptr->next = dev_id; + else + card->id = dev_id; + + return dev_id; +} + +static void pnp_free_card_ids(struct pnp_card *card) +{ + struct pnp_id *id; + struct pnp_id *next; + + id = card->id; + while (id) { + next = id->next; + kfree(id); + id = next; + } +} + +static void pnp_release_card(struct device *dmdev) +{ + struct pnp_card *card = to_pnp_card(dmdev); + + pnp_free_card_ids(card); + kfree(card); +} + +struct pnp_card *pnp_alloc_card(struct pnp_protocol *protocol, int id, char *pnpid) +{ + struct pnp_card *card; + struct pnp_id *dev_id; + + card = kzalloc_obj(struct pnp_card); + if (!card) + return NULL; + + card->protocol = protocol; + card->number = id; + + card->dev.parent = &card->protocol->dev; + dev_set_name(&card->dev, "%02x:%02x", card->protocol->number, card->number); + + card->dev.coherent_dma_mask = DMA_BIT_MASK(24); + card->dev.dma_mask = &card->dev.coherent_dma_mask; + + dev_id = pnp_add_card_id(card, pnpid); + if (!dev_id) { + kfree(card); + return NULL; + } + + return card; +} + +static ssize_t name_show(struct device *dmdev, + struct device_attribute *attr, char *buf) +{ + char *str = buf; + struct pnp_card *card = to_pnp_card(dmdev); + + str += sprintf(str, "%s\n", card->name); + return (str - buf); +} + +static DEVICE_ATTR_RO(name); + +static ssize_t card_id_show(struct device *dmdev, + struct device_attribute *attr, char *buf) +{ + char *str = buf; + struct pnp_card *card = to_pnp_card(dmdev); + struct pnp_id *pos = card->id; + + while (pos) { + str += sprintf(str, "%s\n", pos->id); + pos = pos->next; + } + return (str - buf); +} + +static DEVICE_ATTR_RO(card_id); + +static int pnp_interface_attach_card(struct pnp_card *card) +{ + int rc = device_create_file(&card->dev, &dev_attr_name); + + if (rc) + return rc; + + rc = device_create_file(&card->dev, &dev_attr_card_id); + if (rc) + goto err_name; + + return 0; + +err_name: + device_remove_file(&card->dev, &dev_attr_name); + return rc; +} + +/** + * pnp_add_card - adds a PnP card to the PnP Layer + * @card: pointer to the card to add + */ +int pnp_add_card(struct pnp_card *card) +{ + int error; + struct list_head *pos, *temp; + + card->dev.bus = NULL; + card->dev.release = &pnp_release_card; + error = device_register(&card->dev); + if (error) { + dev_err(&card->dev, "could not register (err=%d)\n", error); + put_device(&card->dev); + return error; + } + + pnp_interface_attach_card(card); + mutex_lock(&pnp_lock); + list_add_tail(&card->global_list, &pnp_cards); + list_add_tail(&card->protocol_list, &card->protocol->cards); + mutex_unlock(&pnp_lock); + + /* we wait until now to add devices in order to ensure the drivers + * will be able to use all of the related devices on the card + * without waiting an unreasonable length of time */ + list_for_each_safe(pos, temp, &card->devices) { + struct pnp_dev *dev = card_to_pnp_dev(pos); + error = __pnp_add_device(dev); + if (error) { + mutex_lock(&pnp_lock); + list_del(&dev->card_list); + dev->card = NULL; + mutex_unlock(&pnp_lock); + put_device(&dev->dev); + } + } + + /* match with card drivers */ + list_for_each_safe(pos, temp, &pnp_card_drivers) { + struct pnp_card_driver *drv = + list_entry(pos, struct pnp_card_driver, + global_list); + card_probe(card, drv); + } + return 0; +} + +/** + * pnp_add_card_device - adds a device to the specified card + * @card: pointer to the card to add to + * @dev: pointer to the device to add + */ +int pnp_add_card_device(struct pnp_card *card, struct pnp_dev *dev) +{ + dev->dev.parent = &card->dev; + dev->card_link = NULL; + dev_set_name(&dev->dev, "%02x:%02x.%02x", + dev->protocol->number, card->number, dev->number); + mutex_lock(&pnp_lock); + dev->card = card; + list_add_tail(&dev->card_list, &card->devices); + mutex_unlock(&pnp_lock); + return 0; +} + +/** + * pnp_request_card_device - Searches for a PnP device under the specified card + * @clink: pointer to the card link, cannot be NULL + * @id: pointer to a PnP ID structure that explains the rules for finding the device + * @from: Starting place to search from. If NULL it will start from the beginning. + */ +struct pnp_dev *pnp_request_card_device(struct pnp_card_link *clink, + const char *id, struct pnp_dev *from) +{ + struct list_head *pos; + struct pnp_dev *dev; + struct pnp_card_driver *drv; + struct pnp_card *card; + + if (!clink || !id) + return NULL; + + card = clink->card; + drv = clink->driver; + if (!from) { + pos = card->devices.next; + } else { + if (from->card != card) + return NULL; + pos = from->card_list.next; + } + while (pos != &card->devices) { + dev = card_to_pnp_dev(pos); + if ((!dev->card_link) && compare_pnp_id(dev->id, id)) + goto found; + pos = pos->next; + } + + return NULL; + +found: + dev->card_link = clink; + dev->dev.driver = &drv->link.driver; + if (pnp_bus_type.probe(&dev->dev)) + goto err_out; + if (device_bind_driver(&dev->dev)) + goto err_out; + + return dev; + +err_out: + dev->dev.driver = NULL; + dev->card_link = NULL; + return NULL; +} +EXPORT_SYMBOL(pnp_request_card_device); + +/** + * pnp_release_card_device - call this when the driver no longer needs the device + * @dev: pointer to the PnP device structure + */ +void pnp_release_card_device(struct pnp_dev *dev) +{ + struct pnp_card_driver *drv = dev->card_link->driver; + + drv->link.remove = &card_remove; + device_release_driver(&dev->dev); + drv->link.remove = &card_remove_first; +} +EXPORT_SYMBOL(pnp_release_card_device); + +/* + * suspend/resume callbacks + */ +static int card_suspend(struct pnp_dev *dev, pm_message_t state) +{ + struct pnp_card_link *link = dev->card_link; + + if (link->pm_state.event == state.event) + return 0; + link->pm_state = state; + return link->driver->suspend(link, state); +} + +static int card_resume(struct pnp_dev *dev) +{ + struct pnp_card_link *link = dev->card_link; + + if (link->pm_state.event == PM_EVENT_ON) + return 0; + link->pm_state = PMSG_ON; + link->driver->resume(link); + return 0; +} + +/** + * pnp_register_card_driver - registers a PnP card driver with the PnP Layer + * @drv: pointer to the driver to register + */ +int pnp_register_card_driver(struct pnp_card_driver *drv) +{ + int error; + struct list_head *pos, *temp; + + drv->link.name = drv->name; + drv->link.id_table = NULL; /* this will disable auto matching */ + drv->link.flags = drv->flags; + drv->link.probe = NULL; + drv->link.remove = &card_remove_first; + drv->link.suspend = drv->suspend ? card_suspend : NULL; + drv->link.resume = drv->resume ? card_resume : NULL; + + error = pnp_register_driver(&drv->link); + if (error < 0) + return error; + + mutex_lock(&pnp_lock); + list_add_tail(&drv->global_list, &pnp_card_drivers); + mutex_unlock(&pnp_lock); + + list_for_each_safe(pos, temp, &pnp_cards) { + struct pnp_card *card = + list_entry(pos, struct pnp_card, global_list); + card_probe(card, drv); + } + return 0; +} +EXPORT_SYMBOL(pnp_register_card_driver); + +/** + * pnp_unregister_card_driver - unregisters a PnP card driver from the PnP Layer + * @drv: pointer to the driver to unregister + */ +void pnp_unregister_card_driver(struct pnp_card_driver *drv) +{ + mutex_lock(&pnp_lock); + list_del(&drv->global_list); + mutex_unlock(&pnp_lock); + pnp_unregister_driver(&drv->link); +} +EXPORT_SYMBOL(pnp_unregister_card_driver); diff --git a/drivers/pnp/core.c b/drivers/pnp/core.c new file mode 100644 index 000000000..816033270 --- /dev/null +++ b/drivers/pnp/core.c @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * core.c - contains all core device and protocol registration functions + * + * Copyright 2002 Adam Belay + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "base.h" + +static LIST_HEAD(pnp_protocols); +LIST_HEAD(pnp_global); +DEFINE_MUTEX(pnp_lock); + +/* + * ACPI or PNPBIOS should tell us about all platform devices, so we can + * skip some blind probes. ISAPNP typically enumerates only plug-in ISA + * devices, not built-in things like COM ports. + */ +int pnp_platform_devices; +EXPORT_SYMBOL(pnp_platform_devices); + +static void pnp_remove_protocol(struct pnp_protocol *protocol) +{ + mutex_lock(&pnp_lock); + list_del(&protocol->protocol_list); + mutex_unlock(&pnp_lock); +} + +/** + * pnp_register_protocol - adds a pnp protocol to the pnp layer + * @protocol: pointer to the corresponding pnp_protocol structure + * + * Ex protocols: ISAPNP, PNPBIOS, etc + */ +int pnp_register_protocol(struct pnp_protocol *protocol) +{ + struct list_head *pos; + int nodenum, ret; + + INIT_LIST_HEAD(&protocol->devices); + INIT_LIST_HEAD(&protocol->cards); + nodenum = 0; + + mutex_lock(&pnp_lock); + + /* assign the lowest unused number */ + list_for_each(pos, &pnp_protocols) { + struct pnp_protocol *cur = to_pnp_protocol(pos); + if (cur->number == nodenum) { + pos = &pnp_protocols; + nodenum++; + } + } + + protocol->number = nodenum; + dev_set_name(&protocol->dev, "pnp%d", nodenum); + + list_add_tail(&protocol->protocol_list, &pnp_protocols); + + mutex_unlock(&pnp_lock); + + ret = device_register(&protocol->dev); + if (ret) + pnp_remove_protocol(protocol); + + return ret; +} + +static void pnp_free_ids(struct pnp_dev *dev) +{ + struct pnp_id *id; + struct pnp_id *next; + + id = dev->id; + while (id) { + next = id->next; + kfree(id); + id = next; + } +} + +void pnp_free_resource(struct pnp_resource *pnp_res) +{ + list_del(&pnp_res->list); + kfree(pnp_res); +} + +void pnp_free_resources(struct pnp_dev *dev) +{ + struct pnp_resource *pnp_res, *tmp; + + list_for_each_entry_safe(pnp_res, tmp, &dev->resources, list) { + pnp_free_resource(pnp_res); + } +} + +static void pnp_release_device(struct device *dmdev) +{ + struct pnp_dev *dev = to_pnp_dev(dmdev); + + pnp_free_ids(dev); + pnp_free_resources(dev); + pnp_free_options(dev); + kfree(dev); +} + +struct pnp_dev *pnp_alloc_dev(struct pnp_protocol *protocol, int id, + const char *pnpid) +{ + struct pnp_dev *dev; + struct pnp_id *dev_id; + + dev = kzalloc_obj(struct pnp_dev); + if (!dev) + return NULL; + + INIT_LIST_HEAD(&dev->resources); + INIT_LIST_HEAD(&dev->options); + dev->protocol = protocol; + dev->number = id; + dev->dma_mask = DMA_BIT_MASK(24); + + dev->dev.parent = &dev->protocol->dev; + dev->dev.bus = &pnp_bus_type; + dev->dev.dma_mask = &dev->dma_mask; + dev->dev.coherent_dma_mask = dev->dma_mask; + dev->dev.release = &pnp_release_device; + + dev_id = pnp_add_id(dev, pnpid); + if (!dev_id) { + kfree(dev); + return NULL; + } + + dev_set_name(&dev->dev, "%02x:%02x", dev->protocol->number, dev->number); + + return dev; +} + +static void pnp_delist_device(struct pnp_dev *dev) +{ + mutex_lock(&pnp_lock); + list_del(&dev->global_list); + list_del(&dev->protocol_list); + mutex_unlock(&pnp_lock); +} + +int __pnp_add_device(struct pnp_dev *dev) +{ + int ret; + + pnp_fixup_device(dev); + dev->status = PNP_READY; + + mutex_lock(&pnp_lock); + + list_add_tail(&dev->global_list, &pnp_global); + list_add_tail(&dev->protocol_list, &dev->protocol->devices); + + mutex_unlock(&pnp_lock); + + ret = device_register(&dev->dev); + if (ret) + pnp_delist_device(dev); + else if (dev->protocol->can_wakeup) + device_set_wakeup_capable(&dev->dev, + dev->protocol->can_wakeup(dev)); + + return ret; +} + +/* + * pnp_add_device - adds a pnp device to the pnp layer + * @dev: pointer to dev to add + * + * adds to driver model, name database, fixups, interface, etc. + */ +int pnp_add_device(struct pnp_dev *dev) +{ + int ret; + char buf[128]; + int len = 0; + struct pnp_id *id; + + if (dev->card) + return -EINVAL; + + ret = __pnp_add_device(dev); + if (ret) + return ret; + + buf[0] = '\0'; + for (id = dev->id; id; id = id->next) + len += scnprintf(buf + len, sizeof(buf) - len, " %s", id->id); + + dev_dbg(&dev->dev, "%s device, IDs%s (%s)\n", dev->protocol->name, buf, + dev->active ? "active" : "disabled"); + return 0; +} + +static int __init pnp_init(void) +{ + return bus_register(&pnp_bus_type); +} + +subsys_initcall(pnp_init); + +int pnp_debug; + +#if defined(CONFIG_PNP_DEBUG_MESSAGES) +module_param_named(debug, pnp_debug, int, 0644); +#endif diff --git a/drivers/pnp/driver.c b/drivers/pnp/driver.c new file mode 100644 index 000000000..6d58b1465 --- /dev/null +++ b/drivers/pnp/driver.c @@ -0,0 +1,341 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * driver.c - device id matching, driver model, etc. + * + * Copyright 2002 Adam Belay + */ + +#include +#include +#include +#include +#include +#include +#include "base.h" + +static int compare_func(const char *ida, const char *idb) +{ + int i; + + /* we only need to compare the last 4 chars */ + for (i = 3; i < 7; i++) { + if (ida[i] != 'X' && + idb[i] != 'X' && toupper(ida[i]) != toupper(idb[i])) + return 0; + } + return 1; +} + +int compare_pnp_id(struct pnp_id *pos, const char *id) +{ + if (!pos || !id || (strlen(id) != 7)) + return 0; + if (memcmp(id, "ANYDEVS", 7) == 0) + return 1; + while (pos) { + if (memcmp(pos->id, id, 3) == 0) + if (compare_func(pos->id, id) == 1) + return 1; + pos = pos->next; + } + return 0; +} + +static const struct pnp_device_id *match_device(const struct pnp_driver *drv, + struct pnp_dev *dev) +{ + const struct pnp_device_id *drv_id = drv->id_table; + + if (!drv_id) + return NULL; + + while (*drv_id->id) { + if (compare_pnp_id(dev->id, drv_id->id)) + return drv_id; + drv_id++; + } + return NULL; +} + +int pnp_device_attach(struct pnp_dev *pnp_dev) +{ + mutex_lock(&pnp_lock); + if (pnp_dev->status != PNP_READY) { + mutex_unlock(&pnp_lock); + return -EBUSY; + } + pnp_dev->status = PNP_ATTACHED; + mutex_unlock(&pnp_lock); + return 0; +} +EXPORT_SYMBOL(pnp_device_attach); + +void pnp_device_detach(struct pnp_dev *pnp_dev) +{ + mutex_lock(&pnp_lock); + if (pnp_dev->status == PNP_ATTACHED) + pnp_dev->status = PNP_READY; + mutex_unlock(&pnp_lock); +} +EXPORT_SYMBOL(pnp_device_detach); + +static int pnp_device_probe(struct device *dev) +{ + int error; + struct pnp_driver *pnp_drv; + struct pnp_dev *pnp_dev; + const struct pnp_device_id *dev_id = NULL; + pnp_dev = to_pnp_dev(dev); + pnp_drv = to_pnp_driver(dev->driver); + + error = pnp_device_attach(pnp_dev); + if (error < 0) + return error; + + if (pnp_dev->active == 0) { + if (!(pnp_drv->flags & PNP_DRIVER_RES_DO_NOT_CHANGE)) { + error = pnp_activate_dev(pnp_dev); + if (error < 0) + return error; + } + } else if ((pnp_drv->flags & PNP_DRIVER_RES_DISABLE) + == PNP_DRIVER_RES_DISABLE) { + error = pnp_disable_dev(pnp_dev); + if (error < 0) + return error; + } + error = 0; + if (pnp_drv->probe) { + dev_id = match_device(pnp_drv, pnp_dev); + if (dev_id != NULL) + error = pnp_drv->probe(pnp_dev, dev_id); + } + if (error >= 0) { + pnp_dev->driver = pnp_drv; + error = 0; + } else + goto fail; + + return error; + +fail: + pnp_device_detach(pnp_dev); + return error; +} + +static void pnp_device_remove(struct device *dev) +{ + struct pnp_dev *pnp_dev = to_pnp_dev(dev); + struct pnp_driver *drv = pnp_dev->driver; + + if (drv) { + if (drv->remove) + drv->remove(pnp_dev); + pnp_dev->driver = NULL; + } + + if (pnp_dev->active && + (!drv || !(drv->flags & PNP_DRIVER_RES_DO_NOT_CHANGE))) + pnp_disable_dev(pnp_dev); + + pnp_device_detach(pnp_dev); +} + +static void pnp_device_shutdown(struct device *dev) +{ + struct pnp_dev *pnp_dev = to_pnp_dev(dev); + struct pnp_driver *drv = pnp_dev->driver; + + if (drv && drv->shutdown) + drv->shutdown(pnp_dev); +} + +static int pnp_uevent(const struct device *dev, struct kobj_uevent_env *env) +{ + struct pnp_id *pos; + const struct pnp_dev *pnp_dev = to_pnp_dev(dev); + + if (!dev) + return -ENODEV; + + pos = pnp_dev->id; + while (pos) { + if (add_uevent_var(env, "MODALIAS=pnp:d%s", pos->id)) + return -ENOMEM; + pos = pos->next; + } + + return 0; +} + +static int pnp_bus_match(struct device *dev, const struct device_driver *drv) +{ + struct pnp_dev *pnp_dev = to_pnp_dev(dev); + const struct pnp_driver *pnp_drv = to_pnp_driver(drv); + + if (match_device(pnp_drv, pnp_dev) == NULL) + return 0; + return 1; +} + +static int __pnp_bus_suspend(struct device *dev, pm_message_t state) +{ + struct pnp_dev *pnp_dev = to_pnp_dev(dev); + struct pnp_driver *pnp_drv = pnp_dev->driver; + int error; + + if (!pnp_drv) + return 0; + + if (pnp_drv->driver.pm && pnp_drv->driver.pm->suspend) { + error = pnp_drv->driver.pm->suspend(dev); + suspend_report_result(dev, pnp_drv->driver.pm->suspend, error); + if (error) + return error; + } + + if (pnp_drv->suspend) { + error = pnp_drv->suspend(pnp_dev, state); + if (error) + return error; + } + + /* can_write is necessary to be able to re-start the device on resume */ + if (pnp_can_disable(pnp_dev) && pnp_can_write(pnp_dev)) { + error = pnp_stop_dev(pnp_dev); + if (error) + return error; + } + + if (pnp_can_suspend(pnp_dev)) + pnp_dev->protocol->suspend(pnp_dev, state); + return 0; +} + +static int pnp_bus_suspend(struct device *dev) +{ + return __pnp_bus_suspend(dev, PMSG_SUSPEND); +} + +static int pnp_bus_freeze(struct device *dev) +{ + return __pnp_bus_suspend(dev, PMSG_FREEZE); +} + +static int pnp_bus_poweroff(struct device *dev) +{ + return __pnp_bus_suspend(dev, PMSG_HIBERNATE); +} + +static int pnp_bus_resume(struct device *dev) +{ + struct pnp_dev *pnp_dev = to_pnp_dev(dev); + struct pnp_driver *pnp_drv = pnp_dev->driver; + int error; + + if (!pnp_drv) + return 0; + + if (pnp_dev->protocol->resume) { + error = pnp_dev->protocol->resume(pnp_dev); + if (error) + return error; + } + + if (pnp_can_write(pnp_dev)) { + error = pnp_start_dev(pnp_dev); + if (error) + return error; + } + + if (pnp_drv->driver.pm && pnp_drv->driver.pm->resume) { + error = pnp_drv->driver.pm->resume(dev); + if (error) + return error; + } + + if (pnp_drv->resume) { + error = pnp_drv->resume(pnp_dev); + if (error) + return error; + } + + return 0; +} + +static const struct dev_pm_ops pnp_bus_dev_pm_ops = { + /* Suspend callbacks */ + .suspend = pnp_bus_suspend, + .resume = pnp_bus_resume, + /* Hibernate callbacks */ + .freeze = pnp_bus_freeze, + .thaw = pnp_bus_resume, + .poweroff = pnp_bus_poweroff, + .restore = pnp_bus_resume, +}; + +const struct bus_type pnp_bus_type = { + .name = "pnp", + .match = pnp_bus_match, + .uevent = pnp_uevent, + .probe = pnp_device_probe, + .remove = pnp_device_remove, + .shutdown = pnp_device_shutdown, + .pm = &pnp_bus_dev_pm_ops, + .dev_groups = pnp_dev_groups, +}; + +bool dev_is_pnp(const struct device *dev) +{ + return dev->bus == &pnp_bus_type; +} +EXPORT_SYMBOL_GPL(dev_is_pnp); + +int pnp_register_driver(struct pnp_driver *drv) +{ + drv->driver.name = drv->name; + drv->driver.bus = &pnp_bus_type; + + return driver_register(&drv->driver); +} +EXPORT_SYMBOL(pnp_register_driver); + +void pnp_unregister_driver(struct pnp_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL(pnp_unregister_driver); + +/** + * pnp_add_id - adds an EISA id to the specified device + * @dev: pointer to the desired device + * @id: pointer to an EISA id string + */ +struct pnp_id *pnp_add_id(struct pnp_dev *dev, const char *id) +{ + struct pnp_id *dev_id, *ptr; + + dev_id = kzalloc_obj(struct pnp_id); + if (!dev_id) + return NULL; + + dev_id->id[0] = id[0]; + dev_id->id[1] = id[1]; + dev_id->id[2] = id[2]; + dev_id->id[3] = tolower(id[3]); + dev_id->id[4] = tolower(id[4]); + dev_id->id[5] = tolower(id[5]); + dev_id->id[6] = tolower(id[6]); + dev_id->id[7] = '\0'; + + dev_id->next = NULL; + ptr = dev->id; + while (ptr && ptr->next) + ptr = ptr->next; + if (ptr) + ptr->next = dev_id; + else + dev->id = dev_id; + + return dev_id; +} diff --git a/drivers/pnp/interface.c b/drivers/pnp/interface.c new file mode 100644 index 000000000..9303fef72 --- /dev/null +++ b/drivers/pnp/interface.c @@ -0,0 +1,470 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * interface.c - contains everything related to the user interface + * + * Some code, especially possible resource dumping is based on isapnp_proc.c (c) Jaroslav Kysela + * Copyright 2002 Adam Belay + * Copyright (C) 2008 Hewlett-Packard Development Company, L.P. + * Bjorn Helgaas + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "base.h" + +struct pnp_info_buffer { + char *buffer; /* pointer to begin of buffer */ + char *curr; /* current position in buffer */ + unsigned long size; /* current size */ + unsigned long len; /* total length of buffer */ + int stop; /* stop flag */ + int error; /* error code */ +}; + +typedef struct pnp_info_buffer pnp_info_buffer_t; + +__printf(2, 3) +static int pnp_printf(pnp_info_buffer_t * buffer, char *fmt, ...) +{ + va_list args; + int res; + + if (buffer->stop || buffer->error) + return 0; + va_start(args, fmt); + res = vsnprintf(buffer->curr, buffer->len - buffer->size, fmt, args); + va_end(args); + if (buffer->size + res >= buffer->len) { + buffer->stop = 1; + return 0; + } + buffer->curr += res; + buffer->size += res; + return res; +} + +static void pnp_print_port(pnp_info_buffer_t * buffer, char *space, + struct pnp_port *port) +{ + pnp_printf(buffer, "%sport %#llx-%#llx, align %#llx, size %#llx, " + "%i-bit address decoding\n", space, + (unsigned long long) port->min, + (unsigned long long) port->max, + port->align ? ((unsigned long long) port->align - 1) : 0, + (unsigned long long) port->size, + port->flags & IORESOURCE_IO_16BIT_ADDR ? 16 : 10); +} + +static void pnp_print_irq(pnp_info_buffer_t * buffer, char *space, + struct pnp_irq *irq) +{ + int first = 1, i; + + pnp_printf(buffer, "%sirq ", space); + for (i = 0; i < PNP_IRQ_NR; i++) + if (test_bit(i, irq->map.bits)) { + if (!first) { + pnp_printf(buffer, ","); + } else { + first = 0; + } + if (i == 2 || i == 9) + pnp_printf(buffer, "2/9"); + else + pnp_printf(buffer, "%i", i); + } + if (bitmap_empty(irq->map.bits, PNP_IRQ_NR)) + pnp_printf(buffer, ""); + if (irq->flags & IORESOURCE_IRQ_HIGHEDGE) + pnp_printf(buffer, " High-Edge"); + if (irq->flags & IORESOURCE_IRQ_LOWEDGE) + pnp_printf(buffer, " Low-Edge"); + if (irq->flags & IORESOURCE_IRQ_HIGHLEVEL) + pnp_printf(buffer, " High-Level"); + if (irq->flags & IORESOURCE_IRQ_LOWLEVEL) + pnp_printf(buffer, " Low-Level"); + if (irq->flags & IORESOURCE_IRQ_OPTIONAL) + pnp_printf(buffer, " (optional)"); + pnp_printf(buffer, "\n"); +} + +static void pnp_print_dma(pnp_info_buffer_t * buffer, char *space, + struct pnp_dma *dma) +{ + int first = 1, i; + char *s; + + pnp_printf(buffer, "%sdma ", space); + for (i = 0; i < 8; i++) + if (dma->map & (1 << i)) { + if (!first) { + pnp_printf(buffer, ","); + } else { + first = 0; + } + pnp_printf(buffer, "%i", i); + } + if (!dma->map) + pnp_printf(buffer, ""); + switch (dma->flags & IORESOURCE_DMA_TYPE_MASK) { + case IORESOURCE_DMA_8BIT: + s = "8-bit"; + break; + case IORESOURCE_DMA_8AND16BIT: + s = "8-bit&16-bit"; + break; + default: + s = "16-bit"; + } + pnp_printf(buffer, " %s", s); + if (dma->flags & IORESOURCE_DMA_MASTER) + pnp_printf(buffer, " master"); + if (dma->flags & IORESOURCE_DMA_BYTE) + pnp_printf(buffer, " byte-count"); + if (dma->flags & IORESOURCE_DMA_WORD) + pnp_printf(buffer, " word-count"); + switch (dma->flags & IORESOURCE_DMA_SPEED_MASK) { + case IORESOURCE_DMA_TYPEA: + s = "type-A"; + break; + case IORESOURCE_DMA_TYPEB: + s = "type-B"; + break; + case IORESOURCE_DMA_TYPEF: + s = "type-F"; + break; + default: + s = "compatible"; + break; + } + pnp_printf(buffer, " %s\n", s); +} + +static void pnp_print_mem(pnp_info_buffer_t * buffer, char *space, + struct pnp_mem *mem) +{ + char *s; + + pnp_printf(buffer, "%sMemory %#llx-%#llx, align %#llx, size %#llx", + space, (unsigned long long) mem->min, + (unsigned long long) mem->max, + (unsigned long long) mem->align, + (unsigned long long) mem->size); + if (mem->flags & IORESOURCE_MEM_WRITEABLE) + pnp_printf(buffer, ", writeable"); + if (mem->flags & IORESOURCE_MEM_CACHEABLE) + pnp_printf(buffer, ", cacheable"); + if (mem->flags & IORESOURCE_MEM_RANGELENGTH) + pnp_printf(buffer, ", range-length"); + if (mem->flags & IORESOURCE_MEM_SHADOWABLE) + pnp_printf(buffer, ", shadowable"); + if (mem->flags & IORESOURCE_MEM_EXPANSIONROM) + pnp_printf(buffer, ", expansion ROM"); + switch (mem->flags & IORESOURCE_MEM_TYPE_MASK) { + case IORESOURCE_MEM_8BIT: + s = "8-bit"; + break; + case IORESOURCE_MEM_8AND16BIT: + s = "8-bit&16-bit"; + break; + case IORESOURCE_MEM_32BIT: + s = "32-bit"; + break; + default: + s = "16-bit"; + } + pnp_printf(buffer, ", %s\n", s); +} + +static void pnp_print_option(pnp_info_buffer_t * buffer, char *space, + struct pnp_option *option) +{ + switch (option->type) { + case IORESOURCE_IO: + pnp_print_port(buffer, space, &option->u.port); + break; + case IORESOURCE_MEM: + pnp_print_mem(buffer, space, &option->u.mem); + break; + case IORESOURCE_IRQ: + pnp_print_irq(buffer, space, &option->u.irq); + break; + case IORESOURCE_DMA: + pnp_print_dma(buffer, space, &option->u.dma); + break; + } +} + +static ssize_t options_show(struct device *dmdev, struct device_attribute *attr, + char *buf) +{ + struct pnp_dev *dev = to_pnp_dev(dmdev); + pnp_info_buffer_t *buffer; + struct pnp_option *option; + int ret, dep = 0, set = 0; + char *indent; + + buffer = kzalloc_obj(*buffer); + if (!buffer) + return -ENOMEM; + + buffer->len = PAGE_SIZE; + buffer->buffer = buf; + buffer->curr = buffer->buffer; + + list_for_each_entry(option, &dev->options, list) { + if (pnp_option_is_dependent(option)) { + indent = " "; + if (!dep || pnp_option_set(option) != set) { + set = pnp_option_set(option); + dep = 1; + pnp_printf(buffer, "Dependent: %02i - " + "Priority %s\n", set, + pnp_option_priority_name(option)); + } + } else { + dep = 0; + indent = ""; + } + pnp_print_option(buffer, indent, option); + } + + ret = (buffer->curr - buf); + kfree(buffer); + return ret; +} +static DEVICE_ATTR_RO(options); + +static ssize_t resources_show(struct device *dmdev, + struct device_attribute *attr, char *buf) +{ + struct pnp_dev *dev = to_pnp_dev(dmdev); + pnp_info_buffer_t *buffer; + struct pnp_resource *pnp_res; + struct resource *res; + int ret; + + if (!dev) + return -EINVAL; + + buffer = kzalloc_obj(*buffer); + if (!buffer) + return -ENOMEM; + + buffer->len = PAGE_SIZE; + buffer->buffer = buf; + buffer->curr = buffer->buffer; + + pnp_printf(buffer, "state = %s\n", dev->active ? "active" : "disabled"); + + list_for_each_entry(pnp_res, &dev->resources, list) { + res = &pnp_res->res; + + pnp_printf(buffer, pnp_resource_type_name(res)); + + if (res->flags & IORESOURCE_DISABLED) { + pnp_printf(buffer, " disabled\n"); + continue; + } + + switch (pnp_resource_type(res)) { + case IORESOURCE_IO: + case IORESOURCE_MEM: + case IORESOURCE_BUS: + pnp_printf(buffer, " %#llx-%#llx%s\n", + (unsigned long long) res->start, + (unsigned long long) res->end, + res->flags & IORESOURCE_WINDOW ? + " window" : ""); + break; + case IORESOURCE_IRQ: + case IORESOURCE_DMA: + pnp_printf(buffer, " %lld\n", + (unsigned long long) res->start); + break; + } + } + + ret = (buffer->curr - buf); + kfree(buffer); + return ret; +} + +static char *pnp_get_resource_value(char *buf, + unsigned long type, + resource_size_t *start, + resource_size_t *end, + unsigned long *flags) +{ + if (start) + *start = 0; + if (end) + *end = 0; + if (flags) + *flags = 0; + + /* TBD: allow for disabled resources */ + + buf = skip_spaces(buf); + if (start) { + *start = simple_strtoull(buf, &buf, 0); + if (end) { + buf = skip_spaces(buf); + if (*buf == '-') { + buf = skip_spaces(buf + 1); + *end = simple_strtoull(buf, &buf, 0); + } else + *end = *start; + } + } + + /* TBD: allow for additional flags, e.g., IORESOURCE_WINDOW */ + + return buf; +} + +static ssize_t resources_store(struct device *dmdev, + struct device_attribute *attr, const char *ubuf, + size_t count) +{ + struct pnp_dev *dev = to_pnp_dev(dmdev); + char *buf = (void *)ubuf; + int retval = 0; + + if (dev->status & PNP_ATTACHED) { + retval = -EBUSY; + dev_info(&dev->dev, "in use; can't configure\n"); + goto done; + } + + buf = skip_spaces(buf); + if (!strncasecmp(buf, "disable", 7)) { + retval = pnp_disable_dev(dev); + goto done; + } + if (!strncasecmp(buf, "activate", 8)) { + retval = pnp_activate_dev(dev); + goto done; + } + if (!strncasecmp(buf, "fill", 4)) { + if (dev->active) + goto done; + retval = pnp_auto_config_dev(dev); + goto done; + } + if (!strncasecmp(buf, "auto", 4)) { + if (dev->active) + goto done; + pnp_init_resources(dev); + retval = pnp_auto_config_dev(dev); + goto done; + } + if (!strncasecmp(buf, "clear", 5)) { + if (dev->active) + goto done; + pnp_init_resources(dev); + goto done; + } + if (!strncasecmp(buf, "get", 3)) { + mutex_lock(&pnp_res_mutex); + if (pnp_can_read(dev)) + dev->protocol->get(dev); + mutex_unlock(&pnp_res_mutex); + goto done; + } + if (!strncasecmp(buf, "set", 3)) { + resource_size_t start; + resource_size_t end; + unsigned long flags; + + if (dev->active) + goto done; + buf += 3; + pnp_init_resources(dev); + mutex_lock(&pnp_res_mutex); + while (1) { + buf = skip_spaces(buf); + if (!strncasecmp(buf, "io", 2)) { + buf = pnp_get_resource_value(buf + 2, + IORESOURCE_IO, + &start, &end, + &flags); + pnp_add_io_resource(dev, start, end, flags); + } else if (!strncasecmp(buf, "mem", 3)) { + buf = pnp_get_resource_value(buf + 3, + IORESOURCE_MEM, + &start, &end, + &flags); + pnp_add_mem_resource(dev, start, end, flags); + } else if (!strncasecmp(buf, "irq", 3)) { + buf = pnp_get_resource_value(buf + 3, + IORESOURCE_IRQ, + &start, NULL, + &flags); + pnp_add_irq_resource(dev, start, flags); + } else if (!strncasecmp(buf, "dma", 3)) { + buf = pnp_get_resource_value(buf + 3, + IORESOURCE_DMA, + &start, NULL, + &flags); + pnp_add_dma_resource(dev, start, flags); + } else if (!strncasecmp(buf, "bus", 3)) { + buf = pnp_get_resource_value(buf + 3, + IORESOURCE_BUS, + &start, &end, + NULL); + pnp_add_bus_resource(dev, start, end); + } else + break; + } + mutex_unlock(&pnp_res_mutex); + goto done; + } + +done: + if (retval < 0) + return retval; + return count; +} +static DEVICE_ATTR_RW(resources); + +static ssize_t id_show(struct device *dmdev, struct device_attribute *attr, + char *buf) +{ + char *str = buf; + struct pnp_dev *dev = to_pnp_dev(dmdev); + struct pnp_id *pos = dev->id; + + while (pos) { + str += sprintf(str, "%s\n", pos->id); + pos = pos->next; + } + return (str - buf); +} +static DEVICE_ATTR_RO(id); + +static struct attribute *pnp_dev_attrs[] = { + &dev_attr_resources.attr, + &dev_attr_options.attr, + &dev_attr_id.attr, + NULL, +}; + +static const struct attribute_group pnp_dev_group = { + .attrs = pnp_dev_attrs, +}; + +const struct attribute_group *pnp_dev_groups[] = { + &pnp_dev_group, + NULL, +}; diff --git a/drivers/pnp/isapnp/Kconfig b/drivers/pnp/isapnp/Kconfig new file mode 100644 index 000000000..8e5dec59e --- /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 || (HAS_IOPORT && COMPILE_TEST) + help + Say Y here if you would like support for ISA Plug and Play devices. + Some information is in . + + If unsure, say Y. diff --git a/drivers/pnp/isapnp/Makefile b/drivers/pnp/isapnp/Makefile new file mode 100644 index 000000000..a0e0c0aec --- /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 000000000..d60d9e377 --- /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 + */ + +#include +#include +#include + +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 000000000..219f96f2a --- /dev/null +++ b/drivers/pnp/isapnp/core.c @@ -0,0 +1,1071 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ISA Plug & Play support + * Copyright (c) by Jaroslav Kysela + * + * Changelog: + * 2000-01-01 Added quirks handling for buggy hardware + * Peter Denison + * 2000-06-14 Added isapnp_probe_devs() and isapnp_activate_dev() + * Christoph Hellwig + * 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 + * 2002-06-06 Made the use of dma channel 0 configurable + * Gerald Teschl + * 2002-10-06 Ported to PnP Layer - Adam Belay + * 2003-08-11 Resource Management Updates - Adam Belay + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 */ + 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_read_byte); +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, + str_plural(cards)); + 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 000000000..55ae72a28 --- /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 + */ + +#include +#include +#include +#include +#include + +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; +} diff --git a/drivers/pnp/manager.c b/drivers/pnp/manager.c new file mode 100644 index 000000000..1765d6e60 --- /dev/null +++ b/drivers/pnp/manager.c @@ -0,0 +1,429 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * manager.c - Resource Management, Conflict Resolution, Activation and Disabling of Devices + * + * based on isapnp.c resource management (c) Jaroslav Kysela + * Copyright 2003 Adam Belay + * Copyright (C) 2008 Hewlett-Packard Development Company, L.P. + * Bjorn Helgaas + */ + +#include +#include +#include +#include +#include +#include +#include +#include "base.h" + +DEFINE_MUTEX(pnp_res_mutex); + +static struct resource *pnp_find_resource(struct pnp_dev *dev, + unsigned char rule, + unsigned long type, + unsigned int bar) +{ + struct resource *res = pnp_get_resource(dev, type, bar); + + /* when the resource already exists, set its resource bits from rule */ + if (res) { + res->flags &= ~IORESOURCE_BITS; + res->flags |= rule & IORESOURCE_BITS; + } + + return res; +} + +static int pnp_assign_port(struct pnp_dev *dev, struct pnp_port *rule, int idx) +{ + struct resource *res, local_res; + + res = pnp_find_resource(dev, rule->flags, IORESOURCE_IO, idx); + if (res) { + pnp_dbg(&dev->dev, " io %d already set to %#llx-%#llx " + "flags %#lx\n", idx, (unsigned long long) res->start, + (unsigned long long) res->end, res->flags); + return 0; + } + + res = &local_res; + res->flags = rule->flags | IORESOURCE_AUTO; + res->start = 0; + res->end = 0; + + if (!rule->size) { + res->flags |= IORESOURCE_DISABLED; + pnp_dbg(&dev->dev, " io %d disabled\n", idx); + goto __add; + } + + res->start = rule->min; + res->end = res->start + rule->size - 1; + + while (!pnp_check_port(dev, res)) { + res->start += rule->align; + res->end = res->start + rule->size - 1; + if (res->start > rule->max || !rule->align) { + pnp_dbg(&dev->dev, " couldn't assign io %d " + "(min %#llx max %#llx)\n", idx, + (unsigned long long) rule->min, + (unsigned long long) rule->max); + return -EBUSY; + } + } + +__add: + pnp_add_io_resource(dev, res->start, res->end, res->flags); + return 0; +} + +static int pnp_assign_mem(struct pnp_dev *dev, struct pnp_mem *rule, int idx) +{ + struct resource *res, local_res; + + res = pnp_find_resource(dev, rule->flags, IORESOURCE_MEM, idx); + if (res) { + pnp_dbg(&dev->dev, " mem %d already set to %#llx-%#llx " + "flags %#lx\n", idx, (unsigned long long) res->start, + (unsigned long long) res->end, res->flags); + return 0; + } + + res = &local_res; + res->flags = rule->flags | IORESOURCE_AUTO; + res->start = 0; + res->end = 0; + + /* ??? rule->flags restricted to 8 bits, all tests bogus ??? */ + if (!(rule->flags & IORESOURCE_MEM_WRITEABLE)) + res->flags |= IORESOURCE_READONLY; + if (rule->flags & IORESOURCE_MEM_RANGELENGTH) + res->flags |= IORESOURCE_RANGELENGTH; + if (rule->flags & IORESOURCE_MEM_SHADOWABLE) + res->flags |= IORESOURCE_SHADOWABLE; + + if (!rule->size) { + res->flags |= IORESOURCE_DISABLED; + pnp_dbg(&dev->dev, " mem %d disabled\n", idx); + goto __add; + } + + res->start = rule->min; + res->end = res->start + rule->size - 1; + + while (!pnp_check_mem(dev, res)) { + res->start += rule->align; + res->end = res->start + rule->size - 1; + if (res->start > rule->max || !rule->align) { + pnp_dbg(&dev->dev, " couldn't assign mem %d " + "(min %#llx max %#llx)\n", idx, + (unsigned long long) rule->min, + (unsigned long long) rule->max); + return -EBUSY; + } + } + +__add: + pnp_add_mem_resource(dev, res->start, res->end, res->flags); + return 0; +} + +static int pnp_assign_irq(struct pnp_dev *dev, struct pnp_irq *rule, int idx) +{ + struct resource *res, local_res; + int i; + + /* IRQ priority: this table is good for i386 */ + static unsigned short xtab[16] = { + 5, 10, 11, 12, 9, 14, 15, 7, 3, 4, 13, 0, 1, 6, 8, 2 + }; + + res = pnp_find_resource(dev, rule->flags, IORESOURCE_IRQ, idx); + if (res) { + pnp_dbg(&dev->dev, " irq %d already set to %d flags %#lx\n", + idx, (int) res->start, res->flags); + return 0; + } + + res = &local_res; + res->flags = rule->flags | IORESOURCE_AUTO; + res->start = -1; + res->end = -1; + + if (bitmap_empty(rule->map.bits, PNP_IRQ_NR)) { + res->flags |= IORESOURCE_DISABLED; + pnp_dbg(&dev->dev, " irq %d disabled\n", idx); + goto __add; + } + + /* TBD: need check for >16 IRQ */ + res->start = find_next_bit(rule->map.bits, PNP_IRQ_NR, 16); + if (res->start < PNP_IRQ_NR) { + res->end = res->start; + goto __add; + } + for (i = 0; i < 16; i++) { + if (test_bit(xtab[i], rule->map.bits)) { + res->start = res->end = xtab[i]; + if (pnp_check_irq(dev, res)) + goto __add; + } + } + + if (rule->flags & IORESOURCE_IRQ_OPTIONAL) { + res->start = -1; + res->end = -1; + res->flags |= IORESOURCE_DISABLED; + pnp_dbg(&dev->dev, " irq %d disabled (optional)\n", idx); + goto __add; + } + + pnp_dbg(&dev->dev, " couldn't assign irq %d\n", idx); + return -EBUSY; + +__add: + pnp_add_irq_resource(dev, res->start, res->flags); + return 0; +} + +#ifdef CONFIG_ISA_DMA_API +static int pnp_assign_dma(struct pnp_dev *dev, struct pnp_dma *rule, int idx) +{ + struct resource *res, local_res; + int i; + + /* DMA priority: this table is good for i386 */ + static unsigned short xtab[8] = { + 1, 3, 5, 6, 7, 0, 2, 4 + }; + + res = pnp_find_resource(dev, rule->flags, IORESOURCE_DMA, idx); + if (res) { + pnp_dbg(&dev->dev, " dma %d already set to %d flags %#lx\n", + idx, (int) res->start, res->flags); + return 0; + } + + res = &local_res; + res->flags = rule->flags | IORESOURCE_AUTO; + res->start = -1; + res->end = -1; + + if (!rule->map) { + res->flags |= IORESOURCE_DISABLED; + pnp_dbg(&dev->dev, " dma %d disabled\n", idx); + goto __add; + } + + for (i = 0; i < 8; i++) { + if (rule->map & (1 << xtab[i])) { + res->start = res->end = xtab[i]; + if (pnp_check_dma(dev, res)) + goto __add; + } + } + + pnp_dbg(&dev->dev, " couldn't assign dma %d\n", idx); + return -EBUSY; + +__add: + pnp_add_dma_resource(dev, res->start, res->flags); + return 0; +} +#endif /* CONFIG_ISA_DMA_API */ + +void pnp_init_resources(struct pnp_dev *dev) +{ + pnp_free_resources(dev); +} + +static void pnp_clean_resource_table(struct pnp_dev *dev) +{ + struct pnp_resource *pnp_res, *tmp; + + list_for_each_entry_safe(pnp_res, tmp, &dev->resources, list) { + if (pnp_res->res.flags & IORESOURCE_AUTO) + pnp_free_resource(pnp_res); + } +} + +/** + * pnp_assign_resources - assigns resources to the device based on the specified dependent number + * @dev: pointer to the desired device + * @set: the dependent function number + */ +static int pnp_assign_resources(struct pnp_dev *dev, int set) +{ + struct pnp_option *option; + int nport = 0, nmem = 0, nirq = 0; + int ndma __maybe_unused = 0; + int ret = 0; + + pnp_dbg(&dev->dev, "pnp_assign_resources, try dependent set %d\n", set); + mutex_lock(&pnp_res_mutex); + pnp_clean_resource_table(dev); + + list_for_each_entry(option, &dev->options, list) { + if (pnp_option_is_dependent(option) && + pnp_option_set(option) != set) + continue; + + switch (option->type) { + case IORESOURCE_IO: + ret = pnp_assign_port(dev, &option->u.port, nport++); + break; + case IORESOURCE_MEM: + ret = pnp_assign_mem(dev, &option->u.mem, nmem++); + break; + case IORESOURCE_IRQ: + ret = pnp_assign_irq(dev, &option->u.irq, nirq++); + break; +#ifdef CONFIG_ISA_DMA_API + case IORESOURCE_DMA: + ret = pnp_assign_dma(dev, &option->u.dma, ndma++); + break; +#endif + default: + ret = -EINVAL; + break; + } + if (ret < 0) + break; + } + + mutex_unlock(&pnp_res_mutex); + if (ret < 0) { + pnp_dbg(&dev->dev, "pnp_assign_resources failed (%d)\n", ret); + pnp_clean_resource_table(dev); + } else + dbg_pnp_show_resources(dev, "pnp_assign_resources succeeded"); + return ret; +} + +/** + * pnp_auto_config_dev - automatically assigns resources to a device + * @dev: pointer to the desired device + */ +int pnp_auto_config_dev(struct pnp_dev *dev) +{ + int i, ret; + + if (!pnp_can_configure(dev)) { + pnp_dbg(&dev->dev, "configuration not supported\n"); + return -ENODEV; + } + + ret = pnp_assign_resources(dev, 0); + if (ret == 0) + return 0; + + for (i = 1; i < dev->num_dependent_sets; i++) { + ret = pnp_assign_resources(dev, i); + if (ret == 0) + return 0; + } + + dev_err(&dev->dev, "unable to assign resources\n"); + return ret; +} + +/** + * pnp_start_dev - low-level start of the PnP device + * @dev: pointer to the desired device + * + * assumes that resources have already been allocated + */ +int pnp_start_dev(struct pnp_dev *dev) +{ + if (!pnp_can_write(dev)) { + pnp_dbg(&dev->dev, "activation not supported\n"); + return -EINVAL; + } + + dbg_pnp_show_resources(dev, "pnp_start_dev"); + if (dev->protocol->set(dev) < 0) { + dev_err(&dev->dev, "activation failed\n"); + return -EIO; + } + + dev_info(&dev->dev, "activated\n"); + return 0; +} +EXPORT_SYMBOL(pnp_start_dev); + +/** + * pnp_stop_dev - low-level disable of the PnP device + * @dev: pointer to the desired device + * + * does not free resources + */ +int pnp_stop_dev(struct pnp_dev *dev) +{ + if (!pnp_can_disable(dev)) { + pnp_dbg(&dev->dev, "disabling not supported\n"); + return -EINVAL; + } + if (dev->protocol->disable(dev) < 0) { + dev_err(&dev->dev, "disable failed\n"); + return -EIO; + } + + dev_info(&dev->dev, "disabled\n"); + return 0; +} +EXPORT_SYMBOL(pnp_stop_dev); + +/** + * pnp_activate_dev - activates a PnP device for use + * @dev: pointer to the desired device + * + * does not validate or set resources so be careful. + */ +int pnp_activate_dev(struct pnp_dev *dev) +{ + int error; + + if (dev->active) + return 0; + + /* ensure resources are allocated */ + if (pnp_auto_config_dev(dev)) + return -EBUSY; + + error = pnp_start_dev(dev); + if (error) + return error; + + dev->active = 1; + return 0; +} +EXPORT_SYMBOL(pnp_activate_dev); + +/** + * pnp_disable_dev - disables device + * @dev: pointer to the desired device + * + * inform the correct pnp protocol so that resources can be used by other devices + */ +int pnp_disable_dev(struct pnp_dev *dev) +{ + int error; + + if (!dev->active) + return 0; + + error = pnp_stop_dev(dev); + if (error) + return error; + + dev->active = 0; + + /* release the resources so that other devices can use them */ + mutex_lock(&pnp_res_mutex); + pnp_clean_resource_table(dev); + mutex_unlock(&pnp_res_mutex); + + return 0; +} +EXPORT_SYMBOL(pnp_disable_dev); diff --git a/drivers/pnp/pnpacpi/Kconfig b/drivers/pnp/pnpacpi/Kconfig new file mode 100644 index 000000000..70f733f63 --- /dev/null +++ b/drivers/pnp/pnpacpi/Kconfig @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Plug and Play ACPI configuration +# +config PNPACPI + bool + default (PNP && ACPI) diff --git a/drivers/pnp/pnpacpi/Makefile b/drivers/pnp/pnpacpi/Makefile new file mode 100644 index 000000000..fb1c7154c --- /dev/null +++ b/drivers/pnp/pnpacpi/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for the kernel PNPACPI driver. +# +obj-y += pnp.o + +pnp-y := core.o rsparser.o diff --git a/drivers/pnp/pnpacpi/core.c b/drivers/pnp/pnpacpi/core.c new file mode 100644 index 000000000..d8ec0ad2a --- /dev/null +++ b/drivers/pnp/pnpacpi/core.c @@ -0,0 +1,321 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * pnpacpi -- PnP ACPI driver + * + * Copyright (c) 2004 Matthieu Castet + * Copyright (c) 2004 Li Shaohua + */ + +#include +#include +#include +#include + +#include "../base.h" +#include "pnpacpi.h" + +static int num; + +/* + * Compatible Device IDs + */ +#define TEST_HEX(c) \ + if (!(('0' <= (c) && (c) <= '9') || ('A' <= (c) && (c) <= 'F'))) \ + return 0 +#define TEST_ALPHA(c) \ + if (!('A' <= (c) && (c) <= 'Z')) \ + return 0 +static int __init ispnpidacpi(const char *id) +{ + TEST_ALPHA(id[0]); + TEST_ALPHA(id[1]); + TEST_ALPHA(id[2]); + TEST_HEX(id[3]); + TEST_HEX(id[4]); + TEST_HEX(id[5]); + TEST_HEX(id[6]); + if (id[7] != '\0') + return 0; + return 1; +} + +static int pnpacpi_get_resources(struct pnp_dev *dev) +{ + pnp_dbg(&dev->dev, "get resources\n"); + return pnpacpi_parse_allocated_resource(dev); +} + +static int pnpacpi_set_resources(struct pnp_dev *dev) +{ + struct acpi_device *acpi_dev; + acpi_handle handle; + int ret = 0; + + pnp_dbg(&dev->dev, "set resources\n"); + + acpi_dev = ACPI_COMPANION(&dev->dev); + if (!acpi_dev) { + dev_dbg(&dev->dev, "ACPI device not found in %s!\n", __func__); + return -ENODEV; + } + + if (WARN_ON_ONCE(acpi_dev != dev->data)) + dev->data = acpi_dev; + + handle = acpi_dev->handle; + if (acpi_has_method(handle, METHOD_NAME__SRS)) { + struct acpi_buffer buffer; + + ret = pnpacpi_build_resource_template(dev, &buffer); + if (ret) + return ret; + + ret = pnpacpi_encode_resources(dev, &buffer); + if (!ret) { + acpi_status status; + + status = acpi_set_current_resources(handle, &buffer); + if (ACPI_FAILURE(status)) + ret = -EIO; + } + kfree(buffer.pointer); + } + if (!ret && acpi_device_power_manageable(acpi_dev)) + ret = acpi_device_set_power(acpi_dev, ACPI_STATE_D0); + + return ret; +} + +static int pnpacpi_disable_resources(struct pnp_dev *dev) +{ + struct acpi_device *acpi_dev; + acpi_status status; + + dev_dbg(&dev->dev, "disable resources\n"); + + acpi_dev = ACPI_COMPANION(&dev->dev); + if (!acpi_dev) { + dev_dbg(&dev->dev, "ACPI device not found in %s!\n", __func__); + return 0; + } + + /* acpi_unregister_gsi(pnp_irq(dev, 0)); */ + if (acpi_device_power_manageable(acpi_dev)) + acpi_device_set_power(acpi_dev, ACPI_STATE_D3_COLD); + + /* continue even if acpi_device_set_power() fails */ + status = acpi_evaluate_object(acpi_dev->handle, "_DIS", NULL, NULL); + if (ACPI_FAILURE(status) && status != AE_NOT_FOUND) + return -ENODEV; + + return 0; +} + +#ifdef CONFIG_ACPI_SLEEP +static bool pnpacpi_can_wakeup(struct pnp_dev *dev) +{ + struct acpi_device *acpi_dev = ACPI_COMPANION(&dev->dev); + + if (!acpi_dev) { + dev_dbg(&dev->dev, "ACPI device not found in %s!\n", __func__); + return false; + } + + return acpi_bus_can_wakeup(acpi_dev->handle); +} + +static int pnpacpi_suspend(struct pnp_dev *dev, pm_message_t state) +{ + struct acpi_device *acpi_dev = ACPI_COMPANION(&dev->dev); + int error = 0; + + if (!acpi_dev) { + dev_dbg(&dev->dev, "ACPI device not found in %s!\n", __func__); + return 0; + } + + if (device_can_wakeup(&dev->dev)) { + error = acpi_pm_set_device_wakeup(&dev->dev, + device_may_wakeup(&dev->dev)); + if (error) + return error; + } + + if (acpi_device_power_manageable(acpi_dev)) { + int power_state = acpi_pm_device_sleep_state(&dev->dev, NULL, + ACPI_STATE_D3_COLD); + if (power_state < 0) + power_state = (state.event == PM_EVENT_ON) ? + ACPI_STATE_D0 : ACPI_STATE_D3_COLD; + + /* + * acpi_device_set_power() can fail (keyboard port can't be + * powered-down?), and in any case, our return value is ignored + * by pnp_bus_suspend(). Hence we don't revert the wakeup + * setting if the set_power fails. + */ + error = acpi_device_set_power(acpi_dev, power_state); + } + + return error; +} + +static int pnpacpi_resume(struct pnp_dev *dev) +{ + struct acpi_device *acpi_dev = ACPI_COMPANION(&dev->dev); + int error = 0; + + if (!acpi_dev) { + dev_dbg(&dev->dev, "ACPI device not found in %s!\n", __func__); + return -ENODEV; + } + + if (device_may_wakeup(&dev->dev)) + acpi_pm_set_device_wakeup(&dev->dev, false); + + if (acpi_device_power_manageable(acpi_dev)) + error = acpi_device_set_power(acpi_dev, ACPI_STATE_D0); + + return error; +} +#endif + +struct pnp_protocol pnpacpi_protocol = { + .name = "Plug and Play ACPI", + .get = pnpacpi_get_resources, + .set = pnpacpi_set_resources, + .disable = pnpacpi_disable_resources, +#ifdef CONFIG_ACPI_SLEEP + .can_wakeup = pnpacpi_can_wakeup, + .suspend = pnpacpi_suspend, + .resume = pnpacpi_resume, +#endif +}; +EXPORT_SYMBOL(pnpacpi_protocol); + +static const char *__init pnpacpi_get_id(struct acpi_device *device) +{ + struct acpi_hardware_id *id; + + list_for_each_entry(id, &device->pnp.ids, list) { + if (ispnpidacpi(id->id)) + return id->id; + } + + return NULL; +} + +static int __init pnpacpi_add_device(struct acpi_device *device) +{ + struct pnp_dev *dev; + const char *pnpid; + struct acpi_hardware_id *id; + int error; + + /* Skip devices that are already bound */ + if (device->physical_node_count) + return 0; + + /* + * If a PnPacpi device is not present , the device + * driver should not be loaded. + */ + if (!acpi_has_method(device->handle, "_CRS")) + return 0; + + pnpid = pnpacpi_get_id(device); + if (!pnpid) + return 0; + + if (!device->status.present) + return 0; + + dev = pnp_alloc_dev(&pnpacpi_protocol, num, pnpid); + if (!dev) + return -ENOMEM; + + ACPI_COMPANION_SET(&dev->dev, device); + dev->data = device; + /* .enabled means the device can decode the resources */ + dev->active = device->status.enabled; + if (acpi_has_method(device->handle, "_SRS")) + dev->capabilities |= PNP_CONFIGURABLE; + dev->capabilities |= PNP_READ; + if (device->flags.dynamic_status && (dev->capabilities & PNP_CONFIGURABLE)) + dev->capabilities |= PNP_WRITE; + if (device->flags.removable) + dev->capabilities |= PNP_REMOVABLE; + if (acpi_has_method(device->handle, "_DIS")) + dev->capabilities |= PNP_DISABLE; + + strscpy(dev->name, acpi_device_bid(device), sizeof(dev->name)); + + if (dev->active) + pnpacpi_parse_allocated_resource(dev); + + if (dev->capabilities & PNP_CONFIGURABLE) + pnpacpi_parse_resource_option_data(dev); + + list_for_each_entry(id, &device->pnp.ids, list) { + if (!strcmp(id->id, pnpid)) + continue; + if (!ispnpidacpi(id->id)) + continue; + pnp_add_id(dev, id->id); + } + + /* 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; + } + + num++; + + return 0; +} + +static acpi_status __init pnpacpi_add_device_handler(acpi_handle handle, + u32 lvl, void *context, + void **rv) +{ + struct acpi_device *device = acpi_fetch_acpi_dev(handle); + + if (!device) + return AE_CTRL_DEPTH; + if (acpi_is_pnp_device(device)) + pnpacpi_add_device(device); + return AE_OK; +} + +int pnpacpi_disabled __initdata; +static int __init pnpacpi_init(void) +{ + if (acpi_disabled || pnpacpi_disabled) { + printk(KERN_INFO "pnp: PnP ACPI: disabled\n"); + return 0; + } + printk(KERN_INFO "pnp: PnP ACPI init\n"); + pnp_register_protocol(&pnpacpi_protocol); + acpi_get_devices(NULL, pnpacpi_add_device_handler, NULL, NULL); + printk(KERN_INFO "pnp: PnP ACPI: found %d devices\n", num); + pnp_platform_devices = 1; + return 0; +} + +fs_initcall(pnpacpi_init); + +static int __init pnpacpi_setup(char *str) +{ + if (str == NULL) + return 1; + if (!strncmp(str, "off", 3)) + pnpacpi_disabled = 1; + return 1; +} + +__setup("pnpacpi=", pnpacpi_setup); diff --git a/drivers/pnp/pnpacpi/pnpacpi.h b/drivers/pnp/pnpacpi/pnpacpi.h new file mode 100644 index 000000000..4489cd6db --- /dev/null +++ b/drivers/pnp/pnpacpi/pnpacpi.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef ACPI_PNP_H +#define ACPI_PNP_H + +#include +#include + +int pnpacpi_parse_allocated_resource(struct pnp_dev *); +int pnpacpi_parse_resource_option_data(struct pnp_dev *); +int pnpacpi_encode_resources(struct pnp_dev *, struct acpi_buffer *); +int pnpacpi_build_resource_template(struct pnp_dev *, struct acpi_buffer *); +#endif diff --git a/drivers/pnp/pnpacpi/rsparser.c b/drivers/pnp/pnpacpi/rsparser.c new file mode 100644 index 000000000..c02ce0834 --- /dev/null +++ b/drivers/pnp/pnpacpi/rsparser.c @@ -0,0 +1,948 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * pnpacpi -- PnP ACPI driver + * + * Copyright (c) 2004 Matthieu Castet + * Copyright (c) 2004 Li Shaohua + * Copyright (C) 2008 Hewlett-Packard Development Company, L.P. + * Bjorn Helgaas + */ +#include +#include +#include +#include +#include +#include "../base.h" +#include "pnpacpi.h" + +static void decode_irq_flags(struct pnp_dev *dev, int flags, u8 *triggering, + u8 *polarity, u8 *shareable) +{ + switch (flags & (IORESOURCE_IRQ_LOWLEVEL | IORESOURCE_IRQ_HIGHLEVEL | + IORESOURCE_IRQ_LOWEDGE | IORESOURCE_IRQ_HIGHEDGE)) { + case IORESOURCE_IRQ_LOWLEVEL: + *triggering = ACPI_LEVEL_SENSITIVE; + *polarity = ACPI_ACTIVE_LOW; + break; + case IORESOURCE_IRQ_HIGHLEVEL: + *triggering = ACPI_LEVEL_SENSITIVE; + *polarity = ACPI_ACTIVE_HIGH; + break; + case IORESOURCE_IRQ_LOWEDGE: + *triggering = ACPI_EDGE_SENSITIVE; + *polarity = ACPI_ACTIVE_LOW; + break; + case IORESOURCE_IRQ_HIGHEDGE: + *triggering = ACPI_EDGE_SENSITIVE; + *polarity = ACPI_ACTIVE_HIGH; + break; + default: + dev_err(&dev->dev, "can't encode invalid IRQ mode %#x\n", + flags); + *triggering = ACPI_EDGE_SENSITIVE; + *polarity = ACPI_ACTIVE_HIGH; + break; + } + + if (flags & IORESOURCE_IRQ_SHAREABLE) + *shareable = ACPI_SHARED; + else + *shareable = ACPI_EXCLUSIVE; +} + +static int dma_flags(struct pnp_dev *dev, int type, int bus_master, + int transfer) +{ + int flags = 0; + + if (bus_master) + flags |= IORESOURCE_DMA_MASTER; + switch (type) { + case ACPI_COMPATIBILITY: + flags |= IORESOURCE_DMA_COMPATIBLE; + break; + case ACPI_TYPE_A: + flags |= IORESOURCE_DMA_TYPEA; + break; + case ACPI_TYPE_B: + flags |= IORESOURCE_DMA_TYPEB; + break; + case ACPI_TYPE_F: + flags |= IORESOURCE_DMA_TYPEF; + break; + default: + /* Set a default value ? */ + flags |= IORESOURCE_DMA_COMPATIBLE; + dev_err(&dev->dev, "invalid DMA type %d\n", type); + } + switch (transfer) { + case ACPI_TRANSFER_8: + flags |= IORESOURCE_DMA_8BIT; + break; + case ACPI_TRANSFER_8_16: + flags |= IORESOURCE_DMA_8AND16BIT; + break; + case ACPI_TRANSFER_16: + flags |= IORESOURCE_DMA_16BIT; + break; + default: + /* Set a default value ? */ + flags |= IORESOURCE_DMA_8AND16BIT; + dev_err(&dev->dev, "invalid DMA transfer type %d\n", transfer); + } + + return flags; +} + +/* + * Allocated Resources + */ + +static void pnpacpi_add_irqresource(struct pnp_dev *dev, struct resource *r) +{ + if (!(r->flags & IORESOURCE_DISABLED)) + pcibios_penalize_isa_irq(r->start, 1); + + pnp_add_resource(dev, r); +} + +/* + * Device CSRs that do not appear in PCI config space should be described + * via ACPI. This would normally be done with Address Space Descriptors + * marked as "consumer-only," but old versions of Windows and Linux ignore + * the producer/consumer flag, so HP invented a vendor-defined resource to + * describe the location and size of CSR space. + */ +static struct acpi_vendor_uuid hp_ccsr_uuid = { + .subtype = 2, + .data = { 0xf9, 0xad, 0xe9, 0x69, 0x4f, 0x92, 0x5f, 0xab, 0xf6, 0x4a, + 0x24, 0xd2, 0x01, 0x37, 0x0e, 0xad }, +}; + +static int vendor_resource_matches(struct pnp_dev *dev, + struct acpi_resource_vendor_typed *vendor, + struct acpi_vendor_uuid *match, + int expected_len) +{ + int uuid_len = sizeof(vendor->uuid); + u8 uuid_subtype = vendor->uuid_subtype; + u8 *uuid = vendor->uuid; + int actual_len; + + /* byte_length includes uuid_subtype and uuid */ + actual_len = vendor->byte_length - uuid_len - 1; + + if (uuid_subtype == match->subtype && + uuid_len == sizeof(match->data) && + memcmp(uuid, match->data, uuid_len) == 0) { + if (expected_len && expected_len != actual_len) { + dev_err(&dev->dev, + "wrong vendor descriptor size; expected %d, found %d bytes\n", + expected_len, actual_len); + return 0; + } + + return 1; + } + + return 0; +} + +static void pnpacpi_parse_allocated_vendor(struct pnp_dev *dev, + struct acpi_resource_vendor_typed *vendor) +{ + struct { u64 start, length; } range; + + if (vendor_resource_matches(dev, vendor, &hp_ccsr_uuid, + sizeof(range))) { + memcpy(&range, vendor->byte_data, sizeof(range)); + pnp_add_mem_resource(dev, range.start, range.start + + range.length - 1, 0); + } +} + +static acpi_status pnpacpi_allocated_resource(struct acpi_resource *res, + void *data) +{ + struct pnp_dev *dev = data; + struct acpi_resource_dma *dma; + struct acpi_resource_vendor_typed *vendor_typed; + struct acpi_resource_gpio *gpio; + struct resource_win win = {{0}, 0}; + struct resource *r = &win.res; + int i, flags; + + if (acpi_dev_resource_address_space(res, &win) + || acpi_dev_resource_ext_address_space(res, &win)) { + pnp_add_resource(dev, &win.res); + return AE_OK; + } + + r->flags = 0; + if (acpi_dev_resource_interrupt(res, 0, r)) { + pnpacpi_add_irqresource(dev, r); + for (i = 1; acpi_dev_resource_interrupt(res, i, r); i++) + pnpacpi_add_irqresource(dev, r); + + if (i > 1) { + /* + * The IRQ encoder puts a single interrupt in each + * descriptor, so if a _CRS descriptor has more than + * one interrupt, we won't be able to re-encode it. + */ + if (pnp_can_write(dev)) { + dev_warn(&dev->dev, + "multiple interrupts in _CRS descriptor; configuration can't be changed\n"); + dev->capabilities &= ~PNP_WRITE; + } + } + return AE_OK; + } else if (acpi_gpio_get_irq_resource(res, &gpio)) { + /* + * If the resource is GpioInt() type then extract the IRQ + * from GPIO resource and fill it into IRQ resource type. + */ + i = acpi_dev_gpio_irq_get(dev->data, 0); + if (i >= 0) { + flags = acpi_dev_irq_flags(gpio->triggering, + gpio->polarity, + gpio->shareable, + gpio->wake_capable); + } else { + flags = IORESOURCE_DISABLED; + } + pnp_add_irq_resource(dev, i, flags); + return AE_OK; + } else if (r->flags & IORESOURCE_DISABLED) { + pnp_add_irq_resource(dev, 0, IORESOURCE_DISABLED); + return AE_OK; + } + + switch (res->type) { + case ACPI_RESOURCE_TYPE_MEMORY24: + case ACPI_RESOURCE_TYPE_MEMORY32: + case ACPI_RESOURCE_TYPE_FIXED_MEMORY32: + if (acpi_dev_resource_memory(res, r)) + pnp_add_resource(dev, r); + break; + case ACPI_RESOURCE_TYPE_IO: + case ACPI_RESOURCE_TYPE_FIXED_IO: + if (acpi_dev_resource_io(res, r)) + pnp_add_resource(dev, r); + break; + case ACPI_RESOURCE_TYPE_DMA: + dma = &res->data.dma; + if (dma->channel_count > 0 && dma->channels[0] != (u8) -1) + flags = dma_flags(dev, dma->type, dma->bus_master, + dma->transfer); + else + flags = IORESOURCE_DISABLED; + pnp_add_dma_resource(dev, dma->channels[0], flags); + break; + + case ACPI_RESOURCE_TYPE_START_DEPENDENT: + case ACPI_RESOURCE_TYPE_END_DEPENDENT: + break; + + case ACPI_RESOURCE_TYPE_VENDOR: + vendor_typed = &res->data.vendor_typed; + pnpacpi_parse_allocated_vendor(dev, vendor_typed); + break; + + case ACPI_RESOURCE_TYPE_END_TAG: + break; + + case ACPI_RESOURCE_TYPE_GENERIC_REGISTER: + break; + + case ACPI_RESOURCE_TYPE_SERIAL_BUS: + /* serial bus connections (I2C/SPI/UART) are not pnp */ + break; + + default: + dev_warn(&dev->dev, "unknown resource type %d in _CRS\n", + res->type); + return AE_ERROR; + } + + return AE_OK; +} + +int pnpacpi_parse_allocated_resource(struct pnp_dev *dev) +{ + struct acpi_device *acpi_dev = dev->data; + acpi_handle handle = acpi_dev->handle; + acpi_status status; + + pnp_dbg(&dev->dev, "parse allocated resources\n"); + + pnp_init_resources(dev); + + status = acpi_walk_resources(handle, METHOD_NAME__CRS, + pnpacpi_allocated_resource, dev); + + if (ACPI_FAILURE(status)) { + if (status != AE_NOT_FOUND) + dev_err(&dev->dev, "can't evaluate _CRS: %d", status); + return -EPERM; + } + return 0; +} + +static __init void pnpacpi_parse_dma_option(struct pnp_dev *dev, + unsigned int option_flags, + struct acpi_resource_dma *p) +{ + int i; + unsigned char map = 0, flags; + + for (i = 0; i < p->channel_count; i++) + map |= 1 << p->channels[i]; + + flags = dma_flags(dev, p->type, p->bus_master, p->transfer); + pnp_register_dma_resource(dev, option_flags, map, flags); +} + +static __init void pnpacpi_parse_irq_option(struct pnp_dev *dev, + unsigned int option_flags, + struct acpi_resource_irq *p) +{ + int i; + pnp_irq_mask_t map; + unsigned char flags; + + bitmap_zero(map.bits, PNP_IRQ_NR); + for (i = 0; i < p->interrupt_count; i++) + if (p->interrupts[i]) + __set_bit(p->interrupts[i], map.bits); + + flags = acpi_dev_irq_flags(p->triggering, p->polarity, p->shareable, p->wake_capable); + pnp_register_irq_resource(dev, option_flags, &map, flags); +} + +static __init void pnpacpi_parse_ext_irq_option(struct pnp_dev *dev, + unsigned int option_flags, + struct acpi_resource_extended_irq *p) +{ + int i; + pnp_irq_mask_t map; + unsigned char flags; + + bitmap_zero(map.bits, PNP_IRQ_NR); + for (i = 0; i < p->interrupt_count; i++) { + if (p->interrupts[i]) { + if (p->interrupts[i] < PNP_IRQ_NR) + __set_bit(p->interrupts[i], map.bits); + else + dev_err(&dev->dev, + "ignoring IRQ %d option (too large for %d entry bitmap)\n", + p->interrupts[i], PNP_IRQ_NR); + } + } + + flags = acpi_dev_irq_flags(p->triggering, p->polarity, p->shareable, p->wake_capable); + pnp_register_irq_resource(dev, option_flags, &map, flags); +} + +static __init void pnpacpi_parse_port_option(struct pnp_dev *dev, + unsigned int option_flags, + struct acpi_resource_io *io) +{ + unsigned char flags = 0; + + if (io->io_decode == ACPI_DECODE_16) + flags = IORESOURCE_IO_16BIT_ADDR; + pnp_register_port_resource(dev, option_flags, io->minimum, io->maximum, + io->alignment, io->address_length, flags); +} + +static __init void pnpacpi_parse_fixed_port_option(struct pnp_dev *dev, + unsigned int option_flags, + struct acpi_resource_fixed_io *io) +{ + pnp_register_port_resource(dev, option_flags, io->address, io->address, + 0, io->address_length, IORESOURCE_IO_FIXED); +} + +static __init void pnpacpi_parse_mem24_option(struct pnp_dev *dev, + unsigned int option_flags, + struct acpi_resource_memory24 *p) +{ + unsigned char flags = 0; + + if (p->write_protect == ACPI_READ_WRITE_MEMORY) + flags = IORESOURCE_MEM_WRITEABLE; + pnp_register_mem_resource(dev, option_flags, p->minimum, p->maximum, + p->alignment, p->address_length, flags); +} + +static __init void pnpacpi_parse_mem32_option(struct pnp_dev *dev, + unsigned int option_flags, + struct acpi_resource_memory32 *p) +{ + unsigned char flags = 0; + + if (p->write_protect == ACPI_READ_WRITE_MEMORY) + flags = IORESOURCE_MEM_WRITEABLE; + pnp_register_mem_resource(dev, option_flags, p->minimum, p->maximum, + p->alignment, p->address_length, flags); +} + +static __init void pnpacpi_parse_fixed_mem32_option(struct pnp_dev *dev, + unsigned int option_flags, + struct acpi_resource_fixed_memory32 *p) +{ + unsigned char flags = 0; + + if (p->write_protect == ACPI_READ_WRITE_MEMORY) + flags = IORESOURCE_MEM_WRITEABLE; + pnp_register_mem_resource(dev, option_flags, p->address, p->address, + 0, p->address_length, flags); +} + +static __init void pnpacpi_parse_address_option(struct pnp_dev *dev, + unsigned int option_flags, + struct acpi_resource *r) +{ + struct acpi_resource_address64 addr, *p = &addr; + acpi_status status; + unsigned char flags = 0; + + status = acpi_resource_to_address64(r, p); + if (ACPI_FAILURE(status)) { + dev_warn(&dev->dev, "can't convert resource type %d\n", + r->type); + return; + } + + if (p->resource_type == ACPI_MEMORY_RANGE) { + if (p->info.mem.write_protect == ACPI_READ_WRITE_MEMORY) + flags = IORESOURCE_MEM_WRITEABLE; + pnp_register_mem_resource(dev, option_flags, p->address.minimum, + p->address.minimum, 0, p->address.address_length, + flags); + } else if (p->resource_type == ACPI_IO_RANGE) + pnp_register_port_resource(dev, option_flags, p->address.minimum, + p->address.minimum, 0, p->address.address_length, + IORESOURCE_IO_FIXED); +} + +static __init void pnpacpi_parse_ext_address_option(struct pnp_dev *dev, + unsigned int option_flags, + struct acpi_resource *r) +{ + struct acpi_resource_extended_address64 *p = &r->data.ext_address64; + unsigned char flags = 0; + + if (p->resource_type == ACPI_MEMORY_RANGE) { + if (p->info.mem.write_protect == ACPI_READ_WRITE_MEMORY) + flags = IORESOURCE_MEM_WRITEABLE; + pnp_register_mem_resource(dev, option_flags, p->address.minimum, + p->address.minimum, 0, p->address.address_length, + flags); + } else if (p->resource_type == ACPI_IO_RANGE) + pnp_register_port_resource(dev, option_flags, p->address.minimum, + p->address.minimum, 0, p->address.address_length, + IORESOURCE_IO_FIXED); +} + +struct acpipnp_parse_option_s { + struct pnp_dev *dev; + unsigned int option_flags; +}; + +static __init acpi_status pnpacpi_option_resource(struct acpi_resource *res, + void *data) +{ + int priority; + struct acpipnp_parse_option_s *parse_data = data; + struct pnp_dev *dev = parse_data->dev; + unsigned int option_flags = parse_data->option_flags; + + switch (res->type) { + case ACPI_RESOURCE_TYPE_IRQ: + pnpacpi_parse_irq_option(dev, option_flags, &res->data.irq); + break; + + case ACPI_RESOURCE_TYPE_DMA: + pnpacpi_parse_dma_option(dev, option_flags, &res->data.dma); + break; + + case ACPI_RESOURCE_TYPE_START_DEPENDENT: + switch (res->data.start_dpf.compatibility_priority) { + case ACPI_GOOD_CONFIGURATION: + priority = PNP_RES_PRIORITY_PREFERRED; + break; + + case ACPI_ACCEPTABLE_CONFIGURATION: + priority = PNP_RES_PRIORITY_ACCEPTABLE; + break; + + case ACPI_SUB_OPTIMAL_CONFIGURATION: + priority = PNP_RES_PRIORITY_FUNCTIONAL; + break; + default: + priority = PNP_RES_PRIORITY_INVALID; + break; + } + parse_data->option_flags = pnp_new_dependent_set(dev, priority); + break; + + case ACPI_RESOURCE_TYPE_END_DEPENDENT: + parse_data->option_flags = 0; + break; + + case ACPI_RESOURCE_TYPE_IO: + pnpacpi_parse_port_option(dev, option_flags, &res->data.io); + break; + + case ACPI_RESOURCE_TYPE_FIXED_IO: + pnpacpi_parse_fixed_port_option(dev, option_flags, + &res->data.fixed_io); + break; + + case ACPI_RESOURCE_TYPE_VENDOR: + case ACPI_RESOURCE_TYPE_END_TAG: + break; + + case ACPI_RESOURCE_TYPE_MEMORY24: + pnpacpi_parse_mem24_option(dev, option_flags, + &res->data.memory24); + break; + + case ACPI_RESOURCE_TYPE_MEMORY32: + pnpacpi_parse_mem32_option(dev, option_flags, + &res->data.memory32); + break; + + case ACPI_RESOURCE_TYPE_FIXED_MEMORY32: + pnpacpi_parse_fixed_mem32_option(dev, option_flags, + &res->data.fixed_memory32); + break; + + case ACPI_RESOURCE_TYPE_ADDRESS16: + case ACPI_RESOURCE_TYPE_ADDRESS32: + case ACPI_RESOURCE_TYPE_ADDRESS64: + pnpacpi_parse_address_option(dev, option_flags, res); + break; + + case ACPI_RESOURCE_TYPE_EXTENDED_ADDRESS64: + pnpacpi_parse_ext_address_option(dev, option_flags, res); + break; + + case ACPI_RESOURCE_TYPE_EXTENDED_IRQ: + pnpacpi_parse_ext_irq_option(dev, option_flags, + &res->data.extended_irq); + break; + + case ACPI_RESOURCE_TYPE_GENERIC_REGISTER: + break; + + default: + dev_warn(&dev->dev, "unknown resource type %d in _PRS\n", + res->type); + return AE_ERROR; + } + + return AE_OK; +} + +int __init pnpacpi_parse_resource_option_data(struct pnp_dev *dev) +{ + struct acpi_device *acpi_dev = dev->data; + acpi_handle handle = acpi_dev->handle; + acpi_status status; + struct acpipnp_parse_option_s parse_data; + + pnp_dbg(&dev->dev, "parse resource options\n"); + + parse_data.dev = dev; + parse_data.option_flags = 0; + + status = acpi_walk_resources(handle, METHOD_NAME__PRS, + pnpacpi_option_resource, &parse_data); + + if (ACPI_FAILURE(status)) { + if (status != AE_NOT_FOUND) + dev_err(&dev->dev, "can't evaluate _PRS: %d", status); + return -EPERM; + } + return 0; +} + +static int pnpacpi_supported_resource(struct acpi_resource *res) +{ + switch (res->type) { + case ACPI_RESOURCE_TYPE_IRQ: + case ACPI_RESOURCE_TYPE_DMA: + case ACPI_RESOURCE_TYPE_IO: + case ACPI_RESOURCE_TYPE_FIXED_IO: + case ACPI_RESOURCE_TYPE_MEMORY24: + case ACPI_RESOURCE_TYPE_MEMORY32: + case ACPI_RESOURCE_TYPE_FIXED_MEMORY32: + case ACPI_RESOURCE_TYPE_ADDRESS16: + case ACPI_RESOURCE_TYPE_ADDRESS32: + case ACPI_RESOURCE_TYPE_ADDRESS64: + case ACPI_RESOURCE_TYPE_EXTENDED_ADDRESS64: + case ACPI_RESOURCE_TYPE_EXTENDED_IRQ: + return 1; + } + return 0; +} + +/* + * Set resource + */ +static acpi_status pnpacpi_count_resources(struct acpi_resource *res, + void *data) +{ + int *res_cnt = data; + + if (pnpacpi_supported_resource(res)) + (*res_cnt)++; + return AE_OK; +} + +static acpi_status pnpacpi_type_resources(struct acpi_resource *res, void *data) +{ + struct acpi_resource **resource = data; + + if (pnpacpi_supported_resource(res)) { + (*resource)->type = res->type; + (*resource)->length = sizeof(struct acpi_resource); + if (res->type == ACPI_RESOURCE_TYPE_IRQ) + (*resource)->data.irq.descriptor_length = + res->data.irq.descriptor_length; + (*resource)++; + } + + return AE_OK; +} + +int pnpacpi_build_resource_template(struct pnp_dev *dev, + struct acpi_buffer *buffer) +{ + struct acpi_device *acpi_dev = dev->data; + acpi_handle handle = acpi_dev->handle; + struct acpi_resource *resource; + int res_cnt = 0; + acpi_status status; + + status = acpi_walk_resources(handle, METHOD_NAME__CRS, + pnpacpi_count_resources, &res_cnt); + if (ACPI_FAILURE(status)) { + dev_err(&dev->dev, "can't evaluate _CRS: %d\n", status); + return -EINVAL; + } + if (!res_cnt) + return -EINVAL; + buffer->length = sizeof(struct acpi_resource) * (res_cnt + 1) + 1; + buffer->pointer = kzalloc(buffer->length - 1, GFP_KERNEL); + if (!buffer->pointer) + return -ENOMEM; + + resource = (struct acpi_resource *)buffer->pointer; + status = acpi_walk_resources(handle, METHOD_NAME__CRS, + pnpacpi_type_resources, &resource); + if (ACPI_FAILURE(status)) { + kfree(buffer->pointer); + dev_err(&dev->dev, "can't evaluate _CRS: %d\n", status); + return -EINVAL; + } + /* resource will pointer the end resource now */ + resource->type = ACPI_RESOURCE_TYPE_END_TAG; + resource->length = sizeof(struct acpi_resource); + + return 0; +} + +static void pnpacpi_encode_irq(struct pnp_dev *dev, + struct acpi_resource *resource, + struct resource *p) +{ + struct acpi_resource_irq *irq = &resource->data.irq; + u8 triggering, polarity, shareable; + + if (!pnp_resource_enabled(p)) { + irq->interrupt_count = 0; + pnp_dbg(&dev->dev, " encode irq (%s)\n", + p ? "disabled" : "missing"); + return; + } + + decode_irq_flags(dev, p->flags, &triggering, &polarity, &shareable); + irq->triggering = triggering; + irq->polarity = polarity; + irq->shareable = shareable; + irq->interrupt_count = 1; + irq->interrupts[0] = p->start; + + pnp_dbg(&dev->dev, " encode irq %d %s %s %s (%d-byte descriptor)\n", + (int) p->start, + triggering == ACPI_LEVEL_SENSITIVE ? "level" : "edge", + polarity == ACPI_ACTIVE_LOW ? "low" : "high", + irq->shareable == ACPI_SHARED ? "shared" : "exclusive", + irq->descriptor_length); +} + +static void pnpacpi_encode_ext_irq(struct pnp_dev *dev, + struct acpi_resource *resource, + struct resource *p) +{ + struct acpi_resource_extended_irq *extended_irq = &resource->data.extended_irq; + u8 triggering, polarity, shareable; + + if (!pnp_resource_enabled(p)) { + extended_irq->interrupt_count = 0; + pnp_dbg(&dev->dev, " encode extended irq (%s)\n", + p ? "disabled" : "missing"); + return; + } + + decode_irq_flags(dev, p->flags, &triggering, &polarity, &shareable); + extended_irq->producer_consumer = ACPI_CONSUMER; + extended_irq->triggering = triggering; + extended_irq->polarity = polarity; + extended_irq->shareable = shareable; + extended_irq->interrupt_count = 1; + extended_irq->interrupts[0] = p->start; + + pnp_dbg(&dev->dev, " encode irq %d %s %s %s\n", (int) p->start, + triggering == ACPI_LEVEL_SENSITIVE ? "level" : "edge", + polarity == ACPI_ACTIVE_LOW ? "low" : "high", + extended_irq->shareable == ACPI_SHARED ? "shared" : "exclusive"); +} + +static void pnpacpi_encode_dma(struct pnp_dev *dev, + struct acpi_resource *resource, + struct resource *p) +{ + struct acpi_resource_dma *dma = &resource->data.dma; + + if (!pnp_resource_enabled(p)) { + dma->channel_count = 0; + pnp_dbg(&dev->dev, " encode dma (%s)\n", + p ? "disabled" : "missing"); + return; + } + + /* Note: pnp_assign_dma will copy pnp_dma->flags into p->flags */ + switch (p->flags & IORESOURCE_DMA_SPEED_MASK) { + case IORESOURCE_DMA_TYPEA: + dma->type = ACPI_TYPE_A; + break; + case IORESOURCE_DMA_TYPEB: + dma->type = ACPI_TYPE_B; + break; + case IORESOURCE_DMA_TYPEF: + dma->type = ACPI_TYPE_F; + break; + default: + dma->type = ACPI_COMPATIBILITY; + } + + switch (p->flags & IORESOURCE_DMA_TYPE_MASK) { + case IORESOURCE_DMA_8BIT: + dma->transfer = ACPI_TRANSFER_8; + break; + case IORESOURCE_DMA_8AND16BIT: + dma->transfer = ACPI_TRANSFER_8_16; + break; + default: + dma->transfer = ACPI_TRANSFER_16; + } + + dma->bus_master = !!(p->flags & IORESOURCE_DMA_MASTER); + dma->channel_count = 1; + dma->channels[0] = p->start; + + pnp_dbg(&dev->dev, " encode dma %d " + "type %#x transfer %#x master %d\n", + (int) p->start, dma->type, dma->transfer, dma->bus_master); +} + +static void pnpacpi_encode_io(struct pnp_dev *dev, + struct acpi_resource *resource, + struct resource *p) +{ + struct acpi_resource_io *io = &resource->data.io; + + if (pnp_resource_enabled(p)) { + /* Note: pnp_assign_port copies pnp_port->flags into p->flags */ + io->io_decode = (p->flags & IORESOURCE_IO_16BIT_ADDR) ? + ACPI_DECODE_16 : ACPI_DECODE_10; + io->minimum = p->start; + io->maximum = p->end; + io->alignment = 0; /* Correct? */ + io->address_length = resource_size(p); + } else { + io->minimum = 0; + io->address_length = 0; + } + + pnp_dbg(&dev->dev, " encode io %#x-%#x decode %#x\n", io->minimum, + io->minimum + io->address_length - 1, io->io_decode); +} + +static void pnpacpi_encode_fixed_io(struct pnp_dev *dev, + struct acpi_resource *resource, + struct resource *p) +{ + struct acpi_resource_fixed_io *fixed_io = &resource->data.fixed_io; + + if (pnp_resource_enabled(p)) { + fixed_io->address = p->start; + fixed_io->address_length = resource_size(p); + } else { + fixed_io->address = 0; + fixed_io->address_length = 0; + } + + pnp_dbg(&dev->dev, " encode fixed_io %#x-%#x\n", fixed_io->address, + fixed_io->address + fixed_io->address_length - 1); +} + +static void pnpacpi_encode_mem24(struct pnp_dev *dev, + struct acpi_resource *resource, + struct resource *p) +{ + struct acpi_resource_memory24 *memory24 = &resource->data.memory24; + + if (pnp_resource_enabled(p)) { + /* Note: pnp_assign_mem copies pnp_mem->flags into p->flags */ + memory24->write_protect = p->flags & IORESOURCE_MEM_WRITEABLE ? + ACPI_READ_WRITE_MEMORY : ACPI_READ_ONLY_MEMORY; + memory24->minimum = p->start; + memory24->maximum = p->end; + memory24->alignment = 0; + memory24->address_length = resource_size(p); + } else { + memory24->minimum = 0; + memory24->address_length = 0; + } + + pnp_dbg(&dev->dev, " encode mem24 %#x-%#x write_protect %#x\n", + memory24->minimum, + memory24->minimum + memory24->address_length - 1, + memory24->write_protect); +} + +static void pnpacpi_encode_mem32(struct pnp_dev *dev, + struct acpi_resource *resource, + struct resource *p) +{ + struct acpi_resource_memory32 *memory32 = &resource->data.memory32; + + if (pnp_resource_enabled(p)) { + memory32->write_protect = p->flags & IORESOURCE_MEM_WRITEABLE ? + ACPI_READ_WRITE_MEMORY : ACPI_READ_ONLY_MEMORY; + memory32->minimum = p->start; + memory32->maximum = p->end; + memory32->alignment = 0; + memory32->address_length = resource_size(p); + } else { + memory32->minimum = 0; + memory32->alignment = 0; + } + + pnp_dbg(&dev->dev, " encode mem32 %#x-%#x write_protect %#x\n", + memory32->minimum, + memory32->minimum + memory32->address_length - 1, + memory32->write_protect); +} + +static void pnpacpi_encode_fixed_mem32(struct pnp_dev *dev, + struct acpi_resource *resource, + struct resource *p) +{ + struct acpi_resource_fixed_memory32 *fixed_memory32 = &resource->data.fixed_memory32; + + if (pnp_resource_enabled(p)) { + fixed_memory32->write_protect = + p->flags & IORESOURCE_MEM_WRITEABLE ? + ACPI_READ_WRITE_MEMORY : ACPI_READ_ONLY_MEMORY; + fixed_memory32->address = p->start; + fixed_memory32->address_length = resource_size(p); + } else { + fixed_memory32->address = 0; + fixed_memory32->address_length = 0; + } + + pnp_dbg(&dev->dev, " encode fixed_mem32 %#x-%#x write_protect %#x\n", + fixed_memory32->address, + fixed_memory32->address + fixed_memory32->address_length - 1, + fixed_memory32->write_protect); +} + +int pnpacpi_encode_resources(struct pnp_dev *dev, struct acpi_buffer *buffer) +{ + int i = 0; + /* pnpacpi_build_resource_template allocates extra mem */ + int res_cnt = (buffer->length - 1) / sizeof(struct acpi_resource) - 1; + struct acpi_resource *resource = buffer->pointer; + unsigned int port = 0, irq = 0, dma = 0, mem = 0; + + pnp_dbg(&dev->dev, "encode %d resources\n", res_cnt); + while (i < res_cnt) { + switch (resource->type) { + case ACPI_RESOURCE_TYPE_IRQ: + pnpacpi_encode_irq(dev, resource, + pnp_get_resource(dev, IORESOURCE_IRQ, irq)); + irq++; + break; + + case ACPI_RESOURCE_TYPE_DMA: + pnpacpi_encode_dma(dev, resource, + pnp_get_resource(dev, IORESOURCE_DMA, dma)); + dma++; + break; + case ACPI_RESOURCE_TYPE_IO: + pnpacpi_encode_io(dev, resource, + pnp_get_resource(dev, IORESOURCE_IO, port)); + port++; + break; + case ACPI_RESOURCE_TYPE_FIXED_IO: + pnpacpi_encode_fixed_io(dev, resource, + pnp_get_resource(dev, IORESOURCE_IO, port)); + port++; + break; + case ACPI_RESOURCE_TYPE_MEMORY24: + pnpacpi_encode_mem24(dev, resource, + pnp_get_resource(dev, IORESOURCE_MEM, mem)); + mem++; + break; + case ACPI_RESOURCE_TYPE_MEMORY32: + pnpacpi_encode_mem32(dev, resource, + pnp_get_resource(dev, IORESOURCE_MEM, mem)); + mem++; + break; + case ACPI_RESOURCE_TYPE_FIXED_MEMORY32: + pnpacpi_encode_fixed_mem32(dev, resource, + pnp_get_resource(dev, IORESOURCE_MEM, mem)); + mem++; + break; + case ACPI_RESOURCE_TYPE_EXTENDED_IRQ: + pnpacpi_encode_ext_irq(dev, resource, + pnp_get_resource(dev, IORESOURCE_IRQ, irq)); + irq++; + break; + case ACPI_RESOURCE_TYPE_START_DEPENDENT: + case ACPI_RESOURCE_TYPE_END_DEPENDENT: + case ACPI_RESOURCE_TYPE_VENDOR: + case ACPI_RESOURCE_TYPE_END_TAG: + case ACPI_RESOURCE_TYPE_ADDRESS16: + case ACPI_RESOURCE_TYPE_ADDRESS32: + case ACPI_RESOURCE_TYPE_ADDRESS64: + case ACPI_RESOURCE_TYPE_EXTENDED_ADDRESS64: + case ACPI_RESOURCE_TYPE_GENERIC_REGISTER: + default: /* other type */ + dev_warn(&dev->dev, + "can't encode unknown resource type %d\n", + resource->type); + return -EINVAL; + } + resource++; + i++; + } + return 0; +} diff --git a/drivers/pnp/pnpbios/Kconfig b/drivers/pnp/pnpbios/Kconfig new file mode 100644 index 000000000..bcdac269a --- /dev/null +++ b/drivers/pnp/pnpbios/Kconfig @@ -0,0 +1,43 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Plug and Play BIOS configuration +# +config PNPBIOS + bool "Plug and Play BIOS support" + depends on ISA && X86_32 + 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. + diff --git a/drivers/pnp/pnpbios/Makefile b/drivers/pnp/pnpbios/Makefile new file mode 100644 index 000000000..a91437c56 --- /dev/null +++ b/drivers/pnp/pnpbios/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# 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 diff --git a/drivers/pnp/pnpbios/bioscalls.c b/drivers/pnp/pnpbios/bioscalls.c new file mode 100644 index 000000000..1f31dce58 --- /dev/null +++ b/drivers/pnp/pnpbios/bioscalls.c @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * bioscalls.c - the lowlevel layer of the PnPBIOS driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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_rw((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(DESC_DATA32_BIOS, + (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 DEFINE_SPINLOCK(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_rw(cpu)[0x40 / 8]; + get_cpu_gdt_rw(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_rw(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; + + 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_rw(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)); + } +} diff --git a/drivers/pnp/pnpbios/core.c b/drivers/pnp/pnpbios/core.c new file mode 100644 index 000000000..f7e86ae9f --- /dev/null +++ b/drivers/pnp/pnpbios/core.c @@ -0,0 +1,575 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * 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 + * Modifications (C) 1998 Tom Lees + * Minor reorganizations by David Hinds + * Further modifications (C) 2001, 2002 by: + * Alan Cox + * Thomas Hood + * Brian Gerst + * + * Ported to the PnP Layer and several additional improvements (C) 2002 + * by Adam Belay + */ + +/* Change Log + * + * Adam Belay - - 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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) +{ + static char const sbin_pnpbios[] = "/sbin/pnpbios"; + 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] = (char *)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(sbin_pnpbios, 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: + kthread_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); + printk(KERN_WARNING "PnPBIOS: disabling dock monitoring.\n"); + kthread_complete_and_exit(&unload_sem, 0); + } + 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 + } + } + } + kthread_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 pnp_dev *dev; + char id[8]; + int error; + + /* check if the device is already added */ + list_for_each_entry(dev, &pnpbios_protocol.devices, 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 const struct dmi_system_id pnpbios_dmi_table[] __initconst = { + { /* 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) || + arch_pnpbios_disabled()) { + 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"); + return PTR_ERR_OR_ZERO(task); +} + +/* Start the kernel thread later: */ +device_initcall(pnpbios_thread_init); + +EXPORT_SYMBOL(pnpbios_protocol); diff --git a/drivers/pnp/pnpbios/pnpbios.h b/drivers/pnp/pnpbios/pnpbios.h new file mode 100644 index 000000000..f33020068 --- /dev/null +++ b/drivers/pnp/pnpbios/pnpbios.h @@ -0,0 +1,168 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * 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 + * Minor reorganizations by David Hinds + */ + +/* + * 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[]; +}; +#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 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 */ diff --git a/drivers/pnp/pnpbios/proc.c b/drivers/pnp/pnpbios/proc.c new file mode 100644 index 000000000..0f0d819b1 --- /dev/null +++ b/drivers/pnp/pnpbios/proc.c @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * /proc/bus/pnp interface for Plug and Play devices + * + * Written by David Hinds, dahinds@users.sourceforge.net + * Modified by Thomas Hood + * + * The .../devices and .../ and .../boot/ 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 +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 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; +} + +#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 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_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 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 proc_ops pnpbios_proc_ops = { + .proc_open = pnpbios_proc_open, + .proc_read = seq_read, + .proc_lseek = seq_lseek, + .proc_release = single_release, + .proc_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_ops, + (void *)(long)(node->handle)); + } + + if (!proc_pnp_boot) + return -EIO; + if (proc_create_data(name, 0644, proc_pnp_boot, &pnpbios_proc_ops, + (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_single("devices", 0, proc_pnp, pnp_devices_proc_show); + proc_create_single("configuration_info", 0, proc_pnp, + pnpconfig_proc_show); + proc_create_single("escd_info", 0, proc_pnp, escd_info_proc_show); + proc_create_single("escd", S_IRUSR, proc_pnp, escd_proc_show); + proc_create_single("legacy_device_resources", 0, proc_pnp, + pnp_legacyres_proc_show); + 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); +} diff --git a/drivers/pnp/pnpbios/rsparser.c b/drivers/pnp/pnpbios/rsparser.c new file mode 100644 index 000000000..70af7821d --- /dev/null +++ b/drivers/pnp/pnpbios/rsparser.c @@ -0,0 +1,810 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * rsparser.c - parses and encodes pnpbios resource data streams + */ + +#include +#include +#include + +#ifdef CONFIG_PCI +#include +#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: + memcpy(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; +} diff --git a/drivers/pnp/quirks.c b/drivers/pnp/quirks.c new file mode 100644 index 000000000..7aaecd137 --- /dev/null +++ b/drivers/pnp/quirks.c @@ -0,0 +1,453 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * This file contains quirk handling code for PnP devices + * Some devices do not report all their resources, and need to have extra + * resources added. This is most easily accomplished at initialisation time + * when building up the resource structure for the first time. + * + * Copyright (c) 2000 Peter Denison + * Copyright (C) 2008 Hewlett-Packard Development Company, L.P. + * Bjorn Helgaas + * + * Heavily based on PCI quirks handling which is + * + * Copyright (c) 1999 Martin Mares + */ + +#include +#include +#include +#include +#include +#include +#include +#include "base.h" + +static void quirk_awe32_add_ports(struct pnp_dev *dev, + struct pnp_option *option, + unsigned int offset) +{ + struct pnp_option *new_option; + + new_option = kmalloc_obj(struct pnp_option); + if (!new_option) { + dev_err(&dev->dev, "couldn't add ioport region to option set " + "%d\n", pnp_option_set(option)); + return; + } + + *new_option = *option; + new_option->u.port.min += offset; + new_option->u.port.max += offset; + list_add(&new_option->list, &option->list); + + dev_info(&dev->dev, "added ioport region %#llx-%#llx to set %d\n", + (unsigned long long) new_option->u.port.min, + (unsigned long long) new_option->u.port.max, + pnp_option_set(option)); +} + +static void quirk_awe32_resources(struct pnp_dev *dev) +{ + struct pnp_option *option; + unsigned int set = ~0; + + /* + * Add two extra ioport regions (at offset 0x400 and 0x800 from the + * one given) to every dependent option set. + */ + list_for_each_entry(option, &dev->options, list) { + if (pnp_option_is_dependent(option) && + pnp_option_set(option) != set) { + set = pnp_option_set(option); + quirk_awe32_add_ports(dev, option, 0x800); + quirk_awe32_add_ports(dev, option, 0x400); + } + } +} + +static void quirk_cmi8330_resources(struct pnp_dev *dev) +{ + struct pnp_option *option; + struct pnp_irq *irq; + struct pnp_dma *dma; + + list_for_each_entry(option, &dev->options, list) { + if (!pnp_option_is_dependent(option)) + continue; + + if (option->type == IORESOURCE_IRQ) { + irq = &option->u.irq; + bitmap_zero(irq->map.bits, PNP_IRQ_NR); + __set_bit(5, irq->map.bits); + __set_bit(7, irq->map.bits); + __set_bit(10, irq->map.bits); + dev_info(&dev->dev, "set possible IRQs in " + "option set %d to 5, 7, 10\n", + pnp_option_set(option)); + } else if (option->type == IORESOURCE_DMA) { + dma = &option->u.dma; + if ((dma->flags & IORESOURCE_DMA_TYPE_MASK) == + IORESOURCE_DMA_8BIT && + dma->map != 0x0A) { + dev_info(&dev->dev, "changing possible " + "DMA channel mask in option set %d " + "from %#02x to 0x0A (1, 3)\n", + pnp_option_set(option), dma->map); + dma->map = 0x0A; + } + } + } +} + +static void quirk_sb16audio_resources(struct pnp_dev *dev) +{ + struct pnp_option *option; + unsigned int prev_option_flags = ~0, n = 0; + struct pnp_port *port; + + /* + * The default range on the OPL port for these devices is 0x388-0x388. + * Here we increase that range so that two such cards can be + * auto-configured. + */ + list_for_each_entry(option, &dev->options, list) { + if (prev_option_flags != option->flags) { + prev_option_flags = option->flags; + n = 0; + } + + if (pnp_option_is_dependent(option) && + option->type == IORESOURCE_IO) { + n++; + port = &option->u.port; + if (n == 3 && port->min == port->max) { + port->max += 0x70; + dev_info(&dev->dev, "increased option port " + "range from %#llx-%#llx to " + "%#llx-%#llx\n", + (unsigned long long) port->min, + (unsigned long long) port->min, + (unsigned long long) port->min, + (unsigned long long) port->max); + } + } + } +} + +static struct pnp_option *pnp_clone_dependent_set(struct pnp_dev *dev, + unsigned int set) +{ + struct pnp_option *tail = NULL, *first_new_option = NULL; + struct pnp_option *option, *new_option; + unsigned int flags; + + list_for_each_entry(option, &dev->options, list) { + if (pnp_option_is_dependent(option)) + tail = option; + } + if (!tail) { + dev_err(&dev->dev, "no dependent option sets\n"); + return NULL; + } + + flags = pnp_new_dependent_set(dev, PNP_RES_PRIORITY_FUNCTIONAL); + list_for_each_entry(option, &dev->options, list) { + if (pnp_option_is_dependent(option) && + pnp_option_set(option) == set) { + new_option = kmalloc_obj(struct pnp_option); + if (!new_option) { + dev_err(&dev->dev, "couldn't clone dependent " + "set %d\n", set); + return NULL; + } + + *new_option = *option; + new_option->flags = flags; + if (!first_new_option) + first_new_option = new_option; + + list_add(&new_option->list, &tail->list); + tail = new_option; + } + } + + return first_new_option; +} + + +static void quirk_add_irq_optional_dependent_sets(struct pnp_dev *dev) +{ + struct pnp_option *new_option; + unsigned int num_sets, i, set; + struct pnp_irq *irq; + + num_sets = dev->num_dependent_sets; + for (i = 0; i < num_sets; i++) { + new_option = pnp_clone_dependent_set(dev, i); + if (!new_option) + return; + + set = pnp_option_set(new_option); + while (new_option && pnp_option_set(new_option) == set) { + if (new_option->type == IORESOURCE_IRQ) { + irq = &new_option->u.irq; + irq->flags |= IORESOURCE_IRQ_OPTIONAL; + } + dbg_pnp_show_option(dev, new_option); + new_option = list_entry(new_option->list.next, + struct pnp_option, list); + } + + dev_info(&dev->dev, "added dependent option set %d (same as " + "set %d except IRQ optional)\n", set, i); + } +} + +static void quirk_ad1815_mpu_resources(struct pnp_dev *dev) +{ + struct pnp_option *option; + struct pnp_irq *irq = NULL; + unsigned int independent_irqs = 0; + + list_for_each_entry(option, &dev->options, list) { + if (option->type == IORESOURCE_IRQ && + !pnp_option_is_dependent(option)) { + independent_irqs++; + irq = &option->u.irq; + } + } + + if (independent_irqs != 1) + return; + + irq->flags |= IORESOURCE_IRQ_OPTIONAL; + dev_info(&dev->dev, "made independent IRQ optional\n"); +} + +static void quirk_system_pci_resources(struct pnp_dev *dev) +{ + struct pci_dev *pdev = NULL; + struct resource *res, *r; + int i, j; + + /* + * Some BIOSes have PNP motherboard devices with resources that + * partially overlap PCI BARs. The PNP system driver claims these + * motherboard resources, which prevents the normal PCI driver from + * requesting them later. + * + * This patch disables the PNP resources that conflict with PCI BARs + * so they won't be claimed by the PNP system driver. + */ + for_each_pci_dev(pdev) { + pci_dev_for_each_resource(pdev, r, i) { + unsigned long type = resource_type(r); + + if (!(type == IORESOURCE_IO || type == IORESOURCE_MEM) || + resource_size(r) == 0) + continue; + + if (r->flags & IORESOURCE_UNSET) + continue; + + for (j = 0; + (res = pnp_get_resource(dev, type, j)); j++) { + if (res->start == 0 && res->end == 0) + continue; + + /* + * If the PNP region doesn't overlap the PCI + * region at all, there's no problem. + */ + if (!resource_overlaps(res, r)) + continue; + + /* + * If the PNP region completely encloses (or is + * at least as large as) the PCI region, that's + * also OK. For example, this happens when the + * PNP device describes a bridge with PCI + * behind it. + */ + if (res->start <= r->start && res->end >= r->end) + continue; + + /* + * Otherwise, the PNP region overlaps *part* of + * the PCI region, and that might prevent a PCI + * driver from requesting its resources. + */ + dev_warn(&dev->dev, + "disabling %pR because it overlaps %s BAR %d %pR\n", + res, pci_name(pdev), i, r); + res->flags |= IORESOURCE_DISABLED; + } + } + } +} + +#ifdef CONFIG_AMD_NB + +#include + +static void quirk_amd_mmconfig_area(struct pnp_dev *dev) +{ + resource_size_t start, end; + struct pnp_resource *pnp_res; + struct resource *res; + struct resource mmconfig_res, *mmconfig; + + mmconfig = amd_get_mmconfig_range(&mmconfig_res); + if (!mmconfig) + return; + + list_for_each_entry(pnp_res, &dev->resources, list) { + res = &pnp_res->res; + if (res->end < mmconfig->start || res->start > mmconfig->end || + (res->start == mmconfig->start && res->end == mmconfig->end)) + continue; + + dev_info(&dev->dev, FW_BUG + "%pR covers only part of AMD MMCONFIG area %pR; adding more reservations\n", + res, mmconfig); + if (mmconfig->start < res->start) { + start = mmconfig->start; + end = res->start - 1; + pnp_add_mem_resource(dev, start, end, 0); + } + if (mmconfig->end > res->end) { + start = res->end + 1; + end = mmconfig->end; + pnp_add_mem_resource(dev, start, end, 0); + } + break; + } +} +#endif + +#ifdef CONFIG_PCI +/* Device IDs of parts that have 32KB MCH space */ +static const unsigned int mch_quirk_devices[] = { + 0x0154, /* Ivy Bridge */ + 0x0a04, /* Haswell-ULT */ + 0x0c00, /* Haswell */ + 0x1604, /* Broadwell */ +}; + +static struct pci_dev *get_intel_host(void) +{ + int i; + struct pci_dev *host; + + for (i = 0; i < ARRAY_SIZE(mch_quirk_devices); i++) { + host = pci_get_device(PCI_VENDOR_ID_INTEL, mch_quirk_devices[i], + NULL); + if (host) + return host; + } + return NULL; +} + +static void quirk_intel_mch(struct pnp_dev *dev) +{ + struct pci_dev *host; + u32 addr_lo, addr_hi; + struct pci_bus_region region; + struct resource mch; + struct pnp_resource *pnp_res; + struct resource *res; + + host = get_intel_host(); + if (!host) + return; + + /* + * MCHBAR is not an architected PCI BAR, so MCH space is usually + * reported as a PNP0C02 resource. The MCH space was originally + * 16KB, but is 32KB in newer parts. Some BIOSes still report a + * PNP0C02 resource that is only 16KB, which means the rest of the + * MCH space is consumed but unreported. + */ + + /* + * Read MCHBAR for Host Member Mapped Register Range Base + * https://www-ssl.intel.com/content/www/us/en/processors/core/4th-gen-core-family-desktop-vol-2-datasheet + * Sec 3.1.12. + */ + pci_read_config_dword(host, 0x48, &addr_lo); + region.start = addr_lo & ~0x7fff; + pci_read_config_dword(host, 0x4c, &addr_hi); + region.start |= (u64) addr_hi << 32; + region.end = region.start + 32*1024 - 1; + + memset(&mch, 0, sizeof(mch)); + mch.flags = IORESOURCE_MEM; + pcibios_bus_to_resource(host->bus, &mch, ®ion); + + list_for_each_entry(pnp_res, &dev->resources, list) { + res = &pnp_res->res; + if (res->end < mch.start || res->start > mch.end) + continue; /* no overlap */ + if (res->start == mch.start && res->end == mch.end) + continue; /* exact match */ + + dev_info(&dev->dev, FW_BUG "PNP resource %pR covers only part of %s Intel MCH; extending to %pR\n", + res, pci_name(host), &mch); + res->start = mch.start; + res->end = mch.end; + break; + } + + pci_dev_put(host); +} +#endif + +/* + * PnP Quirks + * Cards or devices that need some tweaking due to incomplete resource info + */ + +static struct pnp_fixup pnp_fixups[] = { + /* Soundblaster awe io port quirk */ + {"CTL0021", quirk_awe32_resources}, + {"CTL0022", quirk_awe32_resources}, + {"CTL0023", quirk_awe32_resources}, + /* CMI 8330 interrupt and dma fix */ + {"@X@0001", quirk_cmi8330_resources}, + /* Soundblaster audio device io port range quirk */ + {"CTL0001", quirk_sb16audio_resources}, + {"CTL0031", quirk_sb16audio_resources}, + {"CTL0041", quirk_sb16audio_resources}, + {"CTL0042", quirk_sb16audio_resources}, + {"CTL0043", quirk_sb16audio_resources}, + {"CTL0044", quirk_sb16audio_resources}, + {"CTL0045", quirk_sb16audio_resources}, + /* Add IRQ-optional MPU options */ + {"ADS7151", quirk_ad1815_mpu_resources}, + {"ADS7181", quirk_add_irq_optional_dependent_sets}, + {"AZT0002", quirk_add_irq_optional_dependent_sets}, + /* PnP resources that might overlap PCI BARs */ + {"PNP0c01", quirk_system_pci_resources}, + {"PNP0c02", quirk_system_pci_resources}, +#ifdef CONFIG_AMD_NB + {"PNP0c01", quirk_amd_mmconfig_area}, +#endif +#ifdef CONFIG_PCI + {"PNP0c02", quirk_intel_mch}, +#endif + {""} +}; + +void pnp_fixup_device(struct pnp_dev *dev) +{ + struct pnp_fixup *f; + + for (f = pnp_fixups; *f->id; f++) { + if (!compare_pnp_id(dev->id, f->id)) + continue; + pnp_dbg(&dev->dev, "%s: calling %pS\n", f->id, + f->quirk_function); + f->quirk_function(dev); + } +} diff --git a/drivers/pnp/resource.c b/drivers/pnp/resource.c new file mode 100644 index 000000000..6792bd023 --- /dev/null +++ b/drivers/pnp/resource.c @@ -0,0 +1,755 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * resource.c - Contains functions for registering and analyzing resource information + * + * based on isapnp.c resource management (c) Jaroslav Kysela + * Copyright 2003 Adam Belay + * Copyright (C) 2008 Hewlett-Packard Development Company, L.P. + * Bjorn Helgaas + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "base.h" + +static int pnp_reserve_irq[16] = {[0 ... 15] = -1 }; /* reserve (don't use) some IRQ */ +static int pnp_reserve_dma[8] = {[0 ... 7] = -1 }; /* reserve (don't use) some DMA */ +static int pnp_reserve_io[16] = {[0 ... 15] = -1 }; /* reserve (don't use) some I/O region */ +static int pnp_reserve_mem[16] = {[0 ... 15] = -1 }; /* reserve (don't use) some memory region */ + +/* + * option registration + */ + +static struct pnp_option *pnp_build_option(struct pnp_dev *dev, unsigned long type, + unsigned int option_flags) +{ + struct pnp_option *option; + + option = kzalloc_obj(struct pnp_option); + if (!option) + return NULL; + + option->flags = option_flags; + option->type = type; + + list_add_tail(&option->list, &dev->options); + return option; +} + +int pnp_register_irq_resource(struct pnp_dev *dev, unsigned int option_flags, + pnp_irq_mask_t *map, unsigned char flags) +{ + struct pnp_option *option; + struct pnp_irq *irq; + + option = pnp_build_option(dev, IORESOURCE_IRQ, option_flags); + if (!option) + return -ENOMEM; + + irq = &option->u.irq; + irq->map = *map; + irq->flags = flags; + +#ifdef CONFIG_PCI + { + int i; + + for (i = 0; i < 16; i++) + if (test_bit(i, irq->map.bits)) + pcibios_penalize_isa_irq(i, 0); + } +#endif + + dbg_pnp_show_option(dev, option); + return 0; +} + +int pnp_register_dma_resource(struct pnp_dev *dev, unsigned int option_flags, + unsigned char map, unsigned char flags) +{ + struct pnp_option *option; + struct pnp_dma *dma; + + option = pnp_build_option(dev, IORESOURCE_DMA, option_flags); + if (!option) + return -ENOMEM; + + dma = &option->u.dma; + dma->map = map; + dma->flags = flags; + + dbg_pnp_show_option(dev, option); + return 0; +} + +int pnp_register_port_resource(struct pnp_dev *dev, unsigned int option_flags, + resource_size_t min, resource_size_t max, + resource_size_t align, resource_size_t size, + unsigned char flags) +{ + struct pnp_option *option; + struct pnp_port *port; + + option = pnp_build_option(dev, IORESOURCE_IO, option_flags); + if (!option) + return -ENOMEM; + + port = &option->u.port; + port->min = min; + port->max = max; + port->align = align; + port->size = size; + port->flags = flags; + + dbg_pnp_show_option(dev, option); + return 0; +} + +int pnp_register_mem_resource(struct pnp_dev *dev, unsigned int option_flags, + resource_size_t min, resource_size_t max, + resource_size_t align, resource_size_t size, + unsigned char flags) +{ + struct pnp_option *option; + struct pnp_mem *mem; + + option = pnp_build_option(dev, IORESOURCE_MEM, option_flags); + if (!option) + return -ENOMEM; + + mem = &option->u.mem; + mem->min = min; + mem->max = max; + mem->align = align; + mem->size = size; + mem->flags = flags; + + dbg_pnp_show_option(dev, option); + return 0; +} + +void pnp_free_options(struct pnp_dev *dev) +{ + struct pnp_option *option, *tmp; + + list_for_each_entry_safe(option, tmp, &dev->options, list) { + list_del(&option->list); + kfree(option); + } +} + +/* + * resource validity checking + */ + +#define length(start, end) (*(end) - *(start) + 1) + +/* Two ranges conflict if one doesn't end before the other starts */ +#define ranged_conflict(starta, enda, startb, endb) \ + !((*(enda) < *(startb)) || (*(endb) < *(starta))) + +#define cannot_compare(flags) \ +((flags) & IORESOURCE_DISABLED) + +int pnp_check_port(struct pnp_dev *dev, struct resource *res) +{ + int i; + struct pnp_dev *tdev; + struct resource *tres; + resource_size_t *port, *end, *tport, *tend; + + port = &res->start; + end = &res->end; + + /* if the resource doesn't exist, don't complain about it */ + if (cannot_compare(res->flags)) + return 1; + + /* check if the resource is already in use, skip if the + * device is active because it itself may be in use */ + if (!dev->active) { + if (!request_region(*port, length(port, end), "pnp")) + return 0; + release_region(*port, length(port, end)); + } + + /* check if the resource is reserved */ + for (i = 0; i < 8; i++) { + int rport = pnp_reserve_io[i << 1]; + int rend = pnp_reserve_io[(i << 1) + 1] + rport - 1; + if (ranged_conflict(port, end, &rport, &rend)) + return 0; + } + + /* check for internal conflicts */ + for (i = 0; (tres = pnp_get_resource(dev, IORESOURCE_IO, i)); i++) { + if (tres != res && tres->flags & IORESOURCE_IO) { + tport = &tres->start; + tend = &tres->end; + if (ranged_conflict(port, end, tport, tend)) + return 0; + } + } + + /* check for conflicts with other pnp devices */ + pnp_for_each_dev(tdev) { + if (tdev == dev) + continue; + for (i = 0; + (tres = pnp_get_resource(tdev, IORESOURCE_IO, i)); + i++) { + if (tres->flags & IORESOURCE_IO) { + if (cannot_compare(tres->flags)) + continue; + if (tres->flags & IORESOURCE_WINDOW) + continue; + tport = &tres->start; + tend = &tres->end; + if (ranged_conflict(port, end, tport, tend)) + return 0; + } + } + } + + return 1; +} + +int pnp_check_mem(struct pnp_dev *dev, struct resource *res) +{ + int i; + struct pnp_dev *tdev; + struct resource *tres; + resource_size_t *addr, *end, *taddr, *tend; + + addr = &res->start; + end = &res->end; + + /* if the resource doesn't exist, don't complain about it */ + if (cannot_compare(res->flags)) + return 1; + + /* check if the resource is already in use, skip if the + * device is active because it itself may be in use */ + if (!dev->active) { + if (!request_mem_region(*addr, length(addr, end), "pnp")) + return 0; + release_mem_region(*addr, length(addr, end)); + } + + /* check if the resource is reserved */ + for (i = 0; i < 8; i++) { + int raddr = pnp_reserve_mem[i << 1]; + int rend = pnp_reserve_mem[(i << 1) + 1] + raddr - 1; + if (ranged_conflict(addr, end, &raddr, &rend)) + return 0; + } + + /* check for internal conflicts */ + for (i = 0; (tres = pnp_get_resource(dev, IORESOURCE_MEM, i)); i++) { + if (tres != res && tres->flags & IORESOURCE_MEM) { + taddr = &tres->start; + tend = &tres->end; + if (ranged_conflict(addr, end, taddr, tend)) + return 0; + } + } + + /* check for conflicts with other pnp devices */ + pnp_for_each_dev(tdev) { + if (tdev == dev) + continue; + for (i = 0; + (tres = pnp_get_resource(tdev, IORESOURCE_MEM, i)); + i++) { + if (tres->flags & IORESOURCE_MEM) { + if (cannot_compare(tres->flags)) + continue; + if (tres->flags & IORESOURCE_WINDOW) + continue; + taddr = &tres->start; + tend = &tres->end; + if (ranged_conflict(addr, end, taddr, tend)) + return 0; + } + } + } + + return 1; +} + +static irqreturn_t pnp_test_handler(int irq, void *dev_id) +{ + return IRQ_HANDLED; +} + +#ifdef CONFIG_PCI +static int pci_dev_uses_irq(struct pnp_dev *pnp, struct pci_dev *pci, + unsigned int irq) +{ + u32 class; + u8 progif; + + if (pci->irq == irq) { + pnp_dbg(&pnp->dev, " device %s using irq %d\n", + pci_name(pci), irq); + return 1; + } + + /* + * See pci_setup_device() and ata_pci_sff_activate_host() for + * similar IDE legacy detection. + */ + pci_read_config_dword(pci, PCI_CLASS_REVISION, &class); + class >>= 8; /* discard revision ID */ + progif = class & 0xff; + class >>= 8; + + if (class == PCI_CLASS_STORAGE_IDE) { + /* + * Unless both channels are native-PCI mode only, + * treat the compatibility IRQs as busy. + */ + if ((progif & 0x5) != 0x5) + if (ATA_PRIMARY_IRQ(pci) == irq || + ATA_SECONDARY_IRQ(pci) == irq) { + pnp_dbg(&pnp->dev, " legacy IDE device %s " + "using irq %d\n", pci_name(pci), irq); + return 1; + } + } + + return 0; +} +#endif + +static int pci_uses_irq(struct pnp_dev *pnp, unsigned int irq) +{ +#ifdef CONFIG_PCI + struct pci_dev *pci = NULL; + + for_each_pci_dev(pci) { + if (pci_dev_uses_irq(pnp, pci, irq)) { + pci_dev_put(pci); + return 1; + } + } +#endif + return 0; +} + +int pnp_check_irq(struct pnp_dev *dev, struct resource *res) +{ + int i; + struct pnp_dev *tdev; + struct resource *tres; + resource_size_t *irq; + + irq = &res->start; + + /* if the resource doesn't exist, don't complain about it */ + if (cannot_compare(res->flags)) + return 1; + + /* check if the resource is valid */ + if (*irq > 15) + return 0; + + /* check if the resource is reserved */ + for (i = 0; i < 16; i++) { + if (pnp_reserve_irq[i] == *irq) + return 0; + } + + /* check for internal conflicts */ + for (i = 0; (tres = pnp_get_resource(dev, IORESOURCE_IRQ, i)); i++) { + if (tres != res && tres->flags & IORESOURCE_IRQ) { + if (tres->start == *irq) + return 0; + } + } + + /* check if the resource is being used by a pci device */ + if (pci_uses_irq(dev, *irq)) + return 0; + + /* check if the resource is already in use, skip if the + * device is active because it itself may be in use */ + if (!dev->active) { + if (request_irq(*irq, pnp_test_handler, + IRQF_PROBE_SHARED, "pnp", NULL)) + return 0; + free_irq(*irq, NULL); + } + + /* check for conflicts with other pnp devices */ + pnp_for_each_dev(tdev) { + if (tdev == dev) + continue; + for (i = 0; + (tres = pnp_get_resource(tdev, IORESOURCE_IRQ, i)); + i++) { + if (tres->flags & IORESOURCE_IRQ) { + if (cannot_compare(tres->flags)) + continue; + if (tres->start == *irq) + return 0; + } + } + } + + return 1; +} + +#ifdef CONFIG_ISA_DMA_API +int pnp_check_dma(struct pnp_dev *dev, struct resource *res) +{ + int i; + struct pnp_dev *tdev; + struct resource *tres; + resource_size_t *dma; + + dma = &res->start; + + /* if the resource doesn't exist, don't complain about it */ + if (cannot_compare(res->flags)) + return 1; + + /* check if the resource is valid */ + if (*dma == 4 || *dma > 7) + return 0; + + /* check if the resource is reserved */ + for (i = 0; i < 8; i++) { + if (pnp_reserve_dma[i] == *dma) + return 0; + } + + /* check for internal conflicts */ + for (i = 0; (tres = pnp_get_resource(dev, IORESOURCE_DMA, i)); i++) { + if (tres != res && tres->flags & IORESOURCE_DMA) { + if (tres->start == *dma) + return 0; + } + } + + /* check if the resource is already in use, skip if the + * device is active because it itself may be in use */ + if (!dev->active) { + if (request_dma(*dma, "pnp")) + return 0; + free_dma(*dma); + } + + /* check for conflicts with other pnp devices */ + pnp_for_each_dev(tdev) { + if (tdev == dev) + continue; + for (i = 0; + (tres = pnp_get_resource(tdev, IORESOURCE_DMA, i)); + i++) { + if (tres->flags & IORESOURCE_DMA) { + if (cannot_compare(tres->flags)) + continue; + if (tres->start == *dma) + return 0; + } + } + } + + return 1; +} +#endif /* CONFIG_ISA_DMA_API */ + +unsigned long pnp_resource_type(struct resource *res) +{ + return res->flags & (IORESOURCE_IO | IORESOURCE_MEM | + IORESOURCE_IRQ | IORESOURCE_DMA | + IORESOURCE_BUS); +} + +struct resource *pnp_get_resource(struct pnp_dev *dev, + unsigned long type, unsigned int num) +{ + struct pnp_resource *pnp_res; + struct resource *res; + + list_for_each_entry(pnp_res, &dev->resources, list) { + res = &pnp_res->res; + if (pnp_resource_type(res) == type && num-- == 0) + return res; + } + return NULL; +} +EXPORT_SYMBOL(pnp_get_resource); + +static struct pnp_resource *pnp_new_resource(struct pnp_dev *dev) +{ + struct pnp_resource *pnp_res; + + pnp_res = kzalloc_obj(struct pnp_resource); + if (!pnp_res) + return NULL; + + list_add_tail(&pnp_res->list, &dev->resources); + return pnp_res; +} + +struct pnp_resource *pnp_add_resource(struct pnp_dev *dev, + struct resource *res) +{ + struct pnp_resource *pnp_res; + + pnp_res = pnp_new_resource(dev); + if (!pnp_res) { + dev_err(&dev->dev, "can't add resource %pR\n", res); + return NULL; + } + + pnp_res->res = *res; + pnp_res->res.name = dev->name; + dev_dbg(&dev->dev, "%pR\n", res); + return pnp_res; +} + +struct pnp_resource *pnp_add_irq_resource(struct pnp_dev *dev, int irq, + int flags) +{ + struct pnp_resource *pnp_res; + struct resource *res; + + pnp_res = pnp_new_resource(dev); + if (!pnp_res) { + dev_err(&dev->dev, "can't add resource for IRQ %d\n", irq); + return NULL; + } + + res = &pnp_res->res; + res->flags = IORESOURCE_IRQ | flags; + res->start = irq; + res->end = irq; + + dev_dbg(&dev->dev, "%pR\n", res); + return pnp_res; +} + +struct pnp_resource *pnp_add_dma_resource(struct pnp_dev *dev, int dma, + int flags) +{ + struct pnp_resource *pnp_res; + struct resource *res; + + pnp_res = pnp_new_resource(dev); + if (!pnp_res) { + dev_err(&dev->dev, "can't add resource for DMA %d\n", dma); + return NULL; + } + + res = &pnp_res->res; + res->flags = IORESOURCE_DMA | flags; + res->start = dma; + res->end = dma; + + dev_printk(KERN_DEBUG, &dev->dev, "%pR\n", res); + return pnp_res; +} + +struct pnp_resource *pnp_add_io_resource(struct pnp_dev *dev, + resource_size_t start, + resource_size_t end, int flags) +{ + struct pnp_resource *pnp_res; + struct resource *res; + + pnp_res = pnp_new_resource(dev); + if (!pnp_res) { + dev_err(&dev->dev, "can't add resource for IO %#llx-%#llx\n", + (unsigned long long) start, + (unsigned long long) end); + return NULL; + } + + res = &pnp_res->res; + res->flags = IORESOURCE_IO | flags; + res->start = start; + res->end = end; + + dev_printk(KERN_DEBUG, &dev->dev, "%pR\n", res); + return pnp_res; +} + +struct pnp_resource *pnp_add_mem_resource(struct pnp_dev *dev, + resource_size_t start, + resource_size_t end, int flags) +{ + struct pnp_resource *pnp_res; + struct resource *res; + + pnp_res = pnp_new_resource(dev); + if (!pnp_res) { + dev_err(&dev->dev, "can't add resource for MEM %#llx-%#llx\n", + (unsigned long long) start, + (unsigned long long) end); + return NULL; + } + + res = &pnp_res->res; + res->flags = IORESOURCE_MEM | flags; + res->start = start; + res->end = end; + + dev_printk(KERN_DEBUG, &dev->dev, "%pR\n", res); + return pnp_res; +} + +struct pnp_resource *pnp_add_bus_resource(struct pnp_dev *dev, + resource_size_t start, + resource_size_t end) +{ + struct pnp_resource *pnp_res; + struct resource *res; + + pnp_res = pnp_new_resource(dev); + if (!pnp_res) { + dev_err(&dev->dev, "can't add resource for BUS %#llx-%#llx\n", + (unsigned long long) start, + (unsigned long long) end); + return NULL; + } + + res = &pnp_res->res; + res->flags = IORESOURCE_BUS; + res->start = start; + res->end = end; + + dev_printk(KERN_DEBUG, &dev->dev, "%pR\n", res); + return pnp_res; +} + +/* + * Determine whether the specified resource is a possible configuration + * for this device. + */ +int pnp_possible_config(struct pnp_dev *dev, int type, resource_size_t start, + resource_size_t size) +{ + struct pnp_option *option; + struct pnp_port *port; + struct pnp_mem *mem; + struct pnp_irq *irq; + struct pnp_dma *dma; + + list_for_each_entry(option, &dev->options, list) { + if (option->type != type) + continue; + + switch (option->type) { + case IORESOURCE_IO: + port = &option->u.port; + if (port->min == start && port->size == size) + return 1; + break; + case IORESOURCE_MEM: + mem = &option->u.mem; + if (mem->min == start && mem->size == size) + return 1; + break; + case IORESOURCE_IRQ: + irq = &option->u.irq; + if (start < PNP_IRQ_NR && + test_bit(start, irq->map.bits)) + return 1; + break; + case IORESOURCE_DMA: + dma = &option->u.dma; + if (dma->map & (1 << start)) + return 1; + break; + } + } + + return 0; +} +EXPORT_SYMBOL(pnp_possible_config); + +int pnp_range_reserved(resource_size_t start, resource_size_t end) +{ + struct pnp_dev *dev; + struct pnp_resource *pnp_res; + resource_size_t *dev_start, *dev_end; + + pnp_for_each_dev(dev) { + list_for_each_entry(pnp_res, &dev->resources, list) { + dev_start = &pnp_res->res.start; + dev_end = &pnp_res->res.end; + if (ranged_conflict(&start, &end, dev_start, dev_end)) + return 1; + } + } + return 0; +} +EXPORT_SYMBOL(pnp_range_reserved); + +/* format is: pnp_reserve_irq=irq1[,irq2] .... */ +static int __init pnp_setup_reserve_irq(char *str) +{ + int i; + + for (i = 0; i < 16; i++) + if (get_option(&str, &pnp_reserve_irq[i]) != 2) + break; + return 1; +} + +__setup("pnp_reserve_irq=", pnp_setup_reserve_irq); + +/* format is: pnp_reserve_dma=dma1[,dma2] .... */ +static int __init pnp_setup_reserve_dma(char *str) +{ + int i; + + for (i = 0; i < 8; i++) + if (get_option(&str, &pnp_reserve_dma[i]) != 2) + break; + return 1; +} + +__setup("pnp_reserve_dma=", pnp_setup_reserve_dma); + +/* format is: pnp_reserve_io=io1,size1[,io2,size2] .... */ +static int __init pnp_setup_reserve_io(char *str) +{ + int i; + + for (i = 0; i < 16; i++) + if (get_option(&str, &pnp_reserve_io[i]) != 2) + break; + return 1; +} + +__setup("pnp_reserve_io=", pnp_setup_reserve_io); + +/* format is: pnp_reserve_mem=mem1,size1[,mem2,size2] .... */ +static int __init pnp_setup_reserve_mem(char *str) +{ + int i; + + for (i = 0; i < 16; i++) + if (get_option(&str, &pnp_reserve_mem[i]) != 2) + break; + return 1; +} + +__setup("pnp_reserve_mem=", pnp_setup_reserve_mem); diff --git a/drivers/pnp/support.c b/drivers/pnp/support.c new file mode 100644 index 000000000..f6c866851 --- /dev/null +++ b/drivers/pnp/support.c @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * support.c - standard functions for the use of pnp protocol drivers + * + * Copyright 2003 Adam Belay + * Copyright (C) 2008 Hewlett-Packard Development Company, L.P. + * Bjorn Helgaas + */ + +#include +#include +#include +#include +#include "base.h" + +/** + * pnp_is_active - Determines if a device is active based on its current + * resources + * @dev: pointer to the desired PnP device + */ +int pnp_is_active(struct pnp_dev *dev) +{ + /* + * I don't think this is very reliable because pnp_disable_dev() + * only clears out auto-assigned resources. + */ + if (!pnp_port_start(dev, 0) && pnp_port_len(dev, 0) <= 1 && + !pnp_mem_start(dev, 0) && pnp_mem_len(dev, 0) <= 1 && + pnp_irq(dev, 0) == -1 && pnp_dma(dev, 0) == -1) + return 0; + else + return 1; +} +EXPORT_SYMBOL(pnp_is_active); + +/* + * Functionally similar to acpi_ex_eisa_id_to_string(), but that's + * buried in the ACPI CA, and we can't depend on it being present. + */ +void pnp_eisa_id_to_string(u32 id, char *str) +{ + id = be32_to_cpu(id); + + /* + * According to the specs, the first three characters are five-bit + * compressed ASCII, and the left-over high order bit should be zero. + * However, the Linux ISAPNP code historically used six bits for the + * first character, and there seem to be IDs that depend on that, + * e.g., "nEC8241" in the Linux 8250_pnp serial driver and the + * FreeBSD sys/pc98/cbus/sio_cbus.c driver. + */ + str[0] = 'A' + ((id >> 26) & 0x3f) - 1; + str[1] = 'A' + ((id >> 21) & 0x1f) - 1; + str[2] = 'A' + ((id >> 16) & 0x1f) - 1; + str[3] = hex_asc_hi(id >> 8); + str[4] = hex_asc_lo(id >> 8); + str[5] = hex_asc_hi(id); + str[6] = hex_asc_lo(id); + str[7] = '\0'; +} + +char *pnp_resource_type_name(struct resource *res) +{ + switch (pnp_resource_type(res)) { + case IORESOURCE_IO: + return "io"; + case IORESOURCE_MEM: + return "mem"; + case IORESOURCE_IRQ: + return "irq"; + case IORESOURCE_DMA: + return "dma"; + case IORESOURCE_BUS: + return "bus"; + } + return "unknown"; +} + +void dbg_pnp_show_resources(struct pnp_dev *dev, char *desc) +{ + struct pnp_resource *pnp_res; + + if (list_empty(&dev->resources)) + pnp_dbg(&dev->dev, "%s: no current resources\n", desc); + else { + pnp_dbg(&dev->dev, "%s: current resources:\n", desc); + list_for_each_entry(pnp_res, &dev->resources, list) + pnp_dbg(&dev->dev, "%pr\n", &pnp_res->res); + } +} + +char *pnp_option_priority_name(struct pnp_option *option) +{ + switch (pnp_option_priority(option)) { + case PNP_RES_PRIORITY_PREFERRED: + return "preferred"; + case PNP_RES_PRIORITY_ACCEPTABLE: + return "acceptable"; + case PNP_RES_PRIORITY_FUNCTIONAL: + return "functional"; + } + return "invalid"; +} + +void dbg_pnp_show_option(struct pnp_dev *dev, struct pnp_option *option) +{ + char buf[128]; + int len = 0, i; + struct pnp_port *port; + struct pnp_mem *mem; + struct pnp_irq *irq; + struct pnp_dma *dma; + + if (pnp_option_is_dependent(option)) + len += scnprintf(buf + len, sizeof(buf) - len, + " dependent set %d (%s) ", + pnp_option_set(option), + pnp_option_priority_name(option)); + else + len += scnprintf(buf + len, sizeof(buf) - len, + " independent "); + + switch (option->type) { + case IORESOURCE_IO: + port = &option->u.port; + len += scnprintf(buf + len, sizeof(buf) - len, "io min %#llx " + "max %#llx align %lld size %lld flags %#x", + (unsigned long long) port->min, + (unsigned long long) port->max, + (unsigned long long) port->align, + (unsigned long long) port->size, port->flags); + break; + case IORESOURCE_MEM: + mem = &option->u.mem; + len += scnprintf(buf + len, sizeof(buf) - len, "mem min %#llx " + "max %#llx align %lld size %lld flags %#x", + (unsigned long long) mem->min, + (unsigned long long) mem->max, + (unsigned long long) mem->align, + (unsigned long long) mem->size, mem->flags); + break; + case IORESOURCE_IRQ: + irq = &option->u.irq; + len += scnprintf(buf + len, sizeof(buf) - len, "irq"); + if (bitmap_empty(irq->map.bits, PNP_IRQ_NR)) + len += scnprintf(buf + len, sizeof(buf) - len, + " "); + else { + for (i = 0; i < PNP_IRQ_NR; i++) + if (test_bit(i, irq->map.bits)) + len += scnprintf(buf + len, + sizeof(buf) - len, + " %d", i); + } + len += scnprintf(buf + len, sizeof(buf) - len, " flags %#x", + irq->flags); + if (irq->flags & IORESOURCE_IRQ_OPTIONAL) + len += scnprintf(buf + len, sizeof(buf) - len, + " (optional)"); + break; + case IORESOURCE_DMA: + dma = &option->u.dma; + len += scnprintf(buf + len, sizeof(buf) - len, "dma"); + if (!dma->map) + len += scnprintf(buf + len, sizeof(buf) - len, + " "); + else { + for (i = 0; i < 8; i++) + if (dma->map & (1 << i)) + len += scnprintf(buf + len, + sizeof(buf) - len, + " %d", i); + } + len += scnprintf(buf + len, sizeof(buf) - len, " (bitmask %#x) " + "flags %#x", dma->map, dma->flags); + break; + } + pnp_dbg(&dev->dev, "%s\n", buf); +} diff --git a/drivers/pnp/system.c b/drivers/pnp/system.c new file mode 100644 index 000000000..bd939b564 --- /dev/null +++ b/drivers/pnp/system.c @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * system.c - a driver for reserving pnp system resources + * + * Some code is based on pnpbios_core.c + * Copyright 2002 Adam Belay + * (c) Copyright 2007 Hewlett-Packard Development Company, L.P. + * Bjorn Helgaas + */ + +#include +#include +#include +#include +#include +#include + +static const struct pnp_device_id pnp_dev_table[] = { + /* General ID for reserving resources */ + { .id = "PNP0c02" }, + /* memory controller */ + { .id = "PNP0c01" }, + { } +}; + +static void reserve_range(struct pnp_dev *dev, struct resource *r, int port) +{ + char *regionid; + const char *pnpid = dev_name(&dev->dev); + resource_size_t start = r->start, end = r->end; + struct resource *res; + + regionid = kmalloc(16, GFP_KERNEL); + if (!regionid) + return; + + snprintf(regionid, 16, "pnp %s", pnpid); + if (port) + res = request_region(start, end - start + 1, regionid); + else + res = request_mem_region(start, end - start + 1, regionid); + if (res) + res->flags &= ~IORESOURCE_BUSY; + else + kfree(regionid); + + /* + * Failures at this point are usually harmless. pci quirks for + * example do reserve stuff they know about too, so we may well + * have double reservations. + */ + dev_info(&dev->dev, "%pR %s reserved\n", r, + res ? "has been" : "could not be"); +} + +static void reserve_resources_of_dev(struct pnp_dev *dev) +{ + struct resource *res; + int i; + + for (i = 0; (res = pnp_get_resource(dev, IORESOURCE_IO, i)); i++) { + if (res->flags & IORESOURCE_DISABLED) + continue; + if (res->start == 0) + continue; /* disabled */ + if (res->start < 0x100) + /* + * Below 0x100 is only standard PC hardware + * (pics, kbd, timer, dma, ...) + * We should not get resource conflicts there, + * and the kernel reserves these anyway + * (see arch/i386/kernel/setup.c). + * So, do nothing + */ + continue; + if (res->end < res->start) + continue; /* invalid */ + + reserve_range(dev, res, 1); + } + + for (i = 0; (res = pnp_get_resource(dev, IORESOURCE_MEM, i)); i++) { + if (res->flags & IORESOURCE_DISABLED) + continue; + + reserve_range(dev, res, 0); + } +} + +static int system_pnp_probe(struct pnp_dev *dev, + const struct pnp_device_id *dev_id) +{ + reserve_resources_of_dev(dev); + return 0; +} + +static struct pnp_driver system_pnp_driver = { + .name = "system", + .id_table = pnp_dev_table, + .flags = PNP_DRIVER_RES_DO_NOT_CHANGE, + .probe = system_pnp_probe, +}; + +static int __init pnp_system_init(void) +{ + return pnp_register_driver(&system_pnp_driver); +} + +/* + * Reserve motherboard resources after PCI claim BARs, + * but before PCI assign resources for uninitialized PCI devices + */ +fs_initcall(pnp_system_init); -- cgit v1.3.1