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