xref: /freebsd/sys/dev/imcsmb/imcsmb_pci.c (revision c66ec88fed842fbaad62c30d510644ceb7bd2d71)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Authors: Joe Kloss; Ravi Pokala (rpokala@freebsd.org)
5  *
6  * Copyright (c) 2017-2018 Panasas
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  *
29  * $FreeBSD$
30  */
31 
32 #include <sys/param.h>
33 #include <sys/systm.h>
34 #include <sys/kernel.h>
35 #include <sys/module.h>
36 #include <sys/endian.h>
37 #include <sys/errno.h>
38 #include <sys/lock.h>
39 #include <sys/mutex.h>
40 #include <sys/syslog.h>
41 #include <sys/bus.h>
42 
43 #include <machine/bus.h>
44 #include <machine/atomic.h>
45 
46 #include <dev/pci/pcivar.h>
47 #include <dev/pci/pcireg.h>
48 
49 #include <dev/smbus/smbconf.h>
50 
51 #include "imcsmb_reg.h"
52 #include "imcsmb_var.h"
53 
54 /* (Sandy,Ivy)bridge-Xeon and (Has,Broad)well-Xeon CPUs contain one or two
55  * "Integrated Memory Controllers" (iMCs), and each iMC contains two separate
56  * SMBus controllers. These are used for reading SPD data from the DIMMs, and
57  * for reading the "Thermal Sensor on DIMM" (TSODs). The iMC SMBus controllers
58  * are very simple devices, and have limited functionality compared to
59  * full-fledged SMBus controllers, like the one in Intel ICHs and PCHs.
60  *
61  * The publicly available documentation for the iMC SMBus controllers can be
62  * found in the CPU datasheets for (Sandy,Ivy)bridge-Xeon and
63  * (Has,broad)well-Xeon, respectively:
64  *
65  * https://www.intel.com/content/dam/www/public/us/en/documents/datasheets/
66  *      Sandybridge     xeon-e5-1600-2600-vol-2-datasheet.pdf
67  *      Ivybridge       xeon-e5-v2-datasheet-vol-2.pdf
68  *      Haswell         xeon-e5-v3-datasheet-vol-2.pdf
69  *      Broadwell       xeon-e5-v4-datasheet-vol-2.pdf
70  *
71  * Another useful resource is the Linux driver. It is not in the main tree.
72  *
73  * https://www.mail-archive.com/linux-kernel@vger.kernel.org/msg840043.html
74  *
75  * The iMC SMBus controllers do not support interrupts (thus, they must be
76  * polled for IO completion). All of the iMC registers are in PCI configuration
77  * space; there is no support for PIO or MMIO. As a result, this driver does
78  * not need to perform and newbus resource manipulation.
79  *
80  * Because there are multiple SMBus controllers sharing the same PCI device,
81  * this driver is actually *two* drivers:
82  *
83  * - "imcsmb" is an smbus(4)-compliant SMBus controller driver
84  *
85  * - "imcsmb_pci" recognizes the PCI device and assigns the appropriate set of
86  *    PCI config registers to a specific "imcsmb" instance.
87  */
88 
89 /* Depending on the motherboard and firmware, the TSODs might be polled by
90  * firmware. Therefore, when this driver accesses these SMBus controllers, the
91  * firmware polling must be disabled as part of requesting the bus, and
92  * re-enabled when releasing the bus. Unfortunately, the details of how to do
93  * this are vendor-specific. Contact your motherboard vendor to get the
94  * information you need to do proper implementations.
95  *
96  * For NVDIMMs which conform to the ACPI "NFIT" standard, the ACPI firmware
97  * manages the NVDIMM; for those which pre-date the standard, the operating
98  * system interacts with the NVDIMM controller using a vendor-proprietary API
99  * over the SMBus. In that case, the NVDIMM driver would be an SMBus slave
100  * device driver, and would interface with the hardware via an SMBus controller
101  * driver such as this one.
102  */
103 
104 /* PCIe device IDs for (Sandy,Ivy)bridge)-Xeon and (Has,Broad)well-Xeon */
105 #define PCI_VENDOR_INTEL		0x8086
106 #define IMCSMB_PCI_DEV_ID_IMC0_SBX	0x3ca8
107 #define IMCSMB_PCI_DEV_ID_IMC0_IBX	0x0ea8
108 #define IMCSMB_PCI_DEV_ID_IMC0_HSX	0x2fa8
109 #define IMCSMB_PCI_DEV_ID_IMC0_BDX	0x6fa8
110 /* (Sandy,Ivy)bridge-Xeon only have a single memory controller per socket */
111 #define IMCSMB_PCI_DEV_ID_IMC1_HSX	0x2f68
112 #define IMCSMB_PCI_DEV_ID_IMC1_BDX	0x6f68
113 
114 /* There are two SMBus controllers in each device. These define the registers
115  * for each of these devices.
116  */
117 static struct imcsmb_reg_set imcsmb_regs[] = {
118 	{
119 		.smb_stat = IMCSMB_REG_STATUS0,
120 		.smb_cmd = IMCSMB_REG_COMMAND0,
121 		.smb_cntl = IMCSMB_REG_CONTROL0
122 	},
123 	{
124 		.smb_stat = IMCSMB_REG_STATUS1,
125 		.smb_cmd = IMCSMB_REG_COMMAND1,
126 		.smb_cntl = IMCSMB_REG_CONTROL1
127 	},
128 };
129 
130 static struct imcsmb_pci_device {
131 	uint16_t	id;
132 	char		*name;
133 } imcsmb_pci_devices[] = {
134 	{IMCSMB_PCI_DEV_ID_IMC0_SBX,
135 	    "Intel Sandybridge Xeon iMC 0 SMBus controllers"	},
136 	{IMCSMB_PCI_DEV_ID_IMC0_IBX,
137 	    "Intel Ivybridge Xeon iMC 0 SMBus controllers"	},
138 	{IMCSMB_PCI_DEV_ID_IMC0_HSX,
139 	    "Intel Haswell Xeon iMC 0 SMBus controllers"	},
140 	{IMCSMB_PCI_DEV_ID_IMC1_HSX,
141 	    "Intel Haswell Xeon iMC 1 SMBus controllers"	},
142 	{IMCSMB_PCI_DEV_ID_IMC0_BDX,
143 	    "Intel Broadwell Xeon iMC 0 SMBus controllers"	},
144 	{IMCSMB_PCI_DEV_ID_IMC1_BDX,
145 	    "Intel Broadwell Xeon iMC 1 SMBus controllers"	},
146 	{0, NULL},
147 };
148 
149 /* Device methods. */
150 static int imcsmb_pci_attach(device_t dev);
151 static int imcsmb_pci_detach(device_t dev);
152 static int imcsmb_pci_probe(device_t dev);
153 
154 /**
155  * device_attach() method. Set up the PCI device's softc, then explicitly create
156  * children for the actual imcsmbX controllers. Set up the child's ivars to
157  * point to the proper set of the PCI device's config registers.
158  *
159  * @author Joe Kloss, rpokala
160  *
161  * @param[in,out] dev
162  *      Device being attached.
163  */
164 static int
165 imcsmb_pci_attach(device_t dev)
166 {
167 	struct imcsmb_pci_softc *sc;
168 	device_t child;
169 	int rc;
170 	int unit;
171 
172 	/* Initialize private state */
173 	sc = device_get_softc(dev);
174 	sc->dev = dev;
175 	sc->semaphore = 0;
176 
177 	/* Create the imcsmbX children */
178 	for (unit = 0; unit < 2; unit++) {
179 		child = device_add_child(dev, "imcsmb", -1);
180 		if (child == NULL) {
181 			/* Nothing has been allocated, so there's no cleanup. */
182 			device_printf(dev, "Child imcsmb not added\n");
183 			rc = ENXIO;
184 			goto out;
185 		}
186 		/* Set the child's ivars to point to the appropriate set of
187 		 * the PCI device's registers.
188 		 */
189 		device_set_ivars(child, &imcsmb_regs[unit]);
190 	}
191 
192 	/* Attach the imcsmbX children. */
193 	if ((rc = bus_generic_attach(dev)) != 0) {
194 		device_printf(dev, "failed to attach children: %d\n", rc);
195 		goto out;
196 	}
197 
198 out:
199 	return (rc);
200 }
201 
202 /**
203  * device_detach() method. attach() didn't do any allocations, so all that's
204  * needed here is to free up any downstream drivers and children.
205  *
206  * @author Joe Kloss
207  *
208  * @param[in] dev
209  *      Device being detached.
210  */
211 static int
212 imcsmb_pci_detach(device_t dev)
213 {
214 	int rc;
215 
216 	/* Detach any attached drivers */
217 	rc = bus_generic_detach(dev);
218 	if (rc == 0) {
219 		/* Remove all children */
220 		rc = device_delete_children(dev);
221 	}
222 
223 	return (rc);
224 }
225 
226 /**
227  * device_probe() method. Look for the right PCI vendor/device IDs.
228  *
229  * @author Joe Kloss, rpokala
230  *
231  * @param[in,out] dev
232  *      Device being probed.
233  */
234 static int
235 imcsmb_pci_probe(device_t dev)
236 {
237 	struct imcsmb_pci_device *pci_device;
238 	int rc;
239 	uint16_t pci_dev_id;
240 
241 	rc = ENXIO;
242 
243 	if (pci_get_vendor(dev) != PCI_VENDOR_INTEL) {
244 		goto out;
245 	}
246 
247 	pci_dev_id = pci_get_device(dev);
248 	for (pci_device = imcsmb_pci_devices;
249 	    pci_device->name != NULL;
250 	    pci_device++) {
251 		if (pci_dev_id == pci_device->id) {
252 			device_set_desc(dev, pci_device->name);
253 			rc = BUS_PROBE_DEFAULT;
254 			goto out;
255 		}
256 	}
257 
258 out:
259 	return (rc);
260 }
261 
262 /**
263  * Invoked via smbus_callback() -> imcsmb_callback(); clear the semaphore, and
264  * re-enable motherboard-specific DIMM temperature monitoring if needed. This
265  * gets called after the transaction completes.
266  *
267  * @author Joe Kloss
268  *
269  * @param[in,out] dev
270  *      The device whose busses to release.
271  */
272 void
273 imcsmb_pci_release_bus(device_t dev)
274 {
275 	struct imcsmb_pci_softc *sc;
276 
277 	sc = device_get_softc(dev);
278 
279 	/*
280 	 * IF NEEDED, INSERT MOTHERBOARD-SPECIFIC CODE TO RE-ENABLE DIMM
281 	 * TEMPERATURE MONITORING HERE.
282 	 */
283 
284 	atomic_store_rel_int(&sc->semaphore, 0);
285 }
286 
287 /**
288  * Invoked via smbus_callback() -> imcsmb_callback(); set the semaphore, and
289  * disable motherboard-specific DIMM temperature monitoring if needed. This gets
290  * called before the transaction starts.
291  *
292  * @author Joe Kloss
293  *
294  * @param[in,out] dev
295  *      The device whose busses to request.
296  */
297 int
298 imcsmb_pci_request_bus(device_t dev)
299 {
300 	struct imcsmb_pci_softc *sc;
301 	int rc;
302 
303 	sc = device_get_softc(dev);
304 	rc = 0;
305 
306 	/* We don't want to block. Use a simple test-and-set semaphore to
307 	 * protect the bus.
308 	 */
309 	if (atomic_cmpset_acq_int(&sc->semaphore, 0, 1) == 0) {
310 		rc = EWOULDBLOCK;
311 	}
312 
313 	/*
314 	 * IF NEEDED, INSERT MOTHERBOARD-SPECIFIC CODE TO DISABLE DIMM
315 	 * TEMPERATURE MONITORING HERE.
316 	 */
317 
318 	return (rc);
319 }
320 
321 /* Our device class */
322 static devclass_t imcsmb_pci_devclass;
323 
324 /* Device methods */
325 static device_method_t imcsmb_pci_methods[] = {
326 	/* Device interface */
327 	DEVMETHOD(device_attach,	imcsmb_pci_attach),
328 	DEVMETHOD(device_detach,	imcsmb_pci_detach),
329 	DEVMETHOD(device_probe,		imcsmb_pci_probe),
330 
331 	DEVMETHOD_END
332 };
333 
334 static driver_t imcsmb_pci_driver = {
335 	.name = "imcsmb_pci",
336 	.methods = imcsmb_pci_methods,
337 	.size = sizeof(struct imcsmb_pci_softc),
338 };
339 
340 DRIVER_MODULE(imcsmb_pci, pci, imcsmb_pci_driver, imcsmb_pci_devclass, 0, 0);
341 MODULE_DEPEND(imcsmb_pci, pci, 1, 1, 1);
342 MODULE_VERSION(imcsmb_pci, 1);
343 
344 /* vi: set ts=8 sw=4 sts=8 noet: */
345