1 /* 2 * Copyright 2020 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include "pci.hpp" 18 19 #include "tool_errors.hpp" 20 21 extern "C" 22 { 23 #include <pciaccess.h> 24 } // extern "C" 25 26 #include <fmt/format.h> 27 28 #include <stdplus/handle/managed.hpp> 29 30 #include <cstring> 31 #include <system_error> 32 33 namespace host_tool 34 { 35 36 namespace 37 { 38 39 /** @brief RAII wrapper and its destructor for creating a pci_device_iterator */ 40 static void closeIt(struct pci_device_iterator*&& it, 41 const PciAccess* const& pci) 42 { 43 pci->pci_iterator_destroy(it); 44 } 45 using It = stdplus::Managed<struct pci_device_iterator*, 46 const PciAccess* const>::Handle<closeIt>; 47 48 } // namespace 49 50 PciAccessBridge::PciAccessBridge(const struct pci_id_match* match, int bar, 51 std::size_t dataOffset, std::size_t dataLength, 52 const PciAccess* pci) : 53 dataOffset(dataOffset), 54 dataLength(dataLength), pci(pci) 55 { 56 It it(pci->pci_id_match_iterator_create(match), pci); 57 58 while ((dev = pci->pci_device_next(*it))) 59 { 60 int ret = pci->pci_device_probe(dev); 61 if (ret) 62 { 63 throw std::system_error(ret, std::generic_category(), 64 "Error probing PCI device"); 65 } 66 67 /* Verify it's a memory-based bar. */ 68 if (!dev->regions[bar].is_IO) 69 break; 70 } 71 72 if (!dev) 73 { 74 throw NotFoundException(fmt::format( 75 "PCI device {:#04x}:{:#04x}", match->vendor_id, match->device_id)); 76 } 77 78 std::fprintf(stderr, "Find [0x%x 0x%x] \n", match->vendor_id, 79 match->device_id); 80 std::fprintf(stderr, "bar%d[0x%x] \n", bar, 81 static_cast<unsigned int>(dev->regions[bar].base_addr)); 82 83 size = dev->regions[bar].size; 84 int ret = pci->pci_device_map_range( 85 dev, dev->regions[bar].base_addr, dev->regions[bar].size, 86 PCI_DEV_MAP_FLAG_WRITABLE, reinterpret_cast<void**>(&addr)); 87 if (ret) 88 { 89 throw std::system_error(ret, std::generic_category(), 90 "Error mapping PCI device memory"); 91 } 92 } 93 94 PciAccessBridge::~PciAccessBridge() 95 { 96 int ret = pci->pci_device_unmap_range(dev, addr, size); 97 98 if (ret) 99 { 100 std::fprintf(stderr, "Error while unmapping PCI device memory: %s\n", 101 std::strerror(ret)); 102 } 103 } 104 105 void PciAccessBridge::write(const stdplus::span<const std::uint8_t> data) 106 { 107 if (data.size() > dataLength) 108 { 109 throw ToolException( 110 fmt::format("Write of {} bytes exceeds maximum of {}", data.size(), 111 dataLength)); 112 } 113 114 std::memcpy(addr + dataOffset, data.data(), data.size()); 115 } 116 117 void NuvotonPciBridge::enableBridge() 118 { 119 std::uint8_t value; 120 int ret; 121 122 /* TODO: pci_device_disable support is missing in libpciaccess. Add it 123 * to the disableBridge() once it is available. 124 * https://gitlab.freedesktop.org/xorg/lib/libpciaccess/-/merge_requests/17 125 */ 126 127 pci->pci_device_enable(dev); 128 129 /* We need to retain this direct write to config space eventhough 130 * pci_device_enable() should do it. Because currently disabling is done 131 * through write to config space and not done through the proper api. 132 * So libpciaccess ref count does not reset on disable. The 133 * pci_device_enable() above will not do anything the second time. 134 */ 135 ret = pci->pci_device_cfg_read_u8(dev, &value, bridge); 136 if (ret) 137 { 138 throw std::system_error(ret, std::generic_category(), 139 "Error reading bridge status"); 140 } 141 142 if (value & bridgeEnabled) 143 { 144 std::fprintf(stderr, "Bridge already enabled\n"); 145 return; 146 } 147 148 value |= bridgeEnabled; 149 150 ret = pci->pci_device_cfg_write_u8(dev, value, bridge); 151 if (ret) 152 { 153 throw std::system_error(ret, std::generic_category(), 154 "Error enabling bridge"); 155 } 156 } 157 158 void NuvotonPciBridge::disableBridge() 159 { 160 std::uint8_t value; 161 int ret; 162 163 ret = pci->pci_device_cfg_read_u8(dev, &value, bridge); 164 if (ret) 165 { 166 std::fprintf(stderr, "Error reading bridge status: %s\n", 167 std::strerror(ret)); 168 return; 169 } 170 value &= ~bridgeEnabled; 171 172 ret = pci->pci_device_cfg_write_u8(dev, value, bridge); 173 if (ret) 174 { 175 std::fprintf(stderr, "Error disabling bridge: %s\n", 176 std::strerror(ret)); 177 } 178 } 179 180 void AspeedPciBridge::enableBridge() 181 { 182 /* We sent the open command before this, so the window should be open and 183 * the bridge enabled on the BMC. 184 */ 185 std::uint32_t value; 186 187 /* TODO: pci_device_disable support is missing in libpciaccess. Add it 188 * to the disableBridge() once it is available. 189 * https://gitlab.freedesktop.org/xorg/lib/libpciaccess/-/merge_requests/17 190 */ 191 192 pci->pci_device_enable(dev); 193 194 /* We need to retain this direct write to config space eventhough 195 * pci_device_enable() should do it. Because currently disabling is done 196 * through write to config space and not done through the proper api. 197 * So libpciaccess ref count does not reset on disable. The 198 * pci_device_enable() above will not do anything the second time. 199 */ 200 201 std::memcpy(&value, addr + config, sizeof(value)); 202 203 if (0 == (value & bridgeEnabled)) 204 { 205 std::fprintf(stderr, "Bridge not enabled - Enabling from host\n"); 206 207 value |= bridgeEnabled; 208 std::memcpy(addr + config, &value, sizeof(value)); 209 } 210 211 std::fprintf(stderr, "The bridge is enabled!\n"); 212 } 213 214 void AspeedPciBridge::disableBridge() 215 { 216 /* addr is valid if the constructor completed */ 217 218 /* Read current value, and just blindly unset the bit. */ 219 std::uint32_t value; 220 std::memcpy(&value, addr + config, sizeof(value)); 221 222 value &= ~bridgeEnabled; 223 std::memcpy(addr + config, &value, sizeof(value)); 224 } 225 226 void AspeedPciBridge::configure(const ipmi_flash::PciConfigResponse& configResp) 227 { 228 std::fprintf(stderr, "Received address: 0x%x\n", configResp.address); 229 230 /* Configure the mmio to point there. */ 231 std::memcpy(addr + bridge, &configResp.address, sizeof(configResp.address)); 232 } 233 234 } // namespace host_tool 235