1 // Copyright 2018 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 #include <assert.h>
5 #include <fbl/auto_lock.h>
6 #include <hw/inout.h>
7 #include <hwreg/bitfields.h>
8 #include <pci/pio.h>
9 #include <zircon/hw/pci.h>
10 #include <zircon/types.h>
11
12 #ifdef __x86_64__
13
14 static constexpr uint16_t kPciConfigAddrPort = 0xCF8;
15 static constexpr uint16_t kPciConfigDataPort = 0xCFC;
16
17 fbl::Mutex pio_port_lock;
18
19 typedef struct {
20 uint32_t value;
21 DEF_SUBBIT(value, 31, enable);
22 DEF_SUBFIELD(value, 23, 16, bus);
23 DEF_SUBFIELD(value, 15, 11, device);
24 DEF_SUBFIELD(value, 10, 8, function);
25 DEF_SUBFIELD(value, 7, 0, reg_num);
26 } config_address_t;
27
28 // This library assumes the calling process already has the io bitmap permissions
29 // set to access cf8/cfc. Any processes with that permission will be synchronizing
30 // with each other by means of the PCI Root protocol.
31
pci_pio_read(pci_bdf_t bdf,uint8_t offset,uint32_t * val)32 static zx_status_t pci_pio_read(pci_bdf_t bdf, uint8_t offset, uint32_t* val) {
33 fbl::AutoLock lock(&pio_port_lock);
34
35 config_address_t addr = {};
36 addr.set_enable(true);
37 addr.set_bus(bdf.bus_id);
38 addr.set_device(bdf.device_id);
39 addr.set_function(bdf.function_id);
40 addr.set_reg_num(offset & ~0x3); // Lowest 2 bits must be zero, all reads are 32 bit
41
42 outpd(kPciConfigAddrPort, addr.value);
43 *val = inpd(kPciConfigDataPort);
44 return ZX_OK;
45 }
46
pci_pio_read32(pci_bdf_t bdf,uint8_t offset,uint32_t * val)47 zx_status_t pci_pio_read32(pci_bdf_t bdf, uint8_t offset, uint32_t* val) {
48 // Only 32 bit alignment allowed for 32 bit reads.
49 if (offset & 0x3) {
50 printf("invalid args read32\n");
51 return ZX_ERR_INVALID_ARGS;
52 }
53 uint32_t _val = 0;
54 zx_status_t status = pci_pio_read(bdf, offset, val);
55 if (status == ZX_OK) {
56 *val = _val;
57 }
58 return status;
59 }
60
pci_pio_read16(pci_bdf_t bdf,uint8_t offset,uint16_t * val)61 zx_status_t pci_pio_read16(pci_bdf_t bdf, uint8_t offset, uint16_t* val) {
62 // Only 16 bit alignment allowed for 16 bit reads
63 if (offset & 0x1) {
64 printf("invalid args read16\n");
65 return ZX_ERR_INVALID_ARGS;
66 }
67
68 uint32_t _val = 0;
69 zx_status_t status = pci_pio_read(bdf, offset, &_val);
70 if (status == ZX_OK) {
71 // Shift the top 16 over if requested
72 *val = static_cast<uint16_t>(_val >> (8u * (offset & 0x2)));
73 }
74 return status;
75 }
76
pci_pio_read8(pci_bdf_t bdf,uint8_t offset,uint8_t * val)77 zx_status_t pci_pio_read8(pci_bdf_t bdf, uint8_t offset, uint8_t* val) {
78 uint32_t _val = 0;
79 zx_status_t status = pci_pio_read(bdf, offset, &_val);
80 if (status == ZX_OK) {
81 *val = static_cast<uint8_t>(_val >> (8u * (offset & 0x3)));
82 }
83 return status;
84 }
85
86 // Generates an unshifted mask to match the width of the write we're making.
rmw_mask(size_t width)87 static constexpr uint32_t rmw_mask(size_t width) {
88 return (width == 32) ? 0xffffffff : (1u << width) - 1u;
89 }
90
91 // Figure out the shift to align the bytes in the right. Valid offsets are already
92 // checked by the pci_pio_write calls themselves.
calculate_shift(uint8_t offset)93 static constexpr int calculate_shift(uint8_t offset) {
94 return (offset & 0x3) * 8u;
95 }
96
pci_pio_write(pci_bdf_t bdf,uint8_t offset,uint32_t mask,uint32_t val)97 static zx_status_t pci_pio_write(pci_bdf_t bdf, uint8_t offset, uint32_t mask, uint32_t val) {
98 fbl::AutoLock lock(&pio_port_lock);
99
100 config_address_t addr = {};
101 addr.set_enable(true);
102 addr.set_bus(bdf.bus_id);
103 addr.set_device(bdf.device_id);
104 addr.set_function(bdf.function_id);
105 addr.set_reg_num(offset & ~0x3); // Lowest 3 bits must be zero, all reads are 32 bit
106
107 outpd(kPciConfigAddrPort, addr.value);
108 // Zero out the bytes we're going to write and then OR them in.
109 uint32_t old_val = inpd(kPciConfigDataPort);
110 old_val &= ~mask;
111 old_val |= val;
112 outpd(kPciConfigDataPort, old_val);
113
114 return ZX_OK;
115 }
116
pci_pio_write32(pci_bdf_t bdf,uint8_t offset,uint32_t val)117 zx_status_t pci_pio_write32(pci_bdf_t bdf, uint8_t offset, uint32_t val) {
118 // Only 32 bit alignment allowed for 32 bit reads
119 if (offset & 0x3) {
120 return ZX_ERR_INVALID_ARGS;
121 }
122 return pci_pio_write(bdf, offset, rmw_mask(32), val);
123 }
124
125 // These functions both create a shifted mask and shifted value to call the main write
126 // function so that its body can be as simple as possible.
pci_pio_write16(pci_bdf_t bdf,uint8_t offset,uint16_t val)127 zx_status_t pci_pio_write16(pci_bdf_t bdf, uint8_t offset, uint16_t val) {
128 // Only 16 bit alignment allowed for 16 bit reads
129 if (offset & 0x1) {
130 return ZX_ERR_INVALID_ARGS;
131 }
132 int shift = calculate_shift(offset);
133 return pci_pio_write(bdf, offset, rmw_mask(16) << shift, val << shift);
134 }
135
pci_pio_write8(pci_bdf_t bdf,uint8_t offset,uint8_t val)136 zx_status_t pci_pio_write8(pci_bdf_t bdf, uint8_t offset, uint8_t val) {
137 int shift = calculate_shift(offset);
138 return pci_pio_write(bdf, offset, rmw_mask(8) << shift, val << shift);
139 }
140
141 #endif // __x86_64__
142