blob: c22f6d89a47286ad850841386190f0bdb38d24bf [file] [log] [blame]
Jeremy Lintonaaaaecd2020-11-18 10:11:33 -06001/*
Mario Bălănicăda8a3902024-03-06 06:10:52 +02002 * Copyright (c) 2021-2024, Arm Limited and Contributors. All rights reserved.
3 * Copyright (c) 2024, Mario Bălănică <mariobalanica02@gmail.com>
Jeremy Lintonaaaaecd2020-11-18 10:11:33 -06004 *
5 * SPDX-License-Identifier: BSD-3-Clause
6 *
Mario Bălănicăda8a3902024-03-06 06:10:52 +02007 * The RPi has a single nonstandard PCI config region. It is broken into two
Jeremy Lintonaaaaecd2020-11-18 10:11:33 -06008 * pieces, the root port config registers and a window to a single device's
9 * config space which can move between devices. There isn't (yet) an
10 * authoritative public document on this since the available BCM2711 reference
11 * notes that there is a PCIe root port in the memory map but doesn't describe
12 * it. Given that it's not ECAM compliant yet reasonably simple, it makes for
13 * an excellent example of the PCI SMCCC interface.
14 *
Elyes Haouas2be03c02023-02-13 09:14:48 +010015 * The PCI SMCCC interface is described in DEN0115 available from:
Jeremy Lintonaaaaecd2020-11-18 10:11:33 -060016 * https://developer.arm.com/documentation/den0115/latest
17 */
18
19#include <assert.h>
20#include <stdint.h>
21
22#include <common/debug.h>
23#include <common/runtime_svc.h>
24#include <lib/pmf/pmf.h>
25#include <lib/runtime_instr.h>
26#include <services/pci_svc.h>
27#include <services/sdei.h>
28#include <services/std_svc.h>
29#include <smccc_helpers.h>
30
31#include <lib/mmio.h>
32
Jeremy Lintonaaaaecd2020-11-18 10:11:33 -060033#define PCIE_MISC_PCIE_STATUS 0x4068
34#define PCIE_EXT_CFG_INDEX 0x9000
Jeremy Lintonaaaaecd2020-11-18 10:11:33 -060035#define PCIE_EXT_CFG_DATA 0x8000
Mario Bălănicăda8a3902024-03-06 06:10:52 +020036#define PCIE_EXT_CFG_BDF_SHIFT 12
37
Jeremy Lintonaaaaecd2020-11-18 10:11:33 -060038#define INVALID_PCI_ADDR 0xFFFFFFFF
39
Mario Bălănicăda8a3902024-03-06 06:10:52 +020040static spinlock_t pci_lock;
Jeremy Lintonaaaaecd2020-11-18 10:11:33 -060041
Mario Bălănicăda8a3902024-03-06 06:10:52 +020042static uint64_t pcie_rc_bases[] = { RPI_PCIE_RC_BASES };
Jeremy Lintonaaaaecd2020-11-18 10:11:33 -060043
44static uint64_t pci_segment_lib_get_base(uint32_t address, uint32_t offset)
45{
Mario Bălănicăda8a3902024-03-06 06:10:52 +020046 uint64_t base;
47 uint32_t seg, bus, dev, fun;
Jeremy Lintonaaaaecd2020-11-18 10:11:33 -060048
Mario Bălănicăda8a3902024-03-06 06:10:52 +020049 seg = PCI_ADDR_SEG(address);
Jeremy Lintonaaaaecd2020-11-18 10:11:33 -060050
Mario Bălănicăda8a3902024-03-06 06:10:52 +020051 if (seg >= ARRAY_SIZE(pcie_rc_bases)) {
52 return INVALID_PCI_ADDR;
53 }
Jeremy Lintonaaaaecd2020-11-18 10:11:33 -060054
55 /* The root port is at the base of the PCIe register space */
Mario Bălănicăda8a3902024-03-06 06:10:52 +020056 base = pcie_rc_bases[seg];
Jeremy Lintonaaaaecd2020-11-18 10:11:33 -060057
Mario Bălănicăda8a3902024-03-06 06:10:52 +020058 bus = PCI_ADDR_BUS(address);
59 dev = PCI_ADDR_DEV(address);
60 fun = PCI_ADDR_FUN(address);
Jeremy Lintonaaaaecd2020-11-18 10:11:33 -060061
Mario Bălănicăda8a3902024-03-06 06:10:52 +020062 /* There can only be the root port on bus 0 */
63 if ((bus == 0U) && ((dev > 0U) || (fun > 0U))) {
64 return INVALID_PCI_ADDR;
65 }
66
67 /* There can only be one device on bus 1 */
68 if ((bus == 1U) && (dev > 0U)) {
69 return INVALID_PCI_ADDR;
70 }
71
72 if (bus > 0) {
73#if RPI_PCIE_ECAM_SERROR_QUIRK
74 uint32_t status = mmio_read_32(base + PCIE_MISC_PCIE_STATUS);
75
76 /* Assure link up before accessing downstream of root port */
77 if ((status & 0x30) == 0U) {
Jeremy Lintonaaaaecd2020-11-18 10:11:33 -060078 return INVALID_PCI_ADDR;
79 }
Mario Bălănicăda8a3902024-03-06 06:10:52 +020080#endif
81 /*
82 * Device function is mapped at CFG_DATA, a 4 KB window
83 * movable by writing its B/D/F location to CFG_INDEX.
84 */
85 mmio_write_32(base + PCIE_EXT_CFG_INDEX, address << PCIE_EXT_CFG_BDF_SHIFT);
86 base += PCIE_EXT_CFG_DATA;
Jeremy Lintonaaaaecd2020-11-18 10:11:33 -060087 }
Mario Bălănicăda8a3902024-03-06 06:10:52 +020088
89 return base + (offset & PCI_OFFSET_MASK);
Jeremy Lintonaaaaecd2020-11-18 10:11:33 -060090}
91
92/**
93 * pci_read_config() - Performs a config space read at addr
94 * @addr: 32-bit, segment, BDF of requested function encoded per DEN0115
95 * @off: register offset of function described by @addr to read
96 * @sz: size of read (8,16,32) bits.
97 * @val: returned zero extended value read from config space
98 *
99 * sz bits of PCI config space is read at addr:offset, and the value
100 * is returned in val. Invalid segment/offset values return failure.
101 * Reads to valid functions that don't exist return INVALID_PCI_ADDR
102 * as is specified by PCI for requests that aren't completed by EPs.
103 * The boilerplate in pci_svc.c tends to do basic segment, off
104 * and sz validation. This routine should avoid duplicating those
105 * checks.
106 *
107 * This function maps directly to the PCI_READ function in DEN0115
108 * where detailed requirements may be found.
109 *
110 * Return: SMC_PCI_CALL_SUCCESS with val set
111 * SMC_PCI_CALL_INVAL_PARAM, on parameter error
112 */
113uint32_t pci_read_config(uint32_t addr, uint32_t off, uint32_t sz, uint32_t *val)
114{
115 uint32_t ret = SMC_PCI_CALL_SUCCESS;
116 uint64_t base;
117
118 spin_lock(&pci_lock);
119 base = pci_segment_lib_get_base(addr, off);
120
121 if (base == INVALID_PCI_ADDR) {
122 *val = base;
123 } else {
124 switch (sz) {
125 case SMC_PCI_SZ_8BIT:
126 *val = mmio_read_8(base);
127 break;
128 case SMC_PCI_SZ_16BIT:
129 *val = mmio_read_16(base);
130 break;
131 case SMC_PCI_SZ_32BIT:
132 *val = mmio_read_32(base);
133 break;
134 default: /* should be unreachable */
Mario Bălănicăda8a3902024-03-06 06:10:52 +0200135 *val = 0U;
Jeremy Lintonaaaaecd2020-11-18 10:11:33 -0600136 ret = SMC_PCI_CALL_INVAL_PARAM;
137 }
138 }
139 spin_unlock(&pci_lock);
140 return ret;
141}
142
143/**
144 * pci_write_config() - Performs a config space write at addr
145 * @addr: 32-bit, segment, BDF of requested function encoded per DEN0115
146 * @off: register offset of function described by @addr to write
147 * @sz: size of write (8,16,32) bits.
148 * @val: value to be written
149 *
150 * sz bits of PCI config space is written at addr:offset. Invalid
151 * segment/BDF values return failure. Writes to valid functions
152 * without valid EPs are ignored, as is specified by PCI.
153 * The boilerplate in pci_svc.c tends to do basic segment, off
154 * and sz validation, so it shouldn't need to be repeated here.
155 *
156 * This function maps directly to the PCI_WRITE function in DEN0115
157 * where detailed requirements may be found.
158 *
159 * Return: SMC_PCI_CALL_SUCCESS
160 * SMC_PCI_CALL_INVAL_PARAM, on parameter error
161 */
162uint32_t pci_write_config(uint32_t addr, uint32_t off, uint32_t sz, uint32_t val)
163{
164 uint32_t ret = SMC_PCI_CALL_SUCCESS;
165 uint64_t base;
166
167 spin_lock(&pci_lock);
168 base = pci_segment_lib_get_base(addr, off);
169
170 if (base != INVALID_PCI_ADDR) {
171 switch (sz) {
172 case SMC_PCI_SZ_8BIT:
173 mmio_write_8(base, val);
174 break;
175 case SMC_PCI_SZ_16BIT:
176 mmio_write_16(base, val);
177 break;
178 case SMC_PCI_SZ_32BIT:
179 mmio_write_32(base, val);
180 break;
181 default: /* should be unreachable */
182 ret = SMC_PCI_CALL_INVAL_PARAM;
183 }
184 }
185 spin_unlock(&pci_lock);
186 return ret;
187}
188
189/**
190 * pci_get_bus_for_seg() - returns the start->end bus range for a segment
191 * @seg: segment being queried
192 * @bus_range: returned bus begin + (end << 8)
193 * @nseg: returns next segment in this machine or 0 for end
194 *
195 * pci_get_bus_for_seg is called to check if a given segment is
196 * valid on this machine. If it is valid, then its bus ranges are
197 * returned along with the next valid segment on the machine. If
198 * this is the last segment, then nseg must be 0.
199 *
200 * This function maps directly to the PCI_GET_SEG_INFO function
201 * in DEN0115 where detailed requirements may be found.
202 *
203 * Return: SMC_PCI_CALL_SUCCESS, and appropriate bus_range and nseg
204 * SMC_PCI_CALL_NOT_IMPL, if the segment is invalid
205 */
206uint32_t pci_get_bus_for_seg(uint32_t seg, uint32_t *bus_range, uint32_t *nseg)
207{
208 uint32_t ret = SMC_PCI_CALL_SUCCESS;
Mario Bălănicăda8a3902024-03-06 06:10:52 +0200209 uint32_t rc_count = ARRAY_SIZE(pcie_rc_bases);
210
211 *nseg = (seg < rc_count - 1U) ? seg + 1U : 0U;
212
213 if (seg < rc_count) {
214 *bus_range = 0U + (0xFF << 8); /* start 0, end 255 */
Jeremy Lintonaaaaecd2020-11-18 10:11:33 -0600215 } else {
216 *bus_range = 0U;
217 ret = SMC_PCI_CALL_NOT_IMPL;
218 }
219 return ret;
220}