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