blob: 0d9cc6e1935caa2a2d5a5a8caba59696ec4e8337 [file] [log] [blame] [view]
<!--
(C) 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.
-->
# Configuration
Hardware peripherals are attached to the CPU through a bus, such as the PCI bus.
During bootup, the BIOS (or equivalent platform startup software)
discovers all of the peripherals attached to the PCI bus.
Each peripheral is assigned resources (notably interrupt vectors,
and address ranges for configuration registers).
The impact of this is that the actual resources assigned to each peripheral may
be different across reboots.
When the operating system software starts up, it enumerates
the bus and starts drivers for all supported devices.
The drivers then call PCI functions in order to obtain configuration information about
their device(s) so that they can map registers and bind to interrupts.
## Base address register
The Base Address Register (**BAR**) is a configuration register that exists on each
PCI device.
It's where the BIOS stores information about the device, such as the assigned interrupt vector
and addresses of control registers.
Other, device specific information, is stored there as well.
Call **pci_map_bar()**
to cause the BAR register to be mapped into the driver host's address space:
```c
zx_status_t pci_map_bar(const pci_protocol_t* pci, uint32_t bar_id,
uint32_t cache_policy, void** vaddr, size_t* size,
zx_handle_t* out_handle);
```
The first parameter, `pci`, is a pointer to the PCI protocol.
Typically, you obtain this in your **bind()** function through
**device_get_protocol()**.
The second parameter, `bar_id`, is the BAR register number, starting with `0`.
The third parameter, `cache_policy`, determines the caching policy for access,
and can take on the following values:
`cache_policy` value | Meaning
------------------------------------|---------------------
`ZX_CACHE_POLICY_CACHED` | use hardware caching
`ZX_CACHE_POLICY_UNCACHED` | disable caching
`ZX_CACHE_POLICY_UNCACHED_DEVICE` | disable caching, and treat as device memory
`ZX_CACHE_POLICY_WRITE_COMBINING` | uncached with write combining
Note that `ZX_CACHE_POLICY_UNCACHED_DEVICE` is architecture dependent
and may in fact be equivalent to `ZX_CACHE_POLICY_UNCACHED` on some architectures.
The next three arguments are return values.
The `vaddr` and `size` return a pointer (and length) of the register region, while
`out_handle` stores the created handle to the
[VMO](/docs/reference/kernel_objects/vm_object.md).
## Reading and writing memory
Once the **pci_map_bar()**
function returns with a valid result, you can access the BAR with simple pointer
operations, for example:
```c
volatile uint32_t* base;
...
zx_status_t rc;
rc = pci_map_bar(dev->pci, 0, ZX_CACHE_POLICY_UNCACHED_DEVICE, &base, &size, &handle);
if (rc == ZX_OK) {
base[REGISTER_X] = 0x1234; // configure register X for deep sleep mode
}
```
It's important to declare `base` as `volatile` &mdash; this tells the compiler not to
make any assumptions about the contents of the data that `base` points to.
For example:
```c
int timeout = 1000;
while (timeout-- > 0 && !(base[REGISTER_READY] & READY_BIT)) ;
```
is a typical (bounded) polling loop, intended for short polling sequences.
Without the `volatile` keyword in the declaration, the compiler would have no reason
to believe that the value at `base[REGISTER_READY]` would ever change, so it would
cause it to be read only once.