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
19namespace Pci {
20
21#ifdef ARCH_X86
22#include <arch/x86.h>
23static SpinLock pio_lock;
24
25static constexpr uint16_t kPciConfigAddr = 0xCF8;
26static constexpr uint16_t kPciConfigData = 0xCFC;
27static constexpr uint32_t kPciCfgEnable = (1 << 31);
28static constexpr uint32_t WidthMask(size_t width) {
29    return (width == 32) ? 0xffffffff : (1u << width) - 1u;
30}
31
32zx_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
49zx_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
54zx_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
75zx_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
81zx_status_t PioCfgRead(uint32_t addr, uint32_t* val, size_t width) {
82    return ZX_ERR_NOT_SUPPORTED;
83}
84
85zx_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
90zx_status_t PioCfgWrite(uint32_t addr, uint32_t val, size_t width) {
91    return ZX_ERR_NOT_SUPPORTED;;
92}
93
94zx_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