1 // Copyright 2018 The Fuchsia Authors
2 //
3 // Use of this source code is governed by a MIT-style
4 // license that can be found in the LICENSE file or at
5 // https://opensource.org/licenses/MIT
6 //
7 #include "bus.h"
8 #include "common.h"
9 #include "config.h"
10 
11 #include <ddk/debug.h>
12 #include <ddk/device.h>
13 #include <ddk/mmio-buffer.h>
14 #include <fbl/alloc_checker.h>
15 
16 namespace pci {
17 
18 // Creates the PCI bus driver instance and attempts initialization.
Create(zx_device_t * parent)19 zx_status_t Bus::Create(zx_device_t* parent) {
20     zx_status_t status;
21     pciroot_protocol_t pciroot;
22     if ((status = device_get_protocol(parent, ZX_PROTOCOL_PCIROOT, &pciroot)) != ZX_OK) {
23         pci_errorf("failed to obtain pciroot protocol: %d!\n", status);
24         return status;
25     }
26 
27     fbl::AllocChecker ac;
28     Bus* bus = new (&ac) Bus(parent, &pciroot);
29     if (!ac.check()) {
30         pci_errorf("failed to allocate bus object.\n");
31         return ZX_ERR_NO_MEMORY;
32     }
33 
34     if ((status = bus->Initialize()) != ZX_OK) {
35         pci_errorf("failed to initialize bus driver: %d!\n", status);
36         return status;
37     }
38 
39     // Grab the info beforehand so we can get segment/bus information from it
40     // and appropriately name our DDK device.
41     pci_platform_info_t info;
42     status = bus->pciroot().GetPciPlatformInfo(&info);
43     if (status != ZX_OK) {
44         pci_errorf("failed to obtain platform information: %d!\n", status);
45         return status;
46     }
47 
48     // Name the bus instance with segment group and bus range, for example:
49     // pci[0][0:255] for a legacy pci bus in segment group 0.
50     char name[32];
51     snprintf(name, sizeof(name), "pci[%u][%u:%u]", info.segment_group, info.start_bus_num,
52              info.end_bus_num);
53 
54     return bus->DdkAdd(name);
55 }
56 
Initialize()57 zx_status_t Bus::Initialize() {
58     zx_status_t status = pciroot_.GetPciPlatformInfo(&info_);
59     if (status != ZX_OK) {
60         pci_errorf("failed to obtain platform information: %d!\n", status);
61         return status;
62     }
63 
64     if (info_.ecam_vmo != ZX_HANDLE_INVALID) {
65         if ((status = MapEcam()) != ZX_OK) {
66             pci_errorf("failed to map ecam: %d!\n", status);
67             return status;
68         }
69     }
70 
71     ScanDownstream();
72     return ZX_OK;
73 }
74 
75 // Maps a vmo as an mmio_buffer to be used as this Bus driver's ECAM region
76 // for config space access.
MapEcam(void)77 zx_status_t Bus::MapEcam(void) {
78     ZX_DEBUG_ASSERT(info_.ecam_vmo != ZX_HANDLE_INVALID);
79 
80     size_t size;
81     zx_status_t status = zx_vmo_get_size(info_.ecam_vmo, &size);
82     if (status != ZX_OK) {
83         pci_errorf("couldn't get ecam vmo size: %d!\n", status);
84         return status;
85     }
86 
87     status = mmio_buffer_init(&ecam_, 0, size, info_.ecam_vmo, ZX_CACHE_POLICY_UNCACHED);
88     if (status != ZX_OK) {
89         pci_errorf("couldn't map ecam vmo: %d!\n", status);
90         return status;
91     }
92 
93     pci_infof("ecam for segment %u mapped at %p (size: %#zx)\n", info_.segment_group,
94               ecam_.vaddr, ecam_.size);
95     has_ecam_ = true;
96     return ZX_OK;
97 }
98 
MakeConfig(pci_bdf_t bdf,fbl::RefPtr<Config> * config)99 zx_status_t Bus::MakeConfig(pci_bdf_t bdf, fbl::RefPtr<Config>* config) {
100     if (has_ecam_) {
101         return MmioConfig::Create(bdf, &ecam_, info_.start_bus_num, info_.end_bus_num, config);
102     } else {
103         return ProxyConfig::Create(bdf, &pciroot_, config);
104     }
105 }
106 
107 // Scan downstream starting at the start bus number provided to use by the platform.
108 // In the process of scanning, take note of bridges found and configure any that are
109 // unconfigured. In the end the Bus should have a list of all devides, and all bridges
110 // should have a list of references to their own downstream devices.
ScanDownstream(void)111 zx_status_t Bus::ScanDownstream(void) {
112     pci_infof("ScanDownstream %u:%u\n", info_.start_bus_num, info_.end_bus_num);
113     for (uint16_t bus_id = info_.start_bus_num; bus_id <= info_.end_bus_num; bus_id++) {
114         for (uint8_t dev_id = 0; dev_id < PCI_MAX_DEVICES_PER_BUS; dev_id++) {
115             for (uint8_t func_id = 0; func_id < PCI_MAX_FUNCTIONS_PER_DEVICE; func_id++) {
116                 fbl::RefPtr<Config> config;
117                 pci_bdf_t bdf = { static_cast<uint8_t>(bus_id), dev_id, func_id };
118                 zx_status_t status = MakeConfig(bdf, &config);
119                 if (status == ZX_OK) {
120                     if (config->vendor_id() != 0xFFFF) {
121                         pci_infof("found device at %02x:%02x.%1x\n", bus_id, dev_id, func_id);
122                     }
123                 }
124             }
125         }
126     }
127 
128     return ZX_OK;
129 }
130 
DdkRelease()131 void Bus::DdkRelease() {
132     if (ecam_.vaddr) {
133         mmio_buffer_release(&ecam_);
134     }
135     delete this;
136 }
137 
138 } // namespace pci
139 
pci_bus_bind(void * ctx,zx_device_t * parent)140 extern "C" zx_status_t pci_bus_bind(void* ctx, zx_device_t* parent) {
141     return pci::Bus::Create(parent);
142 }
143