blob: 8b4d310cd9eff2120b3d71f9e47281a27eacac25 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <assert.h>
#include <fbl/auto_lock.h>
#include <hw/inout.h>
#include <hwreg/bitfields.h>
#include <pci/pio.h>
#include <zircon/hw/pci.h>
#include <zircon/types.h>
#ifdef __x86_64__
static constexpr uint16_t kPciConfigAddrPort = 0xCF8;
static constexpr uint16_t kPciConfigDataPort = 0xCFC;
fbl::Mutex pio_port_lock;
typedef struct {
uint32_t value;
DEF_SUBBIT(value, 31, enable);
DEF_SUBFIELD(value, 23, 16, bus);
DEF_SUBFIELD(value, 15, 11, device);
DEF_SUBFIELD(value, 10, 8, function);
DEF_SUBFIELD(value, 7, 0, reg_num);
} config_address_t;
// This library assumes the calling process already has the io bitmap permissions
// set to access cf8/cfc. Any processes with that permission will be synchronizing
// with each other by means of the PCI Root protocol.
static zx_status_t pci_pio_read(pci_bdf_t bdf, uint8_t offset, uint32_t* val) {
fbl::AutoLock lock(&pio_port_lock);
config_address_t addr = {};
addr.set_enable(true);
addr.set_bus(bdf.bus_id);
addr.set_device(bdf.device_id);
addr.set_function(bdf.function_id);
addr.set_reg_num(offset & ~0x3); // Lowest 2 bits must be zero, all reads are 32 bit
outpd(kPciConfigAddrPort, addr.value);
*val = inpd(kPciConfigDataPort);
return ZX_OK;
}
zx_status_t pci_pio_read32(pci_bdf_t bdf, uint8_t offset, uint32_t* val) {
// Only 32 bit alignment allowed for 32 bit reads.
if (offset & 0x3) {
printf("invalid args read32\n");
return ZX_ERR_INVALID_ARGS;
}
uint32_t _val = 0;
zx_status_t status = pci_pio_read(bdf, offset, val);
if (status == ZX_OK) {
*val = _val;
}
return status;
}
zx_status_t pci_pio_read16(pci_bdf_t bdf, uint8_t offset, uint16_t* val) {
// Only 16 bit alignment allowed for 16 bit reads
if (offset & 0x1) {
printf("invalid args read16\n");
return ZX_ERR_INVALID_ARGS;
}
uint32_t _val = 0;
zx_status_t status = pci_pio_read(bdf, offset, &_val);
if (status == ZX_OK) {
// Shift the top 16 over if requested
*val = static_cast<uint16_t>(_val >> (8u * (offset & 0x2)));
}
return status;
}
zx_status_t pci_pio_read8(pci_bdf_t bdf, uint8_t offset, uint8_t* val) {
uint32_t _val = 0;
zx_status_t status = pci_pio_read(bdf, offset, &_val);
if (status == ZX_OK) {
*val = static_cast<uint8_t>(_val >> (8u * (offset & 0x3)));
}
return status;
}
// Generates an unshifted mask to match the width of the write we're making.
static constexpr uint32_t rmw_mask(size_t width) {
return (width == 32) ? 0xffffffff : (1u << width) - 1u;
}
// Figure out the shift to align the bytes in the right. Valid offsets are already
// checked by the pci_pio_write calls themselves.
static constexpr int calculate_shift(uint8_t offset) {
return (offset & 0x3) * 8u;
}
static zx_status_t pci_pio_write(pci_bdf_t bdf, uint8_t offset, uint32_t mask, uint32_t val) {
fbl::AutoLock lock(&pio_port_lock);
config_address_t addr = {};
addr.set_enable(true);
addr.set_bus(bdf.bus_id);
addr.set_device(bdf.device_id);
addr.set_function(bdf.function_id);
addr.set_reg_num(offset & ~0x3); // Lowest 3 bits must be zero, all reads are 32 bit
outpd(kPciConfigAddrPort, addr.value);
// Zero out the bytes we're going to write and then OR them in.
uint32_t old_val = inpd(kPciConfigDataPort);
old_val &= ~mask;
old_val |= val;
outpd(kPciConfigDataPort, old_val);
return ZX_OK;
}
zx_status_t pci_pio_write32(pci_bdf_t bdf, uint8_t offset, uint32_t val) {
// Only 32 bit alignment allowed for 32 bit reads
if (offset & 0x3) {
return ZX_ERR_INVALID_ARGS;
}
return pci_pio_write(bdf, offset, rmw_mask(32), val);
}
// These functions both create a shifted mask and shifted value to call the main write
// function so that its body can be as simple as possible.
zx_status_t pci_pio_write16(pci_bdf_t bdf, uint8_t offset, uint16_t val) {
// Only 16 bit alignment allowed for 16 bit reads
if (offset & 0x1) {
return ZX_ERR_INVALID_ARGS;
}
int shift = calculate_shift(offset);
return pci_pio_write(bdf, offset, rmw_mask(16) << shift, val << shift);
}
zx_status_t pci_pio_write8(pci_bdf_t bdf, uint8_t offset, uint8_t val) {
int shift = calculate_shift(offset);
return pci_pio_write(bdf, offset, rmw_mask(8) << shift, val << shift);
}
#endif // __x86_64__