// SPDX-License-Identifier: GPL-2.0+
/*
* Compaq Hot Plug Controller Driver
*
* Copyright (C) 1995,2001 Compaq Computer Corporation
* Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com)
* Copyright (C) 2001 IBM Corp.
*
* All rights reserved.
*
* Send feedback to <greg@kroah.com>
*
*/
#define pr_fmt(fmt) "cpqphp: " fmt
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/printk.h>
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/proc_fs.h>
#include <linux/pci.h>
#include <linux/pci_hotplug.h>
#include "../pci.h"
#include "cpqphp.h"
#include "cpqphp_nvram.h"
u8 cpqhp_nic_irq;
u8 cpqhp_disk_irq;
static u16 unused_IRQ;
/*
* detect_HRT_floating_pointer
*
* find the Hot Plug Resource Table in the specified region of memory.
*
*/
static void __iomem *detect_HRT_floating_pointer(void __iomem *begin, void __iomem *end)
{
void __iomem *fp;
void __iomem *endp;
u8 temp1, temp2, temp3, temp4;
int status = 0;
endp = (end - sizeof(struct hrt) + 1);
for (fp = begin; fp <= endp; fp += 16) {
temp1 = readb(fp + SIG0);
temp2 = readb(fp + SIG1);
temp3 = readb(fp + SIG2);
temp4 = readb(fp + SIG3);
if (temp1 == '$' &&
temp2 == 'H' &&
temp3 == 'R' &&
temp4 == 'T') {
status = 1;
break;
}
}
if (!status)
fp = NULL;
dbg("Discovered Hotplug Resource Table at %p\n", fp);
return fp;
}
int cpqhp_configure_device(struct controller *ctrl, struct pci_func *func)
{
struct pci_bus *child;
int num;
pci_lock_rescan_remove();
if (func->pci_dev == NULL)
func->pci_dev = pci_get_domain_bus_and_slot(0, func->bus,
PCI_DEVFN(func->device,
func->function));
/* No pci device, we need to create it then */
if (func->pci_dev == NULL) {
dbg("INFO: pci_dev still null\n");
num = pci_scan_slot(ctrl->pci_dev->bus, PCI_DEVFN(func->device, func->function));
if (num)
pci_bus_add_devices(ctrl->pci_dev->bus);
func->pci_dev = pci_get_domain_bus_and_slot(0, func->bus,
PCI_DEVFN(func->device,
func->function));
if (func->pci_dev == NULL) {
dbg("ERROR: pci_dev still null\n");
goto out;
}
}
if (func->pci_dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) {
pci_hp_add_bridge(func->pci_dev);
child = func->pci_dev->subordinate;
if (child)
pci_bus_add_devices(child);
}
pci_dev_put(func->pci_dev);
out:
pci_unlock_rescan_remove();
return 0;
}
int cpqhp_unconfigure_device(struct pci_func *func)
{
int j;
dbg("%s: bus/dev/func = %x/%x/%x\n", __func__, func->bus, func->device, func->function);
pci_lock_rescan_remove();
for (j = 0; j < 8 ; j++) {
struct pci_dev *temp = pci_get_domain_bus_and_slot(0,
func->bus,
PCI_DEVFN(func->device,
j));
if (temp) {
pci_dev_put(temp);
pci_stop_and_remove_bus_device(temp);
}
}
pci_unlock_rescan_remove();
return 0;
}
/*
* cpqhp_set_irq
*
* @bus_num: bus number of PCI device
* @dev_num: device number of PCI device
* @slot: pointer to u8 where slot number will be returned
*/
int cpqhp_set_irq(u8 bus_num, u8 dev_num, u8 int_pin, u8 irq_num)
{
int rc = 0;
if (cpqhp_legacy_mode) {
struct pci_dev *fakedev;
struct pci_bus *fakebus;
u16 temp_word;
fakedev = kmalloc_obj(*fakedev);
fakebus = kmalloc_obj(*fakebus);
if (!fakedev || !fakebus) {
kfree(fakedev);
kfree(fakebus);
return -ENOMEM;
}
fakedev->devfn = dev_num << 3;
fakedev->bus = fakebus;
fakebus->number = bus_num;
dbg("%s: dev %d, bus %d, pin %d, num %d\n",
__func__, dev_num, bus_num, int_pin, irq_num);
rc = pcibios_set_irq_routing(fakedev, int_pin - 1, irq_num);
kfree(fakedev);
kfree(fakebus);
dbg("%s: rc %d\n", __func__, rc);
if (!rc)
return !rc;
/* set the Edge Level Control Register (ELCR) */
temp_word = inb(0x4d0);
temp_word |= inb(0x4d1) << 8;
temp_word |= 0x01 << irq_num;
/* This should only be for x86 as it sets the Edge Level
* Control Register
*/
outb((u8)(temp_word & 0xFF), 0x4d0);
outb((u8)((temp_word & 0xFF00) >> 8), 0x4d1);
rc = 0;
}
return rc;
}
static int PCI_ScanBusForNonBridge(struct controller *ctrl, u8 bus_num, u8 *dev_num)
{
u16 tdevice;
u32 work;
int ret = -1;
ctrl->pci_bus->number = bus_num;
for (tdevice = 0; tdevice < 0xFF; tdevice++) {
/* Scan for access first */
if (!pci_bus_read_dev_vendor_id(ctrl->pci_bus, tdevice, &work, 0))
continue;
ret = pci_bus_read_config_dword(ctrl->pci_bus, tdevice, PCI_CLASS_REVISION, &work);
if (ret)
continue;
dbg("Looking for nonbridge bus_num %d dev_num %d\n", bus_num, tdevice);
/* Yep we got one. Not a bridge ? */
if ((work >> 8) != PCI_TO_PCI_BRIDGE_CLASS) {
*dev_num = tdevice;
dbg("found it !\n");
return 0;
} else {
/*
* XXX: Code whose debug printout indicated
* recursion to buses underneath bridges might be
* necessary was removed because it never did
* any recursion.
*/
ret = 0;
pr_warn("missing feature: bridge scan recursion not implemented\n");
}
}
return ret;
}
static int PCI_GetBusDevHelper(struct controller *ctrl, u8 *bus_num, u8 *dev_num, u8 slot, u8 nobridge)
{
int loop, len;
u32 work;
u8 tbus, tdevice, tslot;
len = cpqhp_routing_table_length();
for (loop = 0; loop < len; ++loop) {
tbus = cpqhp_routing_table->slots[loop].bus;
tdevice = cpqhp_routing_table->slots[loop].devfn;
tslot = cpqhp_routing_table->slots[loop].slot;
if (tslot == slot) {
*bus_num = tbus;
*dev_num = tdevice;
ctrl->pci_bus->number = tbus;
pci_bus_read_config_dword(ctrl->pci_bus, *dev_num, PCI_VENDOR_ID, &work);
if (!nobridge || PCI_POSSIBLE_ERROR(work))
return 0;
dbg("bus_num %d devfn %d\n", *bus_num, *dev_num);
pci_bus_read_config_dword(ctrl->pci_bus, *dev_num, PCI_CLASS_REVISION, &work);
dbg("work >> 8 (%x) = BRIDGE (%x)\n", work >> 8, PCI_TO_PCI_BRIDGE_CLASS);
if ((work >> 8) == PCI_TO_PCI_BRIDGE_CLASS) {
pci_bus_read_config_byte(ctrl->pci_bus, *dev_num, PCI_SECONDARY_BUS, &tbus);
dbg("Scan bus for Non Bridge: bus %d\n", tbus);
if (PCI_ScanBusForNonBridge(ctrl, tbus, dev_num) == 0) {
*bus_num = tbus;
return 0;
}
} else
return 0;
}
}
return -1;
}
int cpqhp_get_bus_dev(struct controller *ctrl, u8 *bus_num, u8 *dev_num, u8 slot)
{
/* plain (bridges allowed) */
return PCI_GetBusDevHelper(ctrl, bus_num, dev_num, slot, 0);
}
/* More PCI configuration routines; this time centered around hotplug
* controller
*/
/*
* cpqhp_save_config
*
* Reads configuration for all slots in a PCI bus and saves info.
*
|