1 // Copyright 2017 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 <endian.h>
6 #include <kernel/mutex.h>
7 #include <zircon/types.h>
8 #include <lib/pci/pio.h>
9 #include <kernel/auto_lock.h>
10 #include <kernel/spinlock.h>
11
12 // TODO: This library exists as a shim for the awkward period between bringing
13 // PCI legacy support online, and moving PCI to userspace. Initially, it exists
14 // as a kernel library that userspace accesses via syscalls so that a userspace
15 // process never causes a race condition with the bus driver's accesses. Later,
16 // all accesses will go through the library itself in userspace and the syscalls
17 // will no longer exist.
18
19 namespace Pci {
20
21 #ifdef ARCH_X86
22 #include <arch/x86.h>
23 static SpinLock pio_lock;
24
25 static constexpr uint16_t kPciConfigAddr = 0xCF8;
26 static constexpr uint16_t kPciConfigData = 0xCFC;
27 static constexpr uint32_t kPciCfgEnable = (1 << 31);
WidthMask(size_t width)28 static constexpr uint32_t WidthMask(size_t width) {
29 return (width == 32) ? 0xffffffff : (1u << width) - 1u;
30 }
31
PioCfgRead(uint32_t addr,uint32_t * val,size_t width)32 zx_status_t PioCfgRead(uint32_t addr, uint32_t* val, size_t width) {
33 AutoSpinLock lock(&pio_lock);
34
35 size_t shift = (addr & 0x3) * 8u;
36 if (shift + width > 32) {
37 return ZX_ERR_INVALID_ARGS;
38 }
39
40 outpd(kPciConfigAddr, (addr & ~0x3) | kPciCfgEnable);;
41 uint32_t tmp_val = LE32(inpd(kPciConfigData));
42 uint32_t width_mask = WidthMask(width);
43
44 // Align the read to the correct offset, then mask based on byte width
45 *val = (tmp_val >> shift) & width_mask;
46 return ZX_OK;
47 }
48
PioCfgRead(uint8_t bus,uint8_t dev,uint8_t func,uint8_t offset,uint32_t * val,size_t width)49 zx_status_t PioCfgRead(uint8_t bus, uint8_t dev, uint8_t func,
50 uint8_t offset, uint32_t* val, size_t width) {
51 return PioCfgRead(PciBdfRawAddr(bus, dev, func, offset), val, width);
52 }
53
PioCfgWrite(uint32_t addr,uint32_t val,size_t width)54 zx_status_t PioCfgWrite(uint32_t addr, uint32_t val, size_t width) {
55 AutoSpinLock lock(&pio_lock);
56
57 size_t shift = (addr & 0x3) * 8u;
58 if (shift + width > 32) {
59 return ZX_ERR_INVALID_ARGS;
60 }
61
62 uint32_t width_mask = WidthMask(width);
63 uint32_t write_mask = width_mask << shift;
64 outpd(kPciConfigAddr, (addr & ~0x3) | kPciCfgEnable);
65 uint32_t tmp_val = LE32(inpd(kPciConfigData));
66
67 val &= width_mask;
68 tmp_val &= ~write_mask;
69 tmp_val |= (val << shift);
70 outpd(kPciConfigData, LE32(tmp_val));
71
72 return ZX_OK;
73 }
74
PioCfgWrite(uint8_t bus,uint8_t dev,uint8_t func,uint8_t offset,uint32_t val,size_t width)75 zx_status_t PioCfgWrite(uint8_t bus, uint8_t dev, uint8_t func,
76 uint8_t offset, uint32_t val, size_t width) {
77 return PioCfgWrite(PciBdfRawAddr(bus, dev, func, offset), val, width);
78 }
79
80 #else // not x86
81 zx_status_t PioCfgRead(uint32_t addr, uint32_t* val, size_t width) {
82 return ZX_ERR_NOT_SUPPORTED;
83 }
84
85 zx_status_t PioCfgRead(uint8_t bus, uint8_t dev, uint8_t func, uint8_t offset,
86 uint32_t* val, size_t width) {
87 return ZX_ERR_NOT_SUPPORTED;
88 }
89
90 zx_status_t PioCfgWrite(uint32_t addr, uint32_t val, size_t width) {
91 return ZX_ERR_NOT_SUPPORTED;;
92 }
93
94 zx_status_t PioCfgWrite(uint8_t bus, uint8_t dev, uint8_t func, uint8_t offset,
95 uint32_t val, size_t width) {
96 return ZX_ERR_NOT_SUPPORTED;
97 }
98
99 #endif // ARCH_X86
100 }; // namespace PCI
101