xref: /haiku/src/libs/compat/freebsd_network/pci.cpp (revision 52f7c9389475e19fc21487b38064b4390eeb6fea)
1 /*
2  * Copyright 2022, Haiku, Inc. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 extern "C" {
7 #include "device.h"
8 
9 #include <compat/machine/resource.h>
10 #include <compat/dev/pci/pcireg.h>
11 #include <compat/dev/pci/pcivar.h>
12 }
13 
14 #include <PCI.h>
15 #include <PCI_x86.h>
16 
17 
18 //#define DEBUG_PCI
19 #ifdef DEBUG_PCI
20 #	define TRACE_PCI(dev, format, args...) device_printf(dev, format , ##args)
21 #else
22 #	define TRACE_PCI(dev, format, args...) do { } while (0)
23 #endif
24 
25 
26 pci_module_info *gPci;
27 struct pci_x86_module_info *gPCIx86;
28 
29 
30 status_t
31 init_pci()
32 {
33 	if (gPci != NULL)
34 		return B_OK;
35 
36 	status_t status = get_module(B_PCI_MODULE_NAME, (module_info **)&gPci);
37 	if (status != B_OK)
38 		return status;
39 
40 	// if it fails we just don't support x86 specific features (like MSIs)
41 	if (get_module(B_PCI_X86_MODULE_NAME, (module_info **)&gPCIx86) != B_OK)
42 		gPCIx86 = NULL;
43 
44 	return B_OK;
45 }
46 
47 
48 void
49 uninit_pci()
50 {
51 	if (gPci != NULL)
52 		put_module(B_PCI_MODULE_NAME);
53 	if (gPCIx86 != NULL)
54 		put_module(B_PCI_X86_MODULE_NAME);
55 }
56 
57 
58 pci_info*
59 get_device_pci_info(device_t device)
60 {
61 	struct root_device_softc* root_softc = (struct root_device_softc*)device->root->softc;
62 	if (root_softc->bus != root_device_softc::BUS_pci)
63 		return NULL;
64 	return &root_softc->pci_info;
65 }
66 
67 
68 uint32_t
69 pci_read_config(device_t dev, int offset, int size)
70 {
71 	pci_info* info = get_device_pci_info(dev);
72 
73 	uint32_t value = gPci->read_pci_config(info->bus, info->device,
74 		info->function, offset, size);
75 	TRACE_PCI(dev, "pci_read_config(%i, %i) = 0x%x\n", offset, size, value);
76 	return value;
77 }
78 
79 
80 void
81 pci_write_config(device_t dev, int offset, uint32_t value, int size)
82 {
83 	pci_info* info = get_device_pci_info(dev);
84 
85 	TRACE_PCI(dev, "pci_write_config(%i, 0x%x, %i)\n", offset, value, size);
86 
87 	gPci->write_pci_config(info->bus, info->device, info->function, offset,
88 		size, value);
89 }
90 
91 
92 uint16_t
93 pci_get_vendor(device_t dev)
94 {
95 	return pci_read_config(dev, PCI_vendor_id, 2);
96 }
97 
98 
99 uint16_t
100 pci_get_device(device_t dev)
101 {
102 	return pci_read_config(dev, PCI_device_id, 2);
103 }
104 
105 
106 uint16_t
107 pci_get_subvendor(device_t dev)
108 {
109 	return pci_read_config(dev, PCI_subsystem_vendor_id, 2);
110 }
111 
112 
113 uint16_t
114 pci_get_subdevice(device_t dev)
115 {
116 	return pci_read_config(dev, PCI_subsystem_id, 2);
117 }
118 
119 
120 uint8_t
121 pci_get_revid(device_t dev)
122 {
123 	return pci_read_config(dev, PCI_revision, 1);
124 }
125 
126 
127 uint32_t
128 pci_get_domain(device_t dev)
129 {
130 	return 0;
131 }
132 
133 uint32_t
134 pci_get_devid(device_t dev)
135 {
136 	return pci_read_config(dev, PCI_device_id, 2) << 16 |
137 		pci_read_config(dev, PCI_vendor_id, 2);
138 }
139 
140 uint8_t
141 pci_get_cachelnsz(device_t dev)
142 {
143 	return pci_read_config(dev, PCI_line_size, 1);
144 }
145 
146 uint8_t *
147 pci_get_ether(device_t dev)
148 {
149 	/* used in if_dc to get the MAC from CardBus CIS for Xircom card */
150 	return NULL; /* NULL is handled in the caller correctly */
151 }
152 
153 uint8_t
154 pci_get_bus(device_t dev)
155 {
156 	pci_info *info
157 		= &((struct root_device_softc *)dev->root->softc)->pci_info;
158 	return info->bus;
159 }
160 
161 
162 uint8_t
163 pci_get_slot(device_t dev)
164 {
165 	pci_info *info
166 		= &((struct root_device_softc *)dev->root->softc)->pci_info;
167 	return info->device;
168 }
169 
170 
171 uint8_t
172 pci_get_function(device_t dev)
173 {
174 	pci_info* info = get_device_pci_info(dev);
175 	return info->function;
176 }
177 
178 
179 device_t
180 pci_find_dbsf(uint32_t domain, uint8_t bus, uint8_t slot, uint8_t func)
181 {
182 	// We don't support that yet - if we want to support the multi port
183 	// feature of the Broadcom BCM 570x driver, we would have to change
184 	// that.
185 	return NULL;
186 }
187 
188 
189 static void
190 pci_set_command_bit(device_t dev, uint16_t bit)
191 {
192 	uint16_t command = pci_read_config(dev, PCI_command, 2);
193 	pci_write_config(dev, PCI_command, command | bit, 2);
194 }
195 
196 
197 int
198 pci_enable_busmaster(device_t dev)
199 {
200 	// We do this a bit later than FreeBSD does.
201 	if (pci_get_powerstate(dev) != PCI_POWERSTATE_D0)
202 		pci_set_powerstate(dev, PCI_POWERSTATE_D0);
203 
204 	pci_set_command_bit(dev, PCI_command_master);
205 	return 0;
206 }
207 
208 
209 int
210 pci_enable_io(device_t dev, int space)
211 {
212 	/* adapted from FreeBSD's pci_enable_io_method */
213 	int bit = 0;
214 
215 	switch (space) {
216 		case SYS_RES_IOPORT:
217 			bit = PCI_command_io;
218 			break;
219 		case SYS_RES_MEMORY:
220 			bit = PCI_command_memory;
221 			break;
222 		default:
223 			return EINVAL;
224 	}
225 
226 	pci_set_command_bit(dev, bit);
227 	if (pci_read_config(dev, PCI_command, 2) & bit)
228 		return 0;
229 
230 	device_printf(dev, "pci_enable_io(%d) failed.\n", space);
231 
232 	return ENXIO;
233 }
234 
235 
236 int
237 pci_find_cap(device_t dev, int capability, int *capreg)
238 {
239 	return pci_find_extcap(dev, capability, capreg);
240 }
241 
242 
243 int
244 pci_find_extcap(device_t child, int capability, int *_capabilityRegister)
245 {
246 	uint8 capabilityPointer;
247 	uint8 headerType;
248 	uint16 status;
249 
250 	status = pci_read_config(child, PCIR_STATUS, 2);
251 	if ((status & PCIM_STATUS_CAPPRESENT) == 0)
252 		return ENXIO;
253 
254 	headerType = pci_read_config(child, PCI_header_type, 1);
255 	switch (headerType & PCIM_HDRTYPE) {
256 		case 0:
257 		case 1:
258 			capabilityPointer = PCIR_CAP_PTR;
259 			break;
260 		case 2:
261 			capabilityPointer = PCIR_CAP_PTR_2;
262 			break;
263 		default:
264 			return ENXIO;
265 	}
266 	capabilityPointer = pci_read_config(child, capabilityPointer, 1);
267 
268 	while (capabilityPointer != 0) {
269 		if (pci_read_config(child, capabilityPointer + PCICAP_ID, 1)
270 				== capability) {
271 			if (_capabilityRegister != NULL)
272 				*_capabilityRegister = capabilityPointer;
273 			return 0;
274 		}
275 		capabilityPointer = pci_read_config(child,
276 			capabilityPointer + PCICAP_NEXTPTR, 1);
277 	}
278 
279 	return ENOENT;
280 }
281 
282 
283 int
284 pci_msi_count(device_t dev)
285 {
286 	if (gPCIx86 == NULL)
287 		return 0;
288 
289 	pci_info* info = get_device_pci_info(dev);
290 	return gPCIx86->get_msi_count(info->bus, info->device, info->function);
291 }
292 
293 
294 int
295 pci_alloc_msi(device_t dev, int *count)
296 {
297 	if (gPCIx86 == NULL)
298 		return ENODEV;
299 
300 	pci_info* info = get_device_pci_info(dev);
301 	uint8 startVector = 0;
302 	if (gPCIx86->configure_msi(info->bus, info->device, info->function, *count,
303 			&startVector) != B_OK) {
304 		return ENODEV;
305 	}
306 
307 	((struct root_device_softc *)dev->root->softc)->is_msi = true;
308 	info->u.h0.interrupt_line = startVector;
309 	return EOK;
310 }
311 
312 
313 int
314 pci_release_msi(device_t dev)
315 {
316 	if (gPCIx86 == NULL)
317 		return ENODEV;
318 
319 	pci_info* info = get_device_pci_info(dev);
320 	gPCIx86->unconfigure_msi(info->bus, info->device, info->function);
321 	((struct root_device_softc *)dev->root->softc)->is_msi = false;
322 	((struct root_device_softc *)dev->root->softc)->is_msix = false;
323 	return EOK;
324 }
325 
326 
327 int
328 pci_msix_table_bar(device_t dev)
329 {
330 	pci_info* info = get_device_pci_info(dev);
331 
332 	uint8 capability_offset;
333 	if (gPci->find_pci_capability(info->bus, info->device, info->function,
334 			PCI_cap_id_msix, &capability_offset) != B_OK)
335 		return -1;
336 
337 	uint32 table_value = gPci->read_pci_config(info->bus, info->device, info->function,
338 		capability_offset + PCI_msix_table, 4);
339 
340 	uint32 bar = table_value & PCI_msix_bir_mask;
341 	return PCIR_BAR(bar);
342 }
343 
344 
345 int
346 pci_msix_count(device_t dev)
347 {
348 	if (gPCIx86 == NULL)
349 		return 0;
350 
351 	pci_info* info = get_device_pci_info(dev);
352 	return gPCIx86->get_msix_count(info->bus, info->device, info->function);
353 }
354 
355 
356 int
357 pci_alloc_msix(device_t dev, int *count)
358 {
359 	if (gPCIx86 == NULL)
360 		return ENODEV;
361 
362 	pci_info* info = get_device_pci_info(dev);
363 	uint8 startVector = 0;
364 	if (gPCIx86->configure_msix(info->bus, info->device, info->function, *count,
365 			&startVector) != B_OK) {
366 		return ENODEV;
367 	}
368 
369 	((struct root_device_softc *)dev->root->softc)->is_msix = true;
370 	info->u.h0.interrupt_line = startVector;
371 	return EOK;
372 }
373 
374 
375 int
376 pci_get_max_read_req(device_t dev)
377 {
378 	int cap;
379 	uint16_t val;
380 
381 	if (pci_find_extcap(dev, PCIY_EXPRESS, &cap) != 0)
382 		return (0);
383 	val = pci_read_config(dev, cap + PCIR_EXPRESS_DEVICE_CTL, 2);
384 	val &= PCIM_EXP_CTL_MAX_READ_REQUEST;
385 	val >>= 12;
386 	return (1 << (val + 7));
387 }
388 
389 
390 int
391 pci_set_max_read_req(device_t dev, int size)
392 {
393 	int cap;
394 	uint16_t val;
395 
396 	if (pci_find_extcap(dev, PCIY_EXPRESS, &cap) != 0)
397 		return (0);
398 	if (size < 128)
399 		size = 128;
400 	if (size > 4096)
401 		size = 4096;
402 	size = (1 << (fls(size) - 1));
403 	val = pci_read_config(dev, cap + PCIR_EXPRESS_DEVICE_CTL, 2);
404 	val &= ~PCIM_EXP_CTL_MAX_READ_REQUEST;
405 	val |= (fls(size) - 8) << 12;
406 	pci_write_config(dev, cap + PCIR_EXPRESS_DEVICE_CTL, val, 2);
407 	return (size);
408 }
409 
410 
411 int
412 pci_get_powerstate(device_t dev)
413 {
414 	int capabilityRegister;
415 	uint16 status;
416 	int powerState = PCI_POWERSTATE_D0;
417 
418 	if (pci_find_extcap(dev, PCIY_PMG, &capabilityRegister) != EOK)
419 		return powerState;
420 
421 	status = pci_read_config(dev, capabilityRegister + PCIR_POWER_STATUS, 2);
422 	switch (status & PCI_pm_mask) {
423 		case PCI_pm_state_d0:
424 			break;
425 		case PCI_pm_state_d1:
426 			powerState = PCI_POWERSTATE_D1;
427 			break;
428 		case PCI_pm_state_d2:
429 			powerState = PCI_POWERSTATE_D2;
430 			break;
431 		case PCI_pm_state_d3:
432 			powerState = PCI_POWERSTATE_D3;
433 			break;
434 		default:
435 			powerState = PCI_POWERSTATE_UNKNOWN;
436 			break;
437 	}
438 
439 	TRACE_PCI(dev, "%s: D%i\n", __func__, powerState);
440 	return powerState;
441 }
442 
443 
444 int
445 pci_set_powerstate(device_t dev, int newPowerState)
446 {
447 	int capabilityRegister;
448 	int oldPowerState;
449 	uint8 currentPowerManagementStatus;
450 	uint8 newPowerManagementStatus;
451 	uint16 powerManagementCapabilities;
452 	bigtime_t stateTransitionDelayInUs = 0;
453 
454 	if (pci_find_extcap(dev, PCIY_PMG, &capabilityRegister) != EOK)
455 		return EOPNOTSUPP;
456 
457 	oldPowerState = pci_get_powerstate(dev);
458 	if (oldPowerState == newPowerState)
459 		return EOK;
460 
461 	switch (max_c(oldPowerState, newPowerState)) {
462 		case PCI_POWERSTATE_D2:
463 			stateTransitionDelayInUs = 200;
464 			break;
465 		case PCI_POWERSTATE_D3:
466 			stateTransitionDelayInUs = 10000;
467 			break;
468 	}
469 
470 	currentPowerManagementStatus = pci_read_config(dev, capabilityRegister
471 		+ PCIR_POWER_STATUS, 2);
472 	newPowerManagementStatus = currentPowerManagementStatus & ~PCI_pm_mask;
473 	powerManagementCapabilities = pci_read_config(dev, capabilityRegister
474 		+ PCIR_POWER_CAP, 2);
475 
476 	switch (newPowerState) {
477 		case PCI_POWERSTATE_D0:
478 			newPowerManagementStatus |= PCIM_PSTAT_D0;
479 			break;
480 		case PCI_POWERSTATE_D1:
481 			if ((powerManagementCapabilities & PCI_pm_d1supp) == 0)
482 				return EOPNOTSUPP;
483 			newPowerManagementStatus |= PCIM_PSTAT_D1;
484 			break;
485 		case PCI_POWERSTATE_D2:
486 			if ((powerManagementCapabilities & PCI_pm_d2supp) == 0)
487 				return EOPNOTSUPP;
488 			newPowerManagementStatus |= PCIM_PSTAT_D2;
489 			break;
490 		case PCI_POWERSTATE_D3:
491 			newPowerManagementStatus |= PCIM_PSTAT_D3;
492 			break;
493 		default:
494 			return EINVAL;
495 	}
496 
497 	TRACE_PCI(dev, "%s: D%i -> D%i\n", __func__, oldPowerState, newPowerState);
498 	pci_write_config(dev, capabilityRegister + PCIR_POWER_STATUS,
499 		newPowerManagementStatus, 2);
500 	if (stateTransitionDelayInUs != 0)
501 		snooze(stateTransitionDelayInUs);
502 
503 	return EOK;
504 }
505