Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
adam.huang
Arm Trusted Firmware
Commits
7bfec3ad
Commit
7bfec3ad
authored
3 years ago
by
Joanna Farley
Committed by
TrustedFirmware Code Review
3 years ago
Browse files
Options
Download
Plain Diff
Merge "rpi4: SMCCC PCI implementation" into integration
parents
743e3b41
ab061eb7
master
No related merge requests found
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
plat/rpi/rpi4/rpi4_pci_svc.c
+215
-0
plat/rpi/rpi4/rpi4_pci_svc.c
with
215 additions
and
0 deletions
+215
-0
plat/rpi/rpi4/rpi4_pci_svc.c
0 → 100644
View file @
7bfec3ad
/*
* Copyright (c) 2021, ARM Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
* The RPi4 has a single nonstandard PCI config region. It is broken into two
* pieces, the root port config registers and a window to a single device's
* config space which can move between devices. There isn't (yet) an
* authoritative public document on this since the available BCM2711 reference
* notes that there is a PCIe root port in the memory map but doesn't describe
* it. Given that it's not ECAM compliant yet reasonably simple, it makes for
* an excellent example of the PCI SMCCC interface.
*
* The PCI SMCCC interface is described in DEN0115 availabe from:
* https://developer.arm.com/documentation/den0115/latest
*/
#include <assert.h>
#include <stdint.h>
#include <common/debug.h>
#include <common/runtime_svc.h>
#include <lib/pmf/pmf.h>
#include <lib/runtime_instr.h>
#include <services/pci_svc.h>
#include <services/sdei.h>
#include <services/std_svc.h>
#include <smccc_helpers.h>
#include <lib/mmio.h>
static
spinlock_t
pci_lock
;
#define PCIE_REG_BASE U(RPI_IO_BASE + 0x01500000)
#define PCIE_MISC_PCIE_STATUS 0x4068
#define PCIE_EXT_CFG_INDEX 0x9000
/* A small window pointing at the ECAM of the device selected by CFG_INDEX */
#define PCIE_EXT_CFG_DATA 0x8000
#define INVALID_PCI_ADDR 0xFFFFFFFF
#define PCIE_EXT_BUS_SHIFT 20
#define PCIE_EXT_DEV_SHIFT 15
#define PCIE_EXT_FUN_SHIFT 12
static
uint64_t
pci_segment_lib_get_base
(
uint32_t
address
,
uint32_t
offset
)
{
uint64_t
base
;
uint32_t
bus
,
dev
,
fun
;
uint32_t
status
;
base
=
PCIE_REG_BASE
;
offset
&=
PCI_OFFSET_MASK
;
/* Pick off the 4k register offset */
/* The root port is at the base of the PCIe register space */
if
(
address
!=
0U
)
{
/*
* The current device must be at CFG_DATA, a 4K window mapped,
* via CFG_INDEX, to the device we are accessing. At the same
* time we must avoid accesses to certain areas of the cfg
* space via CFG_DATA. Detect those accesses and report that
* the address is invalid.
*/
base
+=
PCIE_EXT_CFG_DATA
;
bus
=
PCI_ADDR_BUS
(
address
);
dev
=
PCI_ADDR_DEV
(
address
);
fun
=
PCI_ADDR_FUN
(
address
);
address
=
(
bus
<<
PCIE_EXT_BUS_SHIFT
)
|
(
dev
<<
PCIE_EXT_DEV_SHIFT
)
|
(
fun
<<
PCIE_EXT_FUN_SHIFT
);
/* Allow only dev = 0 on root port and bus 1 */
if
((
bus
<
2U
)
&&
(
dev
>
0U
))
{
return
INVALID_PCI_ADDR
;
}
/* Assure link up before reading bus 1 */
status
=
mmio_read_32
(
PCIE_REG_BASE
+
PCIE_MISC_PCIE_STATUS
);
if
((
status
&
0x30
)
!=
0x30
)
{
return
INVALID_PCI_ADDR
;
}
/* Adjust which device the CFG_DATA window is pointing at */
mmio_write_32
(
PCIE_REG_BASE
+
PCIE_EXT_CFG_INDEX
,
address
);
}
return
base
+
offset
;
}
/**
* pci_read_config() - Performs a config space read at addr
* @addr: 32-bit, segment, BDF of requested function encoded per DEN0115
* @off: register offset of function described by @addr to read
* @sz: size of read (8,16,32) bits.
* @val: returned zero extended value read from config space
*
* sz bits of PCI config space is read at addr:offset, and the value
* is returned in val. Invalid segment/offset values return failure.
* Reads to valid functions that don't exist return INVALID_PCI_ADDR
* as is specified by PCI for requests that aren't completed by EPs.
* The boilerplate in pci_svc.c tends to do basic segment, off
* and sz validation. This routine should avoid duplicating those
* checks.
*
* This function maps directly to the PCI_READ function in DEN0115
* where detailed requirements may be found.
*
* Return: SMC_PCI_CALL_SUCCESS with val set
* SMC_PCI_CALL_INVAL_PARAM, on parameter error
*/
uint32_t
pci_read_config
(
uint32_t
addr
,
uint32_t
off
,
uint32_t
sz
,
uint32_t
*
val
)
{
uint32_t
ret
=
SMC_PCI_CALL_SUCCESS
;
uint64_t
base
;
spin_lock
(
&
pci_lock
);
base
=
pci_segment_lib_get_base
(
addr
,
off
);
if
(
base
==
INVALID_PCI_ADDR
)
{
*
val
=
base
;
}
else
{
switch
(
sz
)
{
case
SMC_PCI_SZ_8BIT
:
*
val
=
mmio_read_8
(
base
);
break
;
case
SMC_PCI_SZ_16BIT
:
*
val
=
mmio_read_16
(
base
);
break
;
case
SMC_PCI_SZ_32BIT
:
*
val
=
mmio_read_32
(
base
);
break
;
default:
/* should be unreachable */
*
val
=
0
;
ret
=
SMC_PCI_CALL_INVAL_PARAM
;
}
}
spin_unlock
(
&
pci_lock
);
return
ret
;
}
/**
* pci_write_config() - Performs a config space write at addr
* @addr: 32-bit, segment, BDF of requested function encoded per DEN0115
* @off: register offset of function described by @addr to write
* @sz: size of write (8,16,32) bits.
* @val: value to be written
*
* sz bits of PCI config space is written at addr:offset. Invalid
* segment/BDF values return failure. Writes to valid functions
* without valid EPs are ignored, as is specified by PCI.
* The boilerplate in pci_svc.c tends to do basic segment, off
* and sz validation, so it shouldn't need to be repeated here.
*
* This function maps directly to the PCI_WRITE function in DEN0115
* where detailed requirements may be found.
*
* Return: SMC_PCI_CALL_SUCCESS
* SMC_PCI_CALL_INVAL_PARAM, on parameter error
*/
uint32_t
pci_write_config
(
uint32_t
addr
,
uint32_t
off
,
uint32_t
sz
,
uint32_t
val
)
{
uint32_t
ret
=
SMC_PCI_CALL_SUCCESS
;
uint64_t
base
;
spin_lock
(
&
pci_lock
);
base
=
pci_segment_lib_get_base
(
addr
,
off
);
if
(
base
!=
INVALID_PCI_ADDR
)
{
switch
(
sz
)
{
case
SMC_PCI_SZ_8BIT
:
mmio_write_8
(
base
,
val
);
break
;
case
SMC_PCI_SZ_16BIT
:
mmio_write_16
(
base
,
val
);
break
;
case
SMC_PCI_SZ_32BIT
:
mmio_write_32
(
base
,
val
);
break
;
default:
/* should be unreachable */
ret
=
SMC_PCI_CALL_INVAL_PARAM
;
}
}
spin_unlock
(
&
pci_lock
);
return
ret
;
}
/**
* pci_get_bus_for_seg() - returns the start->end bus range for a segment
* @seg: segment being queried
* @bus_range: returned bus begin + (end << 8)
* @nseg: returns next segment in this machine or 0 for end
*
* pci_get_bus_for_seg is called to check if a given segment is
* valid on this machine. If it is valid, then its bus ranges are
* returned along with the next valid segment on the machine. If
* this is the last segment, then nseg must be 0.
*
* This function maps directly to the PCI_GET_SEG_INFO function
* in DEN0115 where detailed requirements may be found.
*
* Return: SMC_PCI_CALL_SUCCESS, and appropriate bus_range and nseg
* SMC_PCI_CALL_NOT_IMPL, if the segment is invalid
*/
uint32_t
pci_get_bus_for_seg
(
uint32_t
seg
,
uint32_t
*
bus_range
,
uint32_t
*
nseg
)
{
uint32_t
ret
=
SMC_PCI_CALL_SUCCESS
;
*
nseg
=
0U
;
/* only a single segment */
if
(
seg
==
0U
)
{
*
bus_range
=
0xFF00
;
/* start 0, end 255 */
}
else
{
*
bus_range
=
0U
;
ret
=
SMC_PCI_CALL_NOT_IMPL
;
}
return
ret
;
}
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment
Menu
Projects
Groups
Snippets
Help