xref: /haiku/src/libs/compat/freebsd_network/pci.cpp (revision 52c4471a3024d2eb81fe88e2c3982b9f8daa5e56)
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 	pci_info* info = get_device_pci_info(dev);
240 	uint8 offset;
241 	status_t status;
242 
243 	status = gPci->find_pci_capability(info->bus, info->device, info->function,
244 		capability, &offset);
245 	*capreg = offset;
246 	return status;
247 }
248 
249 
250 int
251 pci_find_extcap(device_t dev, int capability, int *capreg)
252 {
253 	pci_info* info = get_device_pci_info(dev);
254 	uint16 offset;
255 	status_t status;
256 
257 	status = gPci->find_pci_extended_capability(info->bus, info->device, info->function,
258 		capability, &offset);
259 	*capreg = offset;
260 	return status;
261 }
262 
263 
264 int
265 pci_msi_count(device_t dev)
266 {
267 	if (gPCIx86 == NULL)
268 		return 0;
269 
270 	pci_info* info = get_device_pci_info(dev);
271 	return gPCIx86->get_msi_count(info->bus, info->device, info->function);
272 }
273 
274 
275 int
276 pci_alloc_msi(device_t dev, int *count)
277 {
278 	if (gPCIx86 == NULL)
279 		return ENODEV;
280 
281 	pci_info* info = get_device_pci_info(dev);
282 	uint8 startVector = 0;
283 	if (gPCIx86->configure_msi(info->bus, info->device, info->function, *count,
284 			&startVector) != B_OK) {
285 		return ENODEV;
286 	}
287 
288 	((struct root_device_softc *)dev->root->softc)->is_msi = true;
289 	info->u.h0.interrupt_line = startVector;
290 	return EOK;
291 }
292 
293 
294 int
295 pci_release_msi(device_t dev)
296 {
297 	if (gPCIx86 == NULL)
298 		return ENODEV;
299 
300 	pci_info* info = get_device_pci_info(dev);
301 	gPCIx86->unconfigure_msi(info->bus, info->device, info->function);
302 	((struct root_device_softc *)dev->root->softc)->is_msi = false;
303 	((struct root_device_softc *)dev->root->softc)->is_msix = false;
304 	return EOK;
305 }
306 
307 
308 int
309 pci_msix_table_bar(device_t dev)
310 {
311 	pci_info* info = get_device_pci_info(dev);
312 
313 	uint8 capability_offset;
314 	if (gPci->find_pci_capability(info->bus, info->device, info->function,
315 			PCI_cap_id_msix, &capability_offset) != B_OK)
316 		return -1;
317 
318 	uint32 table_value = gPci->read_pci_config(info->bus, info->device, info->function,
319 		capability_offset + PCI_msix_table, 4);
320 
321 	uint32 bar = table_value & PCI_msix_bir_mask;
322 	return PCIR_BAR(bar);
323 }
324 
325 
326 int
327 pci_msix_count(device_t dev)
328 {
329 	if (gPCIx86 == NULL)
330 		return 0;
331 
332 	pci_info* info = get_device_pci_info(dev);
333 	return gPCIx86->get_msix_count(info->bus, info->device, info->function);
334 }
335 
336 
337 int
338 pci_alloc_msix(device_t dev, int *count)
339 {
340 	if (gPCIx86 == NULL)
341 		return ENODEV;
342 
343 	pci_info* info = get_device_pci_info(dev);
344 	uint8 startVector = 0;
345 	if (gPCIx86->configure_msix(info->bus, info->device, info->function, *count,
346 			&startVector) != B_OK) {
347 		return ENODEV;
348 	}
349 
350 	((struct root_device_softc *)dev->root->softc)->is_msix = true;
351 	info->u.h0.interrupt_line = startVector;
352 	return EOK;
353 }
354 
355 
356 int
357 pci_get_max_read_req(device_t dev)
358 {
359 	int cap;
360 	uint16_t val;
361 
362 	if (pci_find_extcap(dev, PCIY_EXPRESS, &cap) != 0)
363 		return (0);
364 	val = pci_read_config(dev, cap + PCIR_EXPRESS_DEVICE_CTL, 2);
365 	val &= PCIM_EXP_CTL_MAX_READ_REQUEST;
366 	val >>= 12;
367 	return (1 << (val + 7));
368 }
369 
370 
371 int
372 pci_set_max_read_req(device_t dev, int size)
373 {
374 	int cap;
375 	uint16_t val;
376 
377 	if (pci_find_extcap(dev, PCIY_EXPRESS, &cap) != 0)
378 		return (0);
379 	if (size < 128)
380 		size = 128;
381 	if (size > 4096)
382 		size = 4096;
383 	size = (1 << (fls(size) - 1));
384 	val = pci_read_config(dev, cap + PCIR_EXPRESS_DEVICE_CTL, 2);
385 	val &= ~PCIM_EXP_CTL_MAX_READ_REQUEST;
386 	val |= (fls(size) - 8) << 12;
387 	pci_write_config(dev, cap + PCIR_EXPRESS_DEVICE_CTL, val, 2);
388 	return (size);
389 }
390 
391 
392 int
393 pci_get_powerstate(device_t dev)
394 {
395 	int capabilityRegister;
396 	uint16 status;
397 	int powerState = PCI_POWERSTATE_D0;
398 
399 	if (pci_find_extcap(dev, PCIY_PMG, &capabilityRegister) != EOK)
400 		return powerState;
401 
402 	status = pci_read_config(dev, capabilityRegister + PCIR_POWER_STATUS, 2);
403 	switch (status & PCI_pm_mask) {
404 		case PCI_pm_state_d0:
405 			break;
406 		case PCI_pm_state_d1:
407 			powerState = PCI_POWERSTATE_D1;
408 			break;
409 		case PCI_pm_state_d2:
410 			powerState = PCI_POWERSTATE_D2;
411 			break;
412 		case PCI_pm_state_d3:
413 			powerState = PCI_POWERSTATE_D3;
414 			break;
415 		default:
416 			powerState = PCI_POWERSTATE_UNKNOWN;
417 			break;
418 	}
419 
420 	TRACE_PCI(dev, "%s: D%i\n", __func__, powerState);
421 	return powerState;
422 }
423 
424 
425 int
426 pci_set_powerstate(device_t dev, int newPowerState)
427 {
428 	int capabilityRegister;
429 	int oldPowerState;
430 	uint8 currentPowerManagementStatus;
431 	uint8 newPowerManagementStatus;
432 	uint16 powerManagementCapabilities;
433 	bigtime_t stateTransitionDelayInUs = 0;
434 
435 	if (pci_find_extcap(dev, PCIY_PMG, &capabilityRegister) != EOK)
436 		return EOPNOTSUPP;
437 
438 	oldPowerState = pci_get_powerstate(dev);
439 	if (oldPowerState == newPowerState)
440 		return EOK;
441 
442 	switch (max_c(oldPowerState, newPowerState)) {
443 		case PCI_POWERSTATE_D2:
444 			stateTransitionDelayInUs = 200;
445 			break;
446 		case PCI_POWERSTATE_D3:
447 			stateTransitionDelayInUs = 10000;
448 			break;
449 	}
450 
451 	currentPowerManagementStatus = pci_read_config(dev, capabilityRegister
452 		+ PCIR_POWER_STATUS, 2);
453 	newPowerManagementStatus = currentPowerManagementStatus & ~PCI_pm_mask;
454 	powerManagementCapabilities = pci_read_config(dev, capabilityRegister
455 		+ PCIR_POWER_CAP, 2);
456 
457 	switch (newPowerState) {
458 		case PCI_POWERSTATE_D0:
459 			newPowerManagementStatus |= PCIM_PSTAT_D0;
460 			break;
461 		case PCI_POWERSTATE_D1:
462 			if ((powerManagementCapabilities & PCI_pm_d1supp) == 0)
463 				return EOPNOTSUPP;
464 			newPowerManagementStatus |= PCIM_PSTAT_D1;
465 			break;
466 		case PCI_POWERSTATE_D2:
467 			if ((powerManagementCapabilities & PCI_pm_d2supp) == 0)
468 				return EOPNOTSUPP;
469 			newPowerManagementStatus |= PCIM_PSTAT_D2;
470 			break;
471 		case PCI_POWERSTATE_D3:
472 			newPowerManagementStatus |= PCIM_PSTAT_D3;
473 			break;
474 		default:
475 			return EINVAL;
476 	}
477 
478 	TRACE_PCI(dev, "%s: D%i -> D%i\n", __func__, oldPowerState, newPowerState);
479 	pci_write_config(dev, capabilityRegister + PCIR_POWER_STATUS,
480 		newPowerManagementStatus, 2);
481 	if (stateTransitionDelayInUs != 0)
482 		snooze(stateTransitionDelayInUs);
483 
484 	return EOK;
485 }
486