1// Copyright 2016 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 <err.h>
8#include <platform.h>
9#include <stdint.h>
10#include <stdio.h>
11#include <stdlib.h>
12#include <string.h>
13#include <trace.h>
14
15#include <dev/interrupt.h>
16#include <lib/pci/pio.h>
17#include <lib/user_copy/user_ptr.h>
18#include <object/handle.h>
19#include <object/process_dispatcher.h>
20#include <object/resource.h>
21#include <object/vm_object_dispatcher.h>
22#include <vm/vm_object_physical.h>
23
24#include <fbl/algorithm.h>
25#include <fbl/alloc_checker.h>
26#include <fbl/limits.h>
27#include <fbl/ref_ptr.h>
28#include <fbl/unique_free_ptr.h>
29#include <zircon/syscalls/pci.h>
30
31#include "priv.h"
32
33#define LOCAL_TRACE 0
34
35// If we were built with the GFX console, make sure that it is un-bound when
36// user mode takes control of PCI.  Note: there should probably be a cleaner way
37// of doing this.  Not all system have PCI, and (eventually) not all systems
38// will attempt to initialize PCI.  Someday, there should be a different way of
39// handing off from early/BSOD kernel mode graphics to user mode.
40#include <lib/gfxconsole.h>
41static inline void shutdown_early_init_console() {
42    gfxconsole_bind_display(nullptr, nullptr);
43}
44
45#ifdef WITH_KERNEL_PCIE
46#include <dev/pcie_bus_driver.h>
47#include <dev/pcie_root.h>
48#include <object/pci_device_dispatcher.h>
49
50// Implementation of a PcieRoot with a look-up table based legacy IRQ swizzler
51// suitable for use with ACPI style swizzle definitions.
52class PcieRootLUTSwizzle : public PcieRoot {
53public:
54    static fbl::RefPtr<PcieRoot> Create(PcieBusDriver& bus_drv,
55                                        uint managed_bus_id,
56                                        const zx_pci_irq_swizzle_lut_t& lut) {
57        fbl::AllocChecker ac;
58        auto root = fbl::AdoptRef(new (&ac) PcieRootLUTSwizzle(bus_drv,
59                                                               managed_bus_id,
60                                                               lut));
61        if (!ac.check()) {
62            TRACEF("Out of memory attemping to create PCIe root to manage bus ID 0x%02x\n",
63                   managed_bus_id);
64            return nullptr;
65        }
66
67        return root;
68    }
69
70    zx_status_t Swizzle(uint dev_id, uint func_id, uint pin, uint* irq) override {
71        if ((irq == nullptr) ||
72            (dev_id >= fbl::count_of(lut_)) ||
73            (func_id >= fbl::count_of(lut_[dev_id])) ||
74            (pin >= fbl::count_of(lut_[dev_id][func_id])))
75            return ZX_ERR_INVALID_ARGS;
76
77        *irq = lut_[dev_id][func_id][pin];
78        return (*irq == ZX_PCI_NO_IRQ_MAPPING) ? ZX_ERR_NOT_FOUND : ZX_OK;
79    }
80
81private:
82    PcieRootLUTSwizzle(PcieBusDriver& bus_drv,
83                       uint managed_bus_id,
84                       const zx_pci_irq_swizzle_lut_t& lut)
85        : PcieRoot(bus_drv, managed_bus_id) {
86        ::memcpy(&lut_, &lut, sizeof(lut_));
87    }
88
89    zx_pci_irq_swizzle_lut_t lut_;
90};
91
92// Scan |lut| for entries mapping to |irq|, and replace them with
93// ZX_PCI_NO_IRQ_MAPPING.
94static void pci_irq_swizzle_lut_remove_irq(zx_pci_irq_swizzle_lut_t* lut, uint32_t irq) {
95    for (size_t dev = 0; dev < fbl::count_of(*lut); ++dev) {
96        for (size_t func = 0; func < fbl::count_of((*lut)[dev]); ++func) {
97            for (size_t pin = 0; pin < fbl::count_of((*lut)[dev][func]); ++pin) {
98                uint32_t* assigned_irq = &(*lut)[dev][func][pin];
99                if (*assigned_irq == irq) {
100                    *assigned_irq = ZX_PCI_NO_IRQ_MAPPING;
101                }
102            }
103        }
104    }
105}
106
107zx_status_t sys_pci_add_subtract_io_range(zx_handle_t handle, bool mmio, uint64_t base, uint64_t len, bool add) {
108
109    LTRACEF("handle %x mmio %d base %#" PRIx64 " len %#" PRIx64 " add %d\n", handle, mmio, base, len, add);
110
111    // TODO(ZX-971): finer grained validation
112    // TODO(security): Add additional access checks
113    zx_status_t status;
114    if ((status = validate_resource(handle, ZX_RSRC_KIND_ROOT)) < 0) {
115        return status;
116    }
117
118    auto pcie = PcieBusDriver::GetDriver();
119    if (pcie == nullptr) {
120        return ZX_ERR_BAD_STATE;
121    }
122
123    PciAddrSpace addr_space = mmio ? PciAddrSpace::MMIO : PciAddrSpace::PIO;
124
125    if (add) {
126        return pcie->AddBusRegion(base, len, addr_space);
127    } else {
128        return pcie->SubtractBusRegion(base, len, addr_space);
129    }
130}
131
132zx_status_t sys_pci_init(zx_handle_t handle, user_in_ptr<const zx_pci_init_arg_t> _init_buf, uint32_t len) {
133    // TODO(ZX-971): finer grained validation
134    // TODO(security): Add additional access checks
135    zx_status_t status;
136    if ((status = validate_resource(handle, ZX_RSRC_KIND_ROOT)) < 0) {
137        return status;
138    }
139
140    fbl::unique_free_ptr<zx_pci_init_arg_t> arg;
141
142    if (len < sizeof(*arg) || len > ZX_PCI_INIT_ARG_MAX_SIZE) {
143        return ZX_ERR_INVALID_ARGS;
144    }
145
146    auto pcie = PcieBusDriver::GetDriver();
147    if (pcie == nullptr)
148        return ZX_ERR_BAD_STATE;
149
150    // we have to malloc instead of new since this is a variable-sized structure
151    arg.reset(static_cast<zx_pci_init_arg_t*>(malloc(len)));
152    if (!arg) {
153        return ZX_ERR_NO_MEMORY;
154    }
155    {
156        status = _init_buf.reinterpret<const void>().copy_array_from_user(
157            arg.get(), len);
158        if (status != ZX_OK) {
159            return status;
160        }
161    }
162
163    if (LOCAL_TRACE) {
164        TRACEF("%u address window%s found in init arg\n", arg->addr_window_count,
165               (arg->addr_window_count == 1) ? "" : "s");
166        for (uint32_t i = 0; i < arg->addr_window_count; i++) {
167            printf("[%u]\n\tis_mmio: %d\n\thas_ecam: %d\n\tbase: %#" PRIxPTR "\n"
168                   "\tsize: %zu\n\tbus_start: %u\n\tbus_end: %u\n",
169                   i,
170                   arg->addr_windows[i].is_mmio, arg->addr_windows[i].has_ecam,
171                   arg->addr_windows[i].base, arg->addr_windows[i].size,
172                   arg->addr_windows[i].bus_start, arg->addr_windows[i].bus_end);
173        }
174    }
175
176    const uint32_t win_count = arg->addr_window_count;
177    if (len != sizeof(*arg) + sizeof(arg->addr_windows[0]) * win_count) {
178        return ZX_ERR_INVALID_ARGS;
179    }
180
181    if (arg->num_irqs > fbl::count_of(arg->irqs)) {
182        return ZX_ERR_INVALID_ARGS;
183    }
184
185    // Configure interrupts
186    for (unsigned int i = 0; i < arg->num_irqs; ++i) {
187        uint32_t irq = arg->irqs[i].global_irq;
188        if (!is_valid_interrupt(irq, 0)) {
189            // If the interrupt isn't valid, mask it out of the IRQ swizzle table
190            // and don't activate it.  Attempts to use legacy IRQs for the device
191            // will fail later.
192            arg->irqs[i].global_irq = ZX_PCI_NO_IRQ_MAPPING;
193            pci_irq_swizzle_lut_remove_irq(&arg->dev_pin_to_global_irq, irq);
194            continue;
195        }
196
197        enum interrupt_trigger_mode tm = IRQ_TRIGGER_MODE_EDGE;
198        if (arg->irqs[i].level_triggered) {
199            tm = IRQ_TRIGGER_MODE_LEVEL;
200        }
201        enum interrupt_polarity pol = IRQ_POLARITY_ACTIVE_LOW;
202        if (arg->irqs[i].active_high) {
203            pol = IRQ_POLARITY_ACTIVE_HIGH;
204        }
205
206        zx_status_t status = configure_interrupt(irq, tm, pol);
207        if (status != ZX_OK) {
208            return status;
209        }
210    }
211    // TODO(teisenbe): For now assume there is only one ECAM
212    if (win_count != 1) {
213        return ZX_ERR_INVALID_ARGS;
214    }
215
216    if (arg->addr_windows[0].bus_start != 0) {
217        return ZX_ERR_INVALID_ARGS;
218    }
219
220    if (arg->addr_windows[0].bus_start > arg->addr_windows[0].bus_end) {
221        return ZX_ERR_INVALID_ARGS;
222    }
223
224#if ARCH_X86
225    // Check for a quirk that we've seen.  Some systems will report overly large
226    // PCIe config regions that collide with architectural registers.
227    unsigned int num_buses = arg->addr_windows[0].bus_end -
228                             arg->addr_windows[0].bus_start + 1;
229    paddr_t end = arg->addr_windows[0].base +
230                  num_buses * PCIE_ECAM_BYTE_PER_BUS;
231    const paddr_t high_limit = 0xfec00000ULL;
232    if (end > high_limit) {
233        TRACEF("PCIe config space collides with arch devices, truncating\n");
234        end = high_limit;
235        if (end < arg->addr_windows[0].base) {
236            return ZX_ERR_INVALID_ARGS;
237        }
238        arg->addr_windows[0].size = ROUNDDOWN(end - arg->addr_windows[0].base,
239                                              PCIE_ECAM_BYTE_PER_BUS);
240        uint64_t new_bus_end = (arg->addr_windows[0].size / PCIE_ECAM_BYTE_PER_BUS) +
241                               arg->addr_windows[0].bus_start - 1;
242        if (new_bus_end >= PCIE_MAX_BUSSES) {
243            return ZX_ERR_INVALID_ARGS;
244        }
245        arg->addr_windows[0].bus_end = static_cast<uint8_t>(new_bus_end);
246    }
247#endif
248
249    if (arg->addr_windows[0].is_mmio) {
250        if (arg->addr_windows[0].size < PCIE_ECAM_BYTE_PER_BUS) {
251            return ZX_ERR_INVALID_ARGS;
252        }
253        if (arg->addr_windows[0].size / PCIE_ECAM_BYTE_PER_BUS >
254            PCIE_MAX_BUSSES - arg->addr_windows[0].bus_start) {
255
256            return ZX_ERR_INVALID_ARGS;
257        }
258
259        // TODO(johngro): Update the syscall to pass a paddr_t for base instead of a uint64_t
260        ASSERT(arg->addr_windows[0].base < fbl::numeric_limits<paddr_t>::max());
261
262        // TODO(johngro): Do not limit this to a single range.  Instead, fetch all
263        // of the ECAM ranges from ACPI, as well as the appropriate bus start/end
264        // ranges.
265        const PcieBusDriver::EcamRegion ecam = {
266            .phys_base = static_cast<paddr_t>(arg->addr_windows[0].base),
267            .size = arg->addr_windows[0].size,
268            .bus_start = 0x00,
269            .bus_end = static_cast<uint8_t>((arg->addr_windows[0].size / PCIE_ECAM_BYTE_PER_BUS) - 1),
270        };
271
272        zx_status_t ret = pcie->AddEcamRegion(ecam);
273        if (ret != ZX_OK) {
274            TRACEF("Failed to add ECAM region to PCIe bus driver! (ret %d)\n", ret);
275            return ret;
276        }
277    }
278
279    // TODO(johngro): Change the user-mode and devmgr behavior to add all of the
280    // roots in the system.  Do not assume that there is a single root, nor that
281    // it manages bus ID 0.
282    auto root = PcieRootLUTSwizzle::Create(*pcie, 0, arg->dev_pin_to_global_irq);
283    if (root == nullptr)
284        return ZX_ERR_NO_MEMORY;
285
286    // Enable PIO config space if the address window was not MMIO
287    pcie->EnablePIOWorkaround(!arg->addr_windows[0].is_mmio);
288
289    zx_status_t ret = pcie->AddRoot(fbl::move(root));
290    if (ret != ZX_OK) {
291        TRACEF("Failed to add root complex to PCIe bus driver! (ret %d)\n", ret);
292        return ret;
293    }
294
295    ret = pcie->StartBusDriver();
296    if (ret != ZX_OK) {
297        TRACEF("Failed to start PCIe bus driver! (ret %d)\n", ret);
298        return ret;
299    }
300
301    shutdown_early_init_console();
302    return ZX_OK;
303}
304
305zx_status_t sys_pci_get_nth_device(zx_handle_t hrsrc,
306                                   uint32_t index,
307                                   user_out_ptr<zx_pcie_device_info_t> out_info,
308                                   user_out_handle* out_handle) {
309    /**
310     * Returns the pci config of a device.
311     * @param index Device index
312     * @param out_info Device info (BDF address, vendor id, etc...)
313     */
314    LTRACEF("handle %x index %u\n", hrsrc, index);
315
316    // TODO(ZX-971): finer grained validation
317    zx_status_t status;
318    if ((status = validate_resource(hrsrc, ZX_RSRC_KIND_ROOT)) < 0) {
319        return status;
320    }
321
322    if (!out_info) {
323        return ZX_ERR_INVALID_ARGS;
324    }
325
326    fbl::RefPtr<Dispatcher> dispatcher;
327    zx_rights_t rights;
328    zx_pcie_device_info_t info;
329    status = PciDeviceDispatcher::Create(index, &info, &dispatcher, &rights);
330    if (status != ZX_OK) {
331        return status;
332    }
333
334    // If everything is successful add the handle to the process
335    status = out_info.copy_to_user(info);
336    if (status != ZX_OK)
337        return status;
338
339    return out_handle->make(fbl::move(dispatcher), rights);
340}
341
342zx_status_t sys_pci_config_read(zx_handle_t handle, uint16_t offset, size_t width,
343                                user_out_ptr<uint32_t> out_val) {
344    fbl::RefPtr<PciDeviceDispatcher> pci_device;
345    fbl::RefPtr<Dispatcher> dispatcher;
346
347    // Get the PciDeviceDispatcher from the handle passed in via the pci protocol
348    auto up = ProcessDispatcher::GetCurrent();
349    zx_status_t status = up->GetDispatcherWithRights(handle, ZX_RIGHT_READ | ZX_RIGHT_WRITE,
350                                                     &pci_device);
351    if (status != ZX_OK) {
352        return status;
353    }
354
355    auto device = pci_device->device();
356    auto cfg_size = device->is_pcie() ? PCIE_EXTENDED_CONFIG_SIZE : PCIE_BASE_CONFIG_SIZE;
357    if (out_val.get() == nullptr || offset + width > cfg_size) {
358        return ZX_ERR_INVALID_ARGS;
359    }
360
361    // Based on the width passed in we can use the type safety of the PciConfig layer
362    // to ensure we're getting correctly sized data back and return errors in the PIO
363    // cases.
364    auto config = device->config();
365    switch (width) {
366    case 1u:
367        return out_val.copy_to_user(static_cast<uint32_t>(config->Read(PciReg8(offset))));
368    case 2u:
369        return out_val.copy_to_user(static_cast<uint32_t>(config->Read(PciReg16(offset))));
370    case 4u:
371        return out_val.copy_to_user(config->Read(PciReg32(offset)));
372    }
373
374    // If we reached this point then the width was invalid.
375    return ZX_ERR_INVALID_ARGS;
376}
377
378zx_status_t sys_pci_config_write(zx_handle_t handle, uint16_t offset, size_t width, uint32_t val) {
379    fbl::RefPtr<PciDeviceDispatcher> pci_device;
380    fbl::RefPtr<Dispatcher> dispatcher;
381
382    // Get the PciDeviceDispatcher from the handle passed in via the pci protocol
383    auto up = ProcessDispatcher::GetCurrent();
384    zx_status_t status = up->GetDispatcherWithRights(handle, ZX_RIGHT_READ | ZX_RIGHT_WRITE,
385                                                     &pci_device);
386    if (status != ZX_OK) {
387        return status;
388    }
389
390    // Writes to the PCI header or outside of the device's config space are not allowed.
391    auto device = pci_device->device();
392    auto cfg_size = device->is_pcie() ? PCIE_EXTENDED_CONFIG_SIZE : PCIE_BASE_CONFIG_SIZE;
393    if (offset < ZX_PCI_STANDARD_CONFIG_HDR_SIZE || offset + width > cfg_size) {
394        return ZX_ERR_INVALID_ARGS;
395    }
396
397    auto config = device->config();
398    switch (width) {
399    case 1u:
400        config->Write(PciReg8(offset), static_cast<uint8_t>(val & UINT8_MAX));
401        break;
402    case 2u:
403        config->Write(PciReg16(offset), static_cast<uint16_t>(val & UINT16_MAX));
404        break;
405    case 4u:
406        config->Write(PciReg32(offset), val);
407        break;
408    default:
409        return ZX_ERR_INVALID_ARGS;
410    }
411
412    return ZX_OK;
413}
414/* This is a transitional method to bootstrap legacy PIO access before
415 * PCI moves to userspace.
416 */
417zx_status_t sys_pci_cfg_pio_rw(zx_handle_t handle, uint8_t bus, uint8_t dev, uint8_t func,
418                               uint8_t offset, user_inout_ptr<uint32_t> val, size_t width, bool write) {
419#if ARCH_X86
420    uint32_t val_;
421    zx_status_t status = validate_resource(handle, ZX_RSRC_KIND_ROOT);
422    if (status != ZX_OK) {
423        return status;
424    }
425
426    if (write) {
427        val.copy_from_user(&val_);
428        status = Pci::PioCfgWrite(bus, dev, func, offset, val_, width);
429    } else {
430        status = Pci::PioCfgRead(bus, dev, func, offset, &val_, width);
431        if (status == ZX_OK) {
432            val.copy_to_user(val_);
433        }
434    }
435
436    return status;
437#else
438    return ZX_ERR_NOT_SUPPORTED;
439#endif
440}
441
442zx_status_t sys_pci_enable_bus_master(zx_handle_t dev_handle, bool enable) {
443    /**
444     * Enables or disables bus mastering for the PCI device associated with the handle.
445     * @param handle Handle associated with a PCI device
446     * @param enable true if bus mastering should be enabled.
447     */
448    LTRACEF("handle %x\n", dev_handle);
449
450    auto up = ProcessDispatcher::GetCurrent();
451
452    fbl::RefPtr<PciDeviceDispatcher> pci_device;
453    zx_status_t status = up->GetDispatcherWithRights(dev_handle, ZX_RIGHT_WRITE, &pci_device);
454    if (status != ZX_OK)
455        return status;
456
457    return pci_device->EnableBusMaster(enable);
458}
459
460zx_status_t sys_pci_reset_device(zx_handle_t dev_handle) {
461    /**
462     * Resets the PCI device associated with the handle.
463     * @param handle Handle associated with a PCI device
464     */
465    LTRACEF("handle %x\n", dev_handle);
466
467    auto up = ProcessDispatcher::GetCurrent();
468
469    fbl::RefPtr<PciDeviceDispatcher> pci_device;
470    zx_status_t status = up->GetDispatcherWithRights(dev_handle, ZX_RIGHT_WRITE, &pci_device);
471    if (status != ZX_OK)
472        return status;
473
474    return pci_device->ResetDevice();
475}
476
477zx_status_t sys_pci_get_bar(zx_handle_t dev_handle,
478                            uint32_t bar_num,
479                            user_out_ptr<zx_pci_bar_t> out_bar,
480                            user_out_handle* out_handle) {
481    if (dev_handle == ZX_HANDLE_INVALID ||
482        !out_bar ||
483        bar_num >= PCIE_MAX_BAR_REGS) {
484        return ZX_ERR_INVALID_ARGS;
485    }
486
487    // Grab the PCI device object
488    auto up = ProcessDispatcher::GetCurrent();
489    fbl::RefPtr<PciDeviceDispatcher> pci_device;
490    zx_status_t status = up->GetDispatcherWithRights(dev_handle,
491                                                     ZX_RIGHT_READ | ZX_RIGHT_WRITE, &pci_device);
492    if (status != ZX_OK) {
493        return status;
494    }
495
496    // Get bar info from the device via the dispatcher and make sure it makes sense
497    const pcie_bar_info_t* info = pci_device->GetBar(bar_num);
498    if (info == nullptr || info->size == 0) {
499        return ZX_ERR_NOT_FOUND;
500    }
501
502    // A bar can be MMIO, PIO, or unused. In the MMIO case it can be passed
503    // back to the caller as a VMO.
504    zx_pci_bar_t bar = {};
505    bar.size = info->size;
506    bar.type = (info->is_mmio) ? ZX_PCI_BAR_TYPE_MMIO : ZX_PCI_BAR_TYPE_PIO;
507
508    // MMIO based bars are passed back using a VMO. If we end up creating one here
509    // without errors then later a handle will be passed back to the caller.
510    fbl::RefPtr<Dispatcher> dispatcher;
511    fbl::RefPtr<VmObject> vmo;
512    zx_rights_t rights;
513    if (info->is_mmio) {
514        // Create a VMO mapping to the address / size of the mmio region this bar
515        // was allocated at
516        status = VmObjectPhysical::Create(info->bus_addr,
517                                          fbl::max<uint64_t>(info->size, PAGE_SIZE), &vmo);
518        if (status != ZX_OK) {
519            return status;
520        }
521
522        // Set the name of the vmo for tracking
523        char name[32];
524        auto dev = pci_device->device();
525        snprintf(name, sizeof(name), "pci-%02x:%02x.%1x-bar%u",
526                 dev->bus_id(), dev->dev_id(), dev->func_id(), bar_num);
527        vmo->set_name(name, sizeof(name));
528
529        // Now that the vmo has been created for the bar, create a handle to
530        // the appropriate dispatcher for the caller
531        status = VmObjectDispatcher::Create(vmo, &dispatcher, &rights);
532        if (status != ZX_OK) {
533            return status;
534        }
535
536        pci_device->EnableMmio(true);
537    } else {
538        DEBUG_ASSERT(info->bus_addr != 0);
539        bar.addr = info->bus_addr;
540        pci_device->EnablePio(true);
541    }
542
543    // Metadata has been sorted out, so copy back the structure to userspace
544    // and then account for the vmo handle if one was created.
545    status = out_bar.copy_to_user(bar);
546    if (status != ZX_OK) {
547        return status;
548    }
549
550    if (vmo) {
551        return out_handle->make(fbl::move(dispatcher), rights);
552    }
553
554    return ZX_OK;
555}
556
557zx_status_t sys_pci_map_interrupt(zx_handle_t dev_handle,
558                                  int32_t which_irq,
559                                  user_out_handle* out_handle) {
560    /**
561     * Returns a handle that can be waited on.
562     * @param handle Handle associated with a PCI device
563     * @param which_irq Identifier for an IRQ, returned in sys_pci_get_nth_device, or -1 for legacy
564     * interrupts
565     * @param out_handle pointer to a handle to associate with the interrupt mapping
566     */
567    LTRACEF("handle %x\n", dev_handle);
568
569    auto up = ProcessDispatcher::GetCurrent();
570
571    fbl::RefPtr<PciDeviceDispatcher> pci_device;
572    zx_status_t status =
573        up->GetDispatcherWithRights(dev_handle, ZX_RIGHT_READ, &pci_device);
574    if (status != ZX_OK)
575        return status;
576
577    fbl::RefPtr<Dispatcher> interrupt_dispatcher;
578    zx_rights_t rights;
579    zx_status_t result = pci_device->MapInterrupt(which_irq, &interrupt_dispatcher, &rights);
580    if (result != ZX_OK)
581        return result;
582
583    return out_handle->make(fbl::move(interrupt_dispatcher), rights);
584}
585
586/**
587 * Gets info about the capabilities of a PCI device's IRQ modes.
588 * @param handle Handle associated with a PCI device.
589 * @param mode The IRQ mode whose capabilities are to be queried.
590 * @param out_len Out param which will hold the maximum number of IRQs supported by the mode.
591 */
592zx_status_t sys_pci_query_irq_mode(zx_handle_t dev_handle,
593                                   uint32_t mode,
594                                   user_out_ptr<uint32_t> out_max_irqs) {
595    LTRACEF("handle %x\n", dev_handle);
596
597    auto up = ProcessDispatcher::GetCurrent();
598
599    fbl::RefPtr<PciDeviceDispatcher> pci_device;
600    zx_status_t status = up->GetDispatcherWithRights(dev_handle, ZX_RIGHT_READ, &pci_device);
601    if (status != ZX_OK)
602        return status;
603
604    uint32_t max_irqs;
605    zx_status_t result = pci_device->QueryIrqModeCaps((zx_pci_irq_mode_t)mode, &max_irqs);
606    if (result != ZX_OK)
607        return result;
608
609    status = out_max_irqs.copy_to_user(max_irqs);
610    if (status != ZX_OK)
611        return status;
612
613    return result;
614}
615
616/**
617 * Selects an IRQ mode for a PCI device.
618 * @param handle Handle associated with a PCI device.
619 * @param mode The IRQ mode to select.
620 * @param requested_irq_count The number of IRQs to select request for the given mode.
621 */
622zx_status_t sys_pci_set_irq_mode(zx_handle_t dev_handle,
623                                 uint32_t mode,
624                                 uint32_t requested_irq_count) {
625    LTRACEF("handle %x\n", dev_handle);
626
627    auto up = ProcessDispatcher::GetCurrent();
628
629    fbl::RefPtr<PciDeviceDispatcher> pci_device;
630    zx_status_t status = up->GetDispatcherWithRights(dev_handle, ZX_RIGHT_WRITE, &pci_device);
631    if (status != ZX_OK)
632        return status;
633
634    return pci_device->SetIrqMode((zx_pci_irq_mode_t)mode, requested_irq_count);
635}
636#else  // WITH_KERNEL_PCIE
637zx_status_t sys_pci_init(zx_handle_t, user_in_ptr<const zx_pci_init_arg_t>, uint32_t) {
638    shutdown_early_init_console();
639    return ZX_OK;
640}
641
642zx_status_t sys_pci_add_subtract_io_range(zx_handle_t handle, bool mmio, uint64_t base, uint64_t len, bool add) {
643    return ZX_ERR_NOT_SUPPORTED;
644}
645
646zx_status_t sys_pci_config_read(zx_handle_t handle, uint16_t offset, size_t width,
647                                user_out_ptr<uint32_t> out_val) {
648    return ZX_ERR_NOT_SUPPORTED;
649}
650
651zx_status_t sys_pci_config_write(zx_handle_t handle, uint16_t offset, size_t width, uint32_t val) {
652    return ZX_ERR_NOT_SUPPORTED;
653}
654
655zx_status_t sys_pci_cfg_pio_rw(zx_handle_t handle, uint8_t bus, uint8_t dev, uint8_t func,
656                               uint8_t offset, user_inout_ptr<uint32_t> val, size_t width, bool write) {
657    return ZX_ERR_NOT_SUPPORTED;
658}
659
660zx_status_t sys_pci_get_nth_device(zx_handle_t, uint32_t, user_inout_ptr<zx_pcie_device_info_t>,
661                                   user_out_handle*) {
662    return ZX_ERR_NOT_SUPPORTED;
663}
664
665zx_status_t sys_pci_enable_bus_master(zx_handle_t, bool) {
666    return ZX_ERR_NOT_SUPPORTED;
667}
668
669zx_status_t sys_pci_reset_device(zx_handle_t) {
670    return ZX_ERR_NOT_SUPPORTED;
671}
672
673zx_status_t sys_pci_get_nth_device(zx_handle_t hrsrc,
674                                   uint32_t index,
675                                   user_out_ptr<zx_pcie_device_info_t> out_info,
676                                   user_out_handle* out_handle) {
677    return ZX_ERR_NOT_SUPPORTED;
678}
679
680zx_status_t sys_pci_get_bar(zx_handle_t dev_handle,
681                            uint32_t bar_num,
682                            user_out_ptr<zx_pci_bar_t> out_bar,
683                            user_out_handle* out_handle) {
684    return ZX_ERR_NOT_SUPPORTED;
685}
686
687zx_status_t sys_pci_map_interrupt(zx_handle_t, int32_t, user_out_handle*) {
688    return ZX_ERR_NOT_SUPPORTED;
689}
690
691zx_status_t sys_pci_query_irq_mode(zx_handle_t, uint32_t, user_out_ptr<uint32_t>) {
692    return ZX_ERR_NOT_SUPPORTED;
693}
694
695zx_status_t sys_pci_set_irq_mode(zx_handle_t, uint32_t, uint32_t) {
696    return ZX_ERR_NOT_SUPPORTED;
697}
698#endif // WITH_KERNEL_PCIE
699