[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Xen-devel] [PATCH v3 4/6] xen/arm: zynqmp: eemi access control



On Tue, 28 Aug 2018, Julien Grall wrote:
> Hi Stefano,
> 
> On 11/08/18 01:01, Stefano Stabellini wrote:
> > From: "Edgar E. Iglesias" <edgar.iglesias@xxxxxxxxxx>
> > 
> > From: Edgar E. Iglesias <edgar.iglesias@xxxxxxxxxx>
> > 
> > Introduce data structs to implement basic access controls.
> > Introduce the following three functions:
> > 
> > domain_has_node_access: check access to the node
> > domain_has_reset_access: check access to the reset line
> > domain_has_mmio_access: check access to the register
> > 
> > Signed-off-by: Edgar E. Iglesias <edgar.iglesias@xxxxxxxxxx>
> > Signed-off-by: Stefano Stabellini <stefanos@xxxxxxxxxx>
> > ---
> >   xen/arch/arm/platforms/xilinx-zynqmp-eemi.c | 783
> > ++++++++++++++++++++++++++++
> >   1 file changed, 783 insertions(+)
> > 
> > diff --git a/xen/arch/arm/platforms/xilinx-zynqmp-eemi.c
> > b/xen/arch/arm/platforms/xilinx-zynqmp-eemi.c
> > index c3a19e9..62cc15c 100644
> > --- a/xen/arch/arm/platforms/xilinx-zynqmp-eemi.c
> > +++ b/xen/arch/arm/platforms/xilinx-zynqmp-eemi.c
> > @@ -16,6 +16,74 @@
> >    * GNU General Public License for more details.
> >    */
> >   +/*
> > + *  EEMI Power Management API access
> > + *
> > + * Refs:
> > + *
> > https://www.xilinx.com/support/documentation/user_guides/ug1200-eemi-api.pdf
> > + *
> > + * Background:
> > + * The ZynqMP has a subsystem named the PMU with a CPU and special devices
> > + * dedicated to running Power Management Firmware. Other masters in the
> > + * system need to send requests to the PMU in order to for example:
> > + * * Manage power state
> > + * * Configure clocks
> > + * * Program bitstreams for the programmable logic
> > + * * etc
> > + *
> > + * Although the details of the setup are configurable, in the common case
> > + * the PMU lives in the Secure world. NS World cannot directly communicate
> > + * with it and must use proxy services from ARM Trusted Firmware to reach
> > + * the PMU.
> > + *
> > + * Power Management on the ZynqMP is implemented in a layered manner.
> > + * The PMU knows about various masters and will enforce access controls
> > + * based on a pre-configured partitioning. This configuration dictates
> > + * which devices are owned by the various masters and the PMU FW makes sure
> > + * that a given master cannot turn off a device that it does not own or
> > that
> > + * is in use by other masters.
> > + *
> > + * The PMU is not aware of multiple execution states in masters.
> > + * For example, it treats the ARMv8 cores as single units and does not
> > + * distinguish between Secure vs NS OS's nor does it know about Hypervisors
> > + * and multiple guests. It is up to software on the ARMv8 cores to present
> > + * a unified view of its power requirements.
> > + *
> > + * To implement this unified view, ARM Trusted Firmware at EL3 provides
> > + * access to the PM API via SMC calls. ARM Trusted Firmware is responsible
> > + * for mediating between the Secure and the NS world, rejecting SMC calls
> > + * that request changes that are not allowed.
> > + *
> > + * Xen running above ATF owns the NS world and is responsible for
> > presenting
> > + * unified PM requests taking all guests and the hypervisor into account.
> > + *
> > + * Implementation:
> > + * The PM API contains different classes of calls.
> > + * Certain calls are harmless to expose to any guest.
> > + * These include calls to get the PM API Version, or to read out the
> > version
> > + * of the chip we're running on.
> > + *
> > + * In order to correctly virtualize these calls, we need to know if
> > + * guests issuing these calls have ownership of the given device.
> > + * The approach taken here is to map PM API Nodes identifying
> > + * a device into base addresses for registers that belong to that
> > + * same device.
> > + *
> > + * If the guest has access to devices registers, we give the guest
> > + * access to PM API calls that affect that device. This is implemented
> > + * by pm_node_access and domain_has_node_access().
> > + *
> > + * MMIO access:
> > + * These calls allow guests to access certain memory ranges. These ranges
> > + * are typically protected for secure-world access only and also from
> > + * certain masters only, so guests cannot access them directly.
> > + * Registers within the memory regions affect certain nodes. In this case,
> > + * our input is an address and we map that address into a node. If the
> > + * guest has ownership of that node, the access is allowed.
> > + * Some registers contain bitfields and a single register may contain
> > + * bits that affect multiple nodes.
> > + */
> > +
> >   #include <xen/iocap.h>
> >   #include <xen/sched.h>
> >   #include <xen/types.h>
> > @@ -23,6 +91,721 @@
> >   #include <asm/regs.h>
> >   #include <asm/platforms/xilinx-zynqmp-eemi.h>
> >   +struct pm_access
> > +{
> > +    paddr_t addr;
> 
> It seems that the address will always page-aligned. So could we store a frame
> using mfn_t?

Yes we could store just the frame. I started to make the change
suggested, and all the required corresponding changes to the
initializations below, for instance:

  [NODE_RPU] = { MM_RPU },

needs to become:

  [NODE_RPU] = { _mfn(MM_RPU) },

but when I tried to do that, I get a compilation error:

  xilinx-zynqmp-eemi.c:106:20: error: initializer element is not constant
       [NODE_RPU] = { _mfn(MM_RPU) },

Unfortunately it is not possible to use mfn_t in static initializations,
because it is a static inline. I could do:

  [NODE_RPU] = { { MM_RPU } },

which would work for DEBUG builds but wouldn't work for non-DEBUG
builds.

I'll keep paddr_t for the next version of the series.


> > +    bool hwdom_access;    /* HW domain gets access regardless.  */
> > +};
> > +
> > +/*
> > + * This table maps a node into a memory address.
> > + * If a guest has access to the address, it has enough control
> > + * over the node to grant it access to EEMI calls for that node.
> > + */
> > +static const struct pm_access pm_node_access[] = {
> 
> [...]
> 
> > +
> > +/*
> > + * This table maps reset line IDs into a memory address.
> > + * If a guest has access to the address, it has enough control
> > + * over the affected node to grant it access to EEMI calls for
> > + * resetting that node.
> > + */
> > +#define XILPM_RESET_IDX(n) (n - XILPM_RESET_PCIE_CFG)
> > +static const struct pm_access pm_reset_access[] = {
> 
> [...]
> 
> > +
> > +/*
> > + * This table maps reset line IDs into a memory address.
> > + * If a guest has access to the address, it has enough control
> > + * over the affected node to grant it access to EEMI calls for
> > + * resetting that node.
> > + */
> > +static const struct {
> > +    paddr_t start;
> > +    paddr_t size;
> > +    uint32_t mask;   /* Zero means no mask, i.e all bits.  */
> > +    enum pm_node_id node;
> > +    bool hwdom_access;
> > +    bool readonly;
> > +} pm_mmio_access[] = {
> 
> Those 3 arrays contains a lot of hardcoded value. Can't any of this be
> detected from the device-tree?

No, the information is not available on device tree unfortunately.


> I would be interested to know how this is going to work with upstream Linux.
> Do you hardcode all the values there as well?

Yes: the IDs are specified on an header file, see
include/linux/firmware/xlnx-zynqmp.h on the zynq/firmware branch of the
arm-soc tree. In addition to the IDs, we also have the memory addresses
in Xen to do the permission checks.


> > +static bool pm_check_access(const struct pm_access *acl, struct domain *d,
> > +                            uint32_t idx)
> > +{
> > +    unsigned long mfn;
> 
> mfn_t please. The code is not that nice but at least it add more safety in the
> code. Hopefully iommu_access_permitted will be converted to typesafe MFN soon.

Yes, I can make this change without issues.


> > +
> > +    if ( acl[idx].hwdom_access && is_hardware_domain(d) )
> > +        return true;
> > +
> > +    if ( !acl[idx].addr )
> > +        return false;
> > +
> > +    mfn = PFN_DOWN(acl[idx].addr);
> 
> maddr_to_mfn(...);

OK


> > +    return iomem_access_permitted(d, mfn, mfn);
> 
> Is the address something that a guest would be allowed to read/write directly?
> If not, then iomem_access_permitted(...) should not be used.

Yes, the address would be something remapped to the guest using iomem.


> > +}
> > +
> > +/* Check if a domain has access to a node.  */
> > +static bool domain_has_node_access(struct domain *d, uint32_t nodeid)
> > +{
> > +    if ( nodeid > ARRAY_SIZE(pm_node_access) )
> > +        return false;
> > +
> > +    return pm_check_access(pm_node_access, d, nodeid);
> > +}
> > +
> > +/* Check if a domain has access to a reset line.  */
> > +static bool domain_has_reset_access(struct domain *d, uint32_t rst)
> > +{
> > +    if ( rst < XILPM_RESET_PCIE_CFG )
> > +        return false;
> > +
> > +    rst -= XILPM_RESET_PCIE_CFG;
> > +
> > +    if ( rst > ARRAY_SIZE(pm_reset_access) )
> > +        return false;
> > +
> > +    return pm_check_access(pm_reset_access, d, rst);
> > +}
> > +
> > +/*
> > + * Check if a given domain has access to perform an indirect
> > + * MMIO access.
> 
> This sentence seems to confirm a domain cannot do direct MMIO access to that
> region. So, iomem_access_permitted is definitely not an option here.
> 
> > + *
> > + * If the provided mask is invalid, it will be fixed up.
> > + */
> > +static bool domain_has_mmio_access(struct domain *d,
> > +                                   bool write, paddr_t addr,
> > +                                   uint32_t *mask)
> 
> I don't see this function used below, this would lead to a compilation error.
> Can you make the series is bisectable?

I'll will, sorry about that.


> > +{
> > +    unsigned int i;
> > +    bool ret = false;
> > +    uint32_t prot_mask = 0;
> > +
> > +    /*
> > +     * The hardware domain gets read access to everything.
> > +     * Lower layers will do further filtering.
> > +     */
> > +    if ( !write && is_hardware_domain(d) )
> > +        return true;
> > +
> > +    /* Scan the ACL.  */
> > +    for ( i = 0; i < ARRAY_SIZE(pm_mmio_access); i++ )
> > +    {
> > +        if ( addr < pm_mmio_access[i].start )
> > +            return false;
> > +        if ( addr > pm_mmio_access[i].start + pm_mmio_access[i].size )
> 
> I would add an ASSERT(pm_mmio_access[i].start >= pm_mmio_access[i].size) to
> catch wrapping.

I take that you meant the following:

  ASSERT(pm_mmio_access[i].start + pm_mmio_access[i].size >=  
pm_mmio_access[i].start)


> > +            continue;
> > +
> > +        if ( write && pm_mmio_access[i].readonly )
> > +            return false;
> > +        if ( pm_mmio_access[i].hwdom_access && !is_hardware_domain(d) )
> > +            return false;
> > +        if ( !domain_has_node_access(d, pm_mmio_access[i].node) )
> > +            return false;
> > +
> > +        /* We've got access to this reg (or parts of it).  */
> > +        ret = true;
> > +
> > +        /* Permit write access to selected bits.  */
> > +        prot_mask |= pm_mmio_access[i].mask ?: 0xFFFFFFFF;
> Can you use GENMASK here?

Sure

> NIT: newline

OK

> > +        break;
> > +    }
> > +
> > +    /* Masking only applies to writes.  */
> > +    if ( write )
> > +        *mask &= prot_mask;
> 
> So for reading there are no masking? What should be the value it?

Yes, this is my understanding. The value is safe to read, but not all
bits are writeable.


> > +
> > +    return ret;
> > +}
> > +
> >   bool zynqmp_eemi(struct cpu_user_regs *regs)
> >   {
> >       return false;
> > 
> 
> Cheers,
> 
> -- 
> Julien Grall
> 

_______________________________________________
Xen-devel mailing list
Xen-devel@xxxxxxxxxxxxxxxxxxxx
https://lists.xenproject.org/mailman/listinfo/xen-devel

 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.