aboutsummaryrefslogtreecommitdiff
path: root/src/drivers/bus/pciextra.c
blob: 23617bc9a6f16b48e31a4a6a3ff9eac5f88587c4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );

#include <stdint.h>
#include <ipxe/timer.h>
#include <ipxe/pci.h>

static int pci_find_capability_common ( struct pci_device *pci,
					uint8_t pos, int cap ) {
	uint8_t id;
	int ttl = 48;

	while ( ttl-- && pos >= 0x40 ) {
		pos &= ~3;
		pci_read_config_byte ( pci, pos + PCI_CAP_ID, &id );
		DBG ( "PCI Capability: %d\n", id );
		if ( id == 0xff )
			break;
		if ( id == cap )
			return pos;
		pci_read_config_byte ( pci, pos + PCI_CAP_NEXT, &pos );
	}
	return 0;
}

/**
 * Look for a PCI capability
 *
 * @v pci		PCI device to query
 * @v cap		Capability code
 * @ret address		Address of capability, or 0 if not found
 *
 * Determine whether or not a device supports a given PCI capability.
 * Returns the address of the requested capability structure within
 * the device's PCI configuration space, or 0 if the device does not
 * support it.
 */
int pci_find_capability ( struct pci_device *pci, int cap ) {
	uint16_t status;
	uint8_t pos;
	uint8_t hdr_type;

	pci_read_config_word ( pci, PCI_STATUS, &status );
	if ( ! ( status & PCI_STATUS_CAP_LIST ) )
		return 0;

	pci_read_config_byte ( pci, PCI_HEADER_TYPE, &hdr_type );
	switch ( hdr_type & PCI_HEADER_TYPE_MASK ) {
	case PCI_HEADER_TYPE_NORMAL:
	case PCI_HEADER_TYPE_BRIDGE:
	default:
		pci_read_config_byte ( pci, PCI_CAPABILITY_LIST, &pos );
		break;
	case PCI_HEADER_TYPE_CARDBUS:
		pci_read_config_byte ( pci, PCI_CB_CAPABILITY_LIST, &pos );
		break;
	}
	return pci_find_capability_common ( pci, pos, cap );
}

/**
 * Look for another PCI capability
 *
 * @v pci		PCI device to query
 * @v pos		Address of the current capability
 * @v cap		Capability code
 * @ret address		Address of capability, or 0 if not found
 *
 * Determine whether or not a device supports a given PCI capability
 * starting the search at a given address within the device's PCI
 * configuration space. Returns the address of the next capability
 * structure within the device's PCI configuration space, or 0 if the
 * device does not support another such capability.
 */
int pci_find_next_capability ( struct pci_device *pci, int pos, int cap ) {
	uint8_t new_pos;

	pci_read_config_byte ( pci, pos + PCI_CAP_NEXT, &new_pos );
	return pci_find_capability_common ( pci, new_pos, cap );
}

/**
 * Find the size of a PCI BAR
 *
 * @v pci		PCI device
 * @v reg		PCI register number
 * @ret size		BAR size
 *
 * It should not be necessary for any Etherboot code to call this
 * function.
 */
unsigned long pci_bar_size ( struct pci_device *pci, unsigned int reg ) {
	uint16_t cmd;
	uint32_t start, size;

	/* Save the original command register */
	pci_read_config_word ( pci, PCI_COMMAND, &cmd );
	/* Save the original bar */
	pci_read_config_dword ( pci, reg, &start );
	/* Compute which bits can be set */
	pci_write_config_dword ( pci, reg, ~0 );
	pci_read_config_dword ( pci, reg, &size );
	/* Restore the original size */
	pci_write_config_dword ( pci, reg, start );
	/* Find the significant bits */
	/* Restore the original command register. This reenables decoding. */
	pci_write_config_word ( pci, PCI_COMMAND, cmd );
	if ( start & PCI_BASE_ADDRESS_SPACE_IO ) {
		size &= ~PCI_BASE_ADDRESS_IO_MASK;
	} else {
		size &= ~PCI_BASE_ADDRESS_MEM_MASK;
	}
	/* Find the lowest bit set */
	size = size & ~( size - 1 );
	return size;
}

/**
 * Perform PCI Express function-level reset (FLR)
 *
 * @v pci		PCI device
 * @v exp		PCI Express Capability address
 */
void pci_reset ( struct pci_device *pci, unsigned int exp ) {
	uint16_t control;

	/* Perform a PCIe function-level reset */
	pci_read_config_word ( pci, ( exp + PCI_EXP_DEVCTL ), &control );
	control |= PCI_EXP_DEVCTL_FLR;
	pci_write_config_word ( pci, ( exp + PCI_EXP_DEVCTL ), control );

	/* Allow time for reset to complete */
	mdelay ( PCI_EXP_FLR_DELAY_MS );

	/* Re-enable device */
	adjust_pci_device ( pci );
}