1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Primary to Sideband (P2SB) bridge access support
4 *
5 * Copyright (c) 2017, 2021-2022 Intel Corporation.
6 *
7 * Authors: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
8 * Jonathan Yong <jonathan.yong@intel.com>
9 */
10
11 #include <linux/bits.h>
12 #include <linux/export.h>
13 #include <linux/pci.h>
14 #include <linux/platform_data/x86/p2sb.h>
15
16 #include <asm/cpu_device_id.h>
17 #include <asm/intel-family.h>
18
19 #define P2SBC 0xe0
20 #define P2SBC_HIDE BIT(8)
21
22 #define P2SB_DEVFN_DEFAULT PCI_DEVFN(31, 1)
23
24 static const struct x86_cpu_id p2sb_cpu_ids[] = {
25 X86_MATCH_INTEL_FAM6_MODEL(ATOM_GOLDMONT, PCI_DEVFN(13, 0)),
26 {}
27 };
28
p2sb_get_devfn(unsigned int * devfn)29 static int p2sb_get_devfn(unsigned int *devfn)
30 {
31 unsigned int fn = P2SB_DEVFN_DEFAULT;
32 const struct x86_cpu_id *id;
33
34 id = x86_match_cpu(p2sb_cpu_ids);
35 if (id)
36 fn = (unsigned int)id->driver_data;
37
38 *devfn = fn;
39 return 0;
40 }
41
42 /* Copy resource from the first BAR of the device in question */
p2sb_read_bar0(struct pci_dev * pdev,struct resource * mem)43 static int p2sb_read_bar0(struct pci_dev *pdev, struct resource *mem)
44 {
45 struct resource *bar0 = &pdev->resource[0];
46
47 /* Make sure we have no dangling pointers in the output */
48 memset(mem, 0, sizeof(*mem));
49
50 /*
51 * We copy only selected fields from the original resource.
52 * Because a PCI device will be removed soon, we may not use
53 * any allocated data, hence we may not copy any pointers.
54 */
55 mem->start = bar0->start;
56 mem->end = bar0->end;
57 mem->flags = bar0->flags;
58 mem->desc = bar0->desc;
59
60 return 0;
61 }
62
p2sb_scan_and_read(struct pci_bus * bus,unsigned int devfn,struct resource * mem)63 static int p2sb_scan_and_read(struct pci_bus *bus, unsigned int devfn, struct resource *mem)
64 {
65 struct pci_dev *pdev;
66 int ret;
67
68 pdev = pci_scan_single_device(bus, devfn);
69 if (!pdev)
70 return -ENODEV;
71
72 ret = p2sb_read_bar0(pdev, mem);
73
74 pci_stop_and_remove_bus_device(pdev);
75 return ret;
76 }
77
78 /**
79 * p2sb_bar - Get Primary to Sideband (P2SB) bridge device BAR
80 * @bus: PCI bus to communicate with
81 * @devfn: PCI slot and function to communicate with
82 * @mem: memory resource to be filled in
83 *
84 * The BIOS prevents the P2SB device from being enumerated by the PCI
85 * subsystem, so we need to unhide and hide it back to lookup the BAR.
86 *
87 * if @bus is NULL, the bus 0 in domain 0 will be used.
88 * If @devfn is 0, it will be replaced by devfn of the P2SB device.
89 *
90 * Caller must provide a valid pointer to @mem.
91 *
92 * Locking is handled by pci_rescan_remove_lock mutex.
93 *
94 * Return:
95 * 0 on success or appropriate errno value on error.
96 */
p2sb_bar(struct pci_bus * bus,unsigned int devfn,struct resource * mem)97 int p2sb_bar(struct pci_bus *bus, unsigned int devfn, struct resource *mem)
98 {
99 struct pci_dev *pdev_p2sb;
100 unsigned int devfn_p2sb;
101 u32 value = P2SBC_HIDE;
102 int ret;
103
104 /* Get devfn for P2SB device itself */
105 ret = p2sb_get_devfn(&devfn_p2sb);
106 if (ret)
107 return ret;
108
109 /* if @bus is NULL, use bus 0 in domain 0 */
110 bus = bus ?: pci_find_bus(0, 0);
111
112 /*
113 * Prevent concurrent PCI bus scan from seeing the P2SB device and
114 * removing via sysfs while it is temporarily exposed.
115 */
116 pci_lock_rescan_remove();
117
118 /* Unhide the P2SB device, if needed */
119 pci_bus_read_config_dword(bus, devfn_p2sb, P2SBC, &value);
120 if (value & P2SBC_HIDE)
121 pci_bus_write_config_dword(bus, devfn_p2sb, P2SBC, 0);
122
123 pdev_p2sb = pci_scan_single_device(bus, devfn_p2sb);
124 if (devfn)
125 ret = p2sb_scan_and_read(bus, devfn, mem);
126 else
127 ret = p2sb_read_bar0(pdev_p2sb, mem);
128 pci_stop_and_remove_bus_device(pdev_p2sb);
129
130 /* Hide the P2SB device, if it was hidden */
131 if (value & P2SBC_HIDE)
132 pci_bus_write_config_dword(bus, devfn_p2sb, P2SBC, P2SBC_HIDE);
133
134 pci_unlock_rescan_remove();
135
136 if (ret)
137 return ret;
138
139 if (mem->flags == 0)
140 return -ENODEV;
141
142 return 0;
143 }
144 EXPORT_SYMBOL_GPL(p2sb_bar);
145