diff mbox

[v3,3/3] PCI: ARM: add support for generic PCI host controller

Message ID 1392726043-31088-4-git-send-email-will.deacon@arm.com
State Not Applicable
Headers show

Commit Message

Will Deacon Feb. 18, 2014, 12:20 p.m. UTC
This patch adds support for a generic PCI host controller, such as a
firmware-initialised device with static windows or an emulation by
something such as kvmtool.

The controller itself has no configuration registers and has its address
spaces described entirely by the device-tree (using the bindings from
ePAPR). Both CAM and ECAM are supported for Config Space accesses.

Corresponding documentation is added for the DT binding.

Signed-off-by: Will Deacon <will.deacon@arm.com>
---
 .../devicetree/bindings/pci/host-generic-pci.txt   |  88 +++++
 drivers/pci/host/Kconfig                           |   7 +
 drivers/pci/host/Makefile                          |   1 +
 drivers/pci/host/pci-host-generic.c                | 381 +++++++++++++++++++++
 4 files changed, 477 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/pci/host-generic-pci.txt
 create mode 100644 drivers/pci/host/pci-host-generic.c

Comments

Arnd Bergmann Feb. 18, 2014, 1:46 p.m. UTC | #1
On Tuesday 18 February 2014 12:20:43 Will Deacon wrote:

> +static int gen_pci_alloc_io_offset(u32 sz, resource_size_t *offset)
> +{
> +	static DECLARE_BITMAP(io_map, (IO_SPACE_LIMIT + 1) / SZ_64K);
> +	int idx, num_wins;
> +
> +	if (sz > SZ_64K)
> +		return -ENOSPC;
> +
> +	num_wins = (IO_SPACE_LIMIT + 1) / SZ_64K;
> +	idx = 0;
> +	do {
> +		idx = find_next_zero_bit(io_map, num_wins, idx);
> +		if (idx == num_wins)
> +			return -ENOSPC;
> +	} while (test_and_set_bit(idx, io_map));
> +
> +	*offset = idx * SZ_64K;
> +	return 0;
> +}

Sicne you're always starting the search at 0 and you never free
the map, this is essentially the same as remembering the last number
that was used and using the next one, right?

You should also pass the rang->pci_addr here to calculate the
offset in case the pci_addr is not zero.

> +	/* Register our I/O and Memory resources */
> +	res_valid = 0;
> +	list_for_each_entry(win, &pci->host.windows, list) {
> +		struct resource *parent;
> +
> +		if (resource_type(win->res) == IORESOURCE_IO) {
> +			parent = &ioport_resource;
> +			err = pci_ioremap_io(win->offset, win->res->start);

and consequently pass the pci_addr rather than the offset here. How about
moving the pci_ioremap_io() call into gen_pci_alloc_io_offset()?

	Arnd
--
To unsubscribe from this list: send the line "unsubscribe linux-pci" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jason Gunthorpe Feb. 18, 2014, 6:21 p.m. UTC | #2
On Tue, Feb 18, 2014 at 12:20:43PM +0000, Will Deacon wrote:
> +
> +    // BUS_ADDRESS(3)  CPU_PHYSICAL(2)  SIZE(2)
> +    ranges = <0x1000000 0x0 0x00000000  0x0 0x00000000  0x0 0x00010000>,
                                           ^^^^^^^^^^^^^^

This probably shouldn't be 0 in the example, nor in your kvm tool
output. For example purposes any value will do.

Regards,
Jason
--
To unsubscribe from this list: send the line "unsubscribe linux-pci" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Will Deacon Feb. 18, 2014, 6:44 p.m. UTC | #3
On Tue, Feb 18, 2014 at 06:21:42PM +0000, Jason Gunthorpe wrote:
> On Tue, Feb 18, 2014 at 12:20:43PM +0000, Will Deacon wrote:
> > +
> > +    // BUS_ADDRESS(3)  CPU_PHYSICAL(2)  SIZE(2)
> > +    ranges = <0x1000000 0x0 0x00000000  0x0 0x00000000  0x0 0x00010000>,
>                                            ^^^^^^^^^^^^^^
> 
> This probably shouldn't be 0 in the example, nor in your kvm tool
> output. For example purposes any value will do.

Hmm, so kvmtool actually provides a PC ioport at 0x0, which is handy since
there's an 8250 down there. That means we have:

0x0    - 0x6200  : Weird PC stuff
0x6200 - 0x10000 : PCI IO space

Should I just change everything to be offset by 0x6200?

Will
--
To unsubscribe from this list: send the line "unsubscribe linux-pci" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Arnd Bergmann Feb. 18, 2014, 6:47 p.m. UTC | #4
On Tuesday 18 February 2014 18:44:20 Will Deacon wrote:
> On Tue, Feb 18, 2014 at 06:21:42PM +0000, Jason Gunthorpe wrote:
> > On Tue, Feb 18, 2014 at 12:20:43PM +0000, Will Deacon wrote:
> > > +
> > > +    // BUS_ADDRESS(3)  CPU_PHYSICAL(2)  SIZE(2)
> > > +    ranges = <0x1000000 0x0 0x00000000  0x0 0x00000000  0x0 0x00010000>,
> >                                            ^^^^^^^^^^^^^^
> > 
> > This probably shouldn't be 0 in the example, nor in your kvm tool
> > output. For example purposes any value will do.
> 
> Hmm, so kvmtool actually provides a PC ioport at 0x0, which is handy since
> there's an 8250 down there. That means we have:
> 
> 0x0    - 0x6200  : Weird PC stuff
> 0x6200 - 0x10000 : PCI IO space
> 
> Should I just change everything to be offset by 0x6200?

Do you have a list of the stuff in there?

If that is actually port I/O, leaving it in there may be best,
but I agree the example should be changed.

	Arnd
--
To unsubscribe from this list: send the line "unsubscribe linux-pci" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Will Deacon Feb. 18, 2014, 6:51 p.m. UTC | #5
On Tue, Feb 18, 2014 at 06:47:15PM +0000, Arnd Bergmann wrote:
> On Tuesday 18 February 2014 18:44:20 Will Deacon wrote:
> > On Tue, Feb 18, 2014 at 06:21:42PM +0000, Jason Gunthorpe wrote:
> > > On Tue, Feb 18, 2014 at 12:20:43PM +0000, Will Deacon wrote:
> > > > +
> > > > +    // BUS_ADDRESS(3)  CPU_PHYSICAL(2)  SIZE(2)
> > > > +    ranges = <0x1000000 0x0 0x00000000  0x0 0x00000000  0x0 0x00010000>,
> > >                                            ^^^^^^^^^^^^^^
> > > 
> > > This probably shouldn't be 0 in the example, nor in your kvm tool
> > > output. For example purposes any value will do.
> > 
> > Hmm, so kvmtool actually provides a PC ioport at 0x0, which is handy since
> > there's an 8250 down there. That means we have:
> > 
> > 0x0    - 0x6200  : Weird PC stuff
> > 0x6200 - 0x10000 : PCI IO space
> > 
> > Should I just change everything to be offset by 0x6200?
> 
> Do you have a list of the stuff in there?

kvmtool provides an i8042, pci-shmem, an rtc, 8250 and vesa at the usual PC
offsets. On ARM, I regularly use the 8250 (there are even DT bindings for
it) and MarcZ got the rtc working for fun. Not tried the others though.

> If that is actually port I/O, leaving it in there may be best,
> but I agree the example should be changed.

Yup, it is port I/O. I'll make something up for the example.

Will
--
To unsubscribe from this list: send the line "unsubscribe linux-pci" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jason Gunthorpe Feb. 18, 2014, 6:59 p.m. UTC | #6
On Tue, Feb 18, 2014 at 06:44:20PM +0000, Will Deacon wrote:
> On Tue, Feb 18, 2014 at 06:21:42PM +0000, Jason Gunthorpe wrote:
> > On Tue, Feb 18, 2014 at 12:20:43PM +0000, Will Deacon wrote:
> > > +
> > > +    // BUS_ADDRESS(3)  CPU_PHYSICAL(2)  SIZE(2)
> > > +    ranges = <0x1000000 0x0 0x00000000  0x0 0x00000000  0x0 0x00010000>,
> >                                            ^^^^^^^^^^^^^^
> > 
> > This probably shouldn't be 0 in the example, nor in your kvm tool
> > output. For example purposes any value will do.
> 
> Hmm, so kvmtool actually provides a PC ioport at 0x0, which is handy since
> there's an 8250 down there. That means we have:
> 
> 0x0    - 0x6200  : Weird PC stuff
> 0x6200 - 0x10000 : PCI IO space
> 
> Should I just change everything to be offset by 0x6200?

Just to be clear, kvm has reserved the first 64k of physical address
space for this IO window? The 0 is fine then, but it isn't typical of
real hardware.

Regarding the 0x6200.. There are two conflicting issues there :(
 - You really don't want to let the PCI core assign resources to that
   range, it probably won't work.
 - You probably need that range mapped into the ARM IO window, and the
   PCI driver is the thing doing that

The PCI core already has a black out for low IO ports, I think it
already covers the usual PC suspects so you are probably fine with
what you have for KVM.

Jason
--
To unsubscribe from this list: send the line "unsubscribe linux-pci" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Will Deacon Feb. 18, 2014, 7:09 p.m. UTC | #7
On Tue, Feb 18, 2014 at 06:59:47PM +0000, Jason Gunthorpe wrote:
> On Tue, Feb 18, 2014 at 06:44:20PM +0000, Will Deacon wrote:
> > On Tue, Feb 18, 2014 at 06:21:42PM +0000, Jason Gunthorpe wrote:
> > > On Tue, Feb 18, 2014 at 12:20:43PM +0000, Will Deacon wrote:
> > > > +
> > > > +    // BUS_ADDRESS(3)  CPU_PHYSICAL(2)  SIZE(2)
> > > > +    ranges = <0x1000000 0x0 0x00000000  0x0 0x00000000  0x0 0x00010000>,
> > >                                            ^^^^^^^^^^^^^^
> > > 
> > > This probably shouldn't be 0 in the example, nor in your kvm tool
> > > output. For example purposes any value will do.
> > 
> > Hmm, so kvmtool actually provides a PC ioport at 0x0, which is handy since
> > there's an 8250 down there. That means we have:
> > 
> > 0x0    - 0x6200  : Weird PC stuff
> > 0x6200 - 0x10000 : PCI IO space
> > 
> > Should I just change everything to be offset by 0x6200?
> 
> Just to be clear, kvm has reserved the first 64k of physical address
> space for this IO window? The 0 is fine then, but it isn't typical of
> real hardware.

Yup, that's spot on.

> Regarding the 0x6200.. There are two conflicting issues there :(
>  - You really don't want to let the PCI core assign resources to that
>    range, it probably won't work.

Right, with kvmtool we don't support resource assignment (the BARs are fixed)
so everything is PCI_PROBE_ONLY.

>  - You probably need that range mapped into the ARM IO window, and the
>    PCI driver is the thing doing that
> 
> The PCI core already has a black out for low IO ports, I think it
> already covers the usual PC suspects so you are probably fine with
> what you have for KVM.

Great, so I'll just update the example then. Thanks!

Will
--
To unsubscribe from this list: send the line "unsubscribe linux-pci" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Will Deacon Feb. 18, 2014, 7:10 p.m. UTC | #8
On Tue, Feb 18, 2014 at 01:46:44PM +0000, Arnd Bergmann wrote:
> On Tuesday 18 February 2014 12:20:43 Will Deacon wrote:
> 
> > +static int gen_pci_alloc_io_offset(u32 sz, resource_size_t *offset)
> > +{
> > +	static DECLARE_BITMAP(io_map, (IO_SPACE_LIMIT + 1) / SZ_64K);
> > +	int idx, num_wins;
> > +
> > +	if (sz > SZ_64K)
> > +		return -ENOSPC;
> > +
> > +	num_wins = (IO_SPACE_LIMIT + 1) / SZ_64K;
> > +	idx = 0;
> > +	do {
> > +		idx = find_next_zero_bit(io_map, num_wins, idx);
> > +		if (idx == num_wins)
> > +			return -ENOSPC;
> > +	} while (test_and_set_bit(idx, io_map));
> > +
> > +	*offset = idx * SZ_64K;
> > +	return 0;
> > +}
> 
> Sicne you're always starting the search at 0 and you never free
> the map, this is essentially the same as remembering the last number
> that was used and using the next one, right?

Yes, I can implement that easily enough. I was wondering if there was a case
for allowing windows to be freed, but it doesn't feel especially compelling.

> You should also pass the rang->pci_addr here to calculate the
> offset in case the pci_addr is not zero.
> 
> > +	/* Register our I/O and Memory resources */
> > +	res_valid = 0;
> > +	list_for_each_entry(win, &pci->host.windows, list) {
> > +		struct resource *parent;
> > +
> > +		if (resource_type(win->res) == IORESOURCE_IO) {
> > +			parent = &ioport_resource;
> > +			err = pci_ioremap_io(win->offset, win->res->start);
> 
> and consequently pass the pci_addr rather than the offset here. How about
> moving the pci_ioremap_io() call into gen_pci_alloc_io_offset()?

Can do.

Thanks for the feedback.

Will
--
To unsubscribe from this list: send the line "unsubscribe linux-pci" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Arnd Bergmann Feb. 18, 2014, 7:11 p.m. UTC | #9
On Tuesday 18 February 2014 18:51:50 Will Deacon wrote:
> 
> kvmtool provides an i8042, pci-shmem, an rtc, 8250 and vesa at the usual PC
> offsets. On ARM, I regularly use the 8250 (there are even DT bindings for
> it) and MarcZ got the rtc working for fun. Not tried the others though.

Ah, interesting. But which ones are between 0x1000-0x6200? The ones
you list should all be in the ISA range between 0x0-0xfff as far
as I can tell, which we keep clear of by setting PCIBIOS_MIN_IO
to 0x1000.

	Arnd
--
To unsubscribe from this list: send the line "unsubscribe linux-pci" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Will Deacon Feb. 18, 2014, 7:16 p.m. UTC | #10
On Tue, Feb 18, 2014 at 07:11:25PM +0000, Arnd Bergmann wrote:
> On Tuesday 18 February 2014 18:51:50 Will Deacon wrote:
> > 
> > kvmtool provides an i8042, pci-shmem, an rtc, 8250 and vesa at the usual PC
> > offsets. On ARM, I regularly use the 8250 (there are even DT bindings for
> > it) and MarcZ got the rtc working for fun. Not tried the others though.
> 
> Ah, interesting. But which ones are between 0x1000-0x6200? The ones
> you list should all be in the ISA range between 0x0-0xfff as far
> as I can tell, which we keep clear of by setting PCIBIOS_MIN_IO
> to 0x1000.

I think there's just a hole between 0x1000 - 0x6200.

Will
--
To unsubscribe from this list: send the line "unsubscribe linux-pci" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Arnd Bergmann Feb. 18, 2014, 7:32 p.m. UTC | #11
On Tuesday 18 February 2014 19:09:29 Will Deacon wrote:
> 
> > Regarding the 0x6200.. There are two conflicting issues there 
> >  - You really don't want to let the PCI core assign resources to that
> >    range, it probably won't work.
> 
> Right, with kvmtool we don't support resource assignment (the BARs are fixed)
> so everything is PCI_PROBE_ONLY.

Ok, I looked at the source now and can confirm:

* 0x0-0x1000 are used for lots of legacy ISA devices.
* PCI devices get assigned IO addresses in 0x400 steps starting at 0x6200.
* There are three PCI drivers doing this: VESA, PCI-SHMEM and virtio-pci.

Regarding the PCI_PROBE_ONLY flag, how do you set that? Should we
have a standard DT property for that? On PowerPC we already specified
"linux,pci-probe-only" and "linux,pci-assign-all-buses", which seems
reasonable to use in architecture independent code as well.

	Arnd
--
To unsubscribe from this list: send the line "unsubscribe linux-pci" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Will Deacon Feb. 18, 2014, 7:36 p.m. UTC | #12
On Tue, Feb 18, 2014 at 07:32:33PM +0000, Arnd Bergmann wrote:
> On Tuesday 18 February 2014 19:09:29 Will Deacon wrote:
> > 
> > > Regarding the 0x6200.. There are two conflicting issues there 
> > >  - You really don't want to let the PCI core assign resources to that
> > >    range, it probably won't work.
> > 
> > Right, with kvmtool we don't support resource assignment (the BARs are fixed)
> > so everything is PCI_PROBE_ONLY.
> 
> Ok, I looked at the source now and can confirm:
> 
> * 0x0-0x1000 are used for lots of legacy ISA devices.
> * PCI devices get assigned IO addresses in 0x400 steps starting at 0x6200.
> * There are three PCI drivers doing this: VESA, PCI-SHMEM and virtio-pci.
> 
> Regarding the PCI_PROBE_ONLY flag, how do you set that? Should we
> have a standard DT property for that? On PowerPC we already specified
> "linux,pci-probe-only" and "linux,pci-assign-all-buses", which seems
> reasonable to use in architecture independent code as well.

For arch/arm/ it's done on the command line (pci=firmware) but yes, for the
generic driver it sounds like a good idea to put this in the device-tree.
I'll look at adding the ppc properties in the next version.

Cheers,

Will
--
To unsubscribe from this list: send the line "unsubscribe linux-pci" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Will Deacon Feb. 18, 2014, 7:57 p.m. UTC | #13
On Tue, Feb 18, 2014 at 07:36:54PM +0000, Will Deacon wrote:
> On Tue, Feb 18, 2014 at 07:32:33PM +0000, Arnd Bergmann wrote:
> > On Tuesday 18 February 2014 19:09:29 Will Deacon wrote:
> > > 
> > > > Regarding the 0x6200.. There are two conflicting issues there 
> > > >  - You really don't want to let the PCI core assign resources to that
> > > >    range, it probably won't work.
> > > 
> > > Right, with kvmtool we don't support resource assignment (the BARs are fixed)
> > > so everything is PCI_PROBE_ONLY.
> > 
> > Ok, I looked at the source now and can confirm:
> > 
> > * 0x0-0x1000 are used for lots of legacy ISA devices.
> > * PCI devices get assigned IO addresses in 0x400 steps starting at 0x6200.
> > * There are three PCI drivers doing this: VESA, PCI-SHMEM and virtio-pci.
> > 
> > Regarding the PCI_PROBE_ONLY flag, how do you set that? Should we
> > have a standard DT property for that? On PowerPC we already specified
> > "linux,pci-probe-only" and "linux,pci-assign-all-buses", which seems
> > reasonable to use in architecture independent code as well.
> 
> For arch/arm/ it's done on the command line (pci=firmware) but yes, for the
> generic driver it sounds like a good idea to put this in the device-tree.
> I'll look at adding the ppc properties in the next version.

Damn, I spoke too soon. PowerPC puts this information in the /chosen node, so
it applies to all PCI host controllers in the system. That then maps nicely
to pci_{add,clear}_flags which are global properties in Linux.

It sounds like this should really be per-controller. What do you think?

Will
--
To unsubscribe from this list: send the line "unsubscribe linux-pci" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Arnd Bergmann Feb. 18, 2014, 8:19 p.m. UTC | #14
On Tuesday 18 February 2014 19:57:32 Will Deacon wrote:
> On Tue, Feb 18, 2014 at 07:36:54PM +0000, Will Deacon wrote:
> > On Tue, Feb 18, 2014 at 07:32:33PM +0000, Arnd Bergmann wrote:
> > > On Tuesday 18 February 2014 19:09:29 Will Deacon wrote:
> > > Regarding the PCI_PROBE_ONLY flag, how do you set that? Should we
> > > have a standard DT property for that? On PowerPC we already specified
> > > "linux,pci-probe-only" and "linux,pci-assign-all-buses", which seems
> > > reasonable to use in architecture independent code as well.
> > 
> > For arch/arm/ it's done on the command line (pci=firmware) but yes, for the
> > generic driver it sounds like a good idea to put this in the device-tree.
> > I'll look at adding the ppc properties in the next version.
> 
> Damn, I spoke too soon. PowerPC puts this information in the /chosen node, so
> it applies to all PCI host controllers in the system. That then maps nicely
> to pci_{add,clear}_flags which are global properties in Linux.
> 
> It sounds like this should really be per-controller. What do you think?

I don't have a strong opinion either way. I think the reason for having it
in the /chosen node is so that a second-stage boot loader can easily
override it, which is not a concern for us. In particular, the properties
are only used on rtas-pci, which means we access the config space to
firmware runtime abstraction services rather than hardware registers.

Apparently the embedded powerpc platforms don't support these flags at
all, which I also didn't notice at first.

	Arnd
--
To unsubscribe from this list: send the line "unsubscribe linux-pci" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Will Deacon Feb. 19, 2014, 10:57 a.m. UTC | #15
On Tue, Feb 18, 2014 at 08:19:21PM +0000, Arnd Bergmann wrote:
> On Tuesday 18 February 2014 19:57:32 Will Deacon wrote:
> > On Tue, Feb 18, 2014 at 07:36:54PM +0000, Will Deacon wrote:
> > > On Tue, Feb 18, 2014 at 07:32:33PM +0000, Arnd Bergmann wrote:
> > > > On Tuesday 18 February 2014 19:09:29 Will Deacon wrote:
> > > > Regarding the PCI_PROBE_ONLY flag, how do you set that? Should we
> > > > have a standard DT property for that? On PowerPC we already specified
> > > > "linux,pci-probe-only" and "linux,pci-assign-all-buses", which seems
> > > > reasonable to use in architecture independent code as well.
> > > 
> > > For arch/arm/ it's done on the command line (pci=firmware) but yes, for the
> > > generic driver it sounds like a good idea to put this in the device-tree.
> > > I'll look at adding the ppc properties in the next version.
> > 
> > Damn, I spoke too soon. PowerPC puts this information in the /chosen node, so
> > it applies to all PCI host controllers in the system. That then maps nicely
> > to pci_{add,clear}_flags which are global properties in Linux.
> > 
> > It sounds like this should really be per-controller. What do you think?
> 
> I don't have a strong opinion either way. I think the reason for having it
> in the /chosen node is so that a second-stage boot loader can easily
> override it, which is not a concern for us. In particular, the properties
> are only used on rtas-pci, which means we access the config space to
> firmware runtime abstraction services rather than hardware registers.
> 
> Apparently the embedded powerpc platforms don't support these flags at
> all, which I also didn't notice at first.

Even in the rtas case there's a:

	#ifdef CONFIG_PPC32 /* Will be made generic soon */

for linux,pci-assign-all-buses. I think I'll just go for
linux,pci-probe-only initially, especially as PCI_REASSIGN_ALL_BUS isn't
actually used anywhere outside of arch/powerpc/.

Will
--
To unsubscribe from this list: send the line "unsubscribe linux-pci" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Will Deacon Feb. 19, 2014, 11:07 a.m. UTC | #16
On Tue, Feb 18, 2014 at 07:10:23PM +0000, Will Deacon wrote:
> On Tue, Feb 18, 2014 at 01:46:44PM +0000, Arnd Bergmann wrote:
> > On Tuesday 18 February 2014 12:20:43 Will Deacon wrote:
> > > +	/* Register our I/O and Memory resources */
> > > +	res_valid = 0;
> > > +	list_for_each_entry(win, &pci->host.windows, list) {
> > > +		struct resource *parent;
> > > +
> > > +		if (resource_type(win->res) == IORESOURCE_IO) {
> > > +			parent = &ioport_resource;
> > > +			err = pci_ioremap_io(win->offset, win->res->start);
> > 
> > and consequently pass the pci_addr rather than the offset here. How about
> > moving the pci_ioremap_io() call into gen_pci_alloc_io_offset()?

I've probably just confused myself, but passing the pci_addr to
pci_ioremap_io doesn't make sense to me. My understanding is that:

  cpu = bus + offset

In the case of I/O, the offset is really:

  offset = (PCI_IO_VIRT_BASE - bus) + window

where window is determined by the simple allocator I wrote.

Now, the __io macro takes care of PCI_IO_VIRT_BASE so we don't actually care
about that when adding the PCI I/O resources, instead we'll just pass:

  offset = window - bus

and then pci_ioremap_io will just want the window offset, since that's added
directly on to PCI_IO_VIRT_BASE for the ioremap_page_range.

If I call pci_ioremap_io(range->pci_addr, ...) then I'm going to trip a BUG_ON
unless the pci_addr is within IO_SPACE_LIMIT.

Will
--
To unsubscribe from this list: send the line "unsubscribe linux-pci" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Arnd Bergmann Feb. 19, 2014, 2:17 p.m. UTC | #17
On Wednesday 19 February 2014 11:07:19 Will Deacon wrote:
> On Tue, Feb 18, 2014 at 07:10:23PM +0000, Will Deacon wrote:
> > On Tue, Feb 18, 2014 at 01:46:44PM +0000, Arnd Bergmann wrote:
> > > On Tuesday 18 February 2014 12:20:43 Will Deacon wrote:
> > > > + /* Register our I/O and Memory resources */
> > > > + res_valid = 0;
> > > > + list_for_each_entry(win, &pci->host.windows, list) {
> > > > +         struct resource *parent;
> > > > +
> > > > +         if (resource_type(win->res) == IORESOURCE_IO) {
> > > > +                 parent = &ioport_resource;
> > > > +                 err = pci_ioremap_io(win->offset, win->res->start);
> > > 
> > > and consequently pass the pci_addr rather than the offset here. How about
> > > moving the pci_ioremap_io() call into gen_pci_alloc_io_offset()?
> 
> I've probably just confused myself, but passing the pci_addr to
> pci_ioremap_io doesn't make sense to me. 

I think the confusion is that there are two different things we call
offset here. The calculation of the offset you pass into
pci_ioremap_io() is correct AFAICT now, but it's not what you are
supposed to pass into pci_add_resource_offset() or what we normally
put into pci_host_bridge_window->offset.

> My understanding is that:
> 
>   cpu = bus + offset

Right. This would be the case for mem_offset.

> In the case of I/O, the offset is really:
> 
>   offset = (PCI_IO_VIRT_BASE - bus) + window

I can't seem to make sense of this calculation. PCI_IO_VIRT_BASE
is a pointer to the start of the virtual window. You can add offsets
to the pointer, but subtracting a number from it is not a well-defined
operation.

> where window is determined by the simple allocator I wrote.

And your allocator calls it offset, which is what confused me.

> Now, the __io macro takes care of PCI_IO_VIRT_BASE so we don't actually care
> about that when adding the PCI I/O resources, instead we'll just pass:
> 
>   offset = window - bus

Yes, this would be the io_offset that you pass into pci_add_resource_offset().

> and then pci_ioremap_io will just want the window offset, since that's added
> directly on to PCI_IO_VIRT_BASE for the ioremap_page_range.

Yes.

> If I call pci_ioremap_io(range->pci_addr, ...) then I'm going to trip a BUG_ON
> unless the pci_addr is within IO_SPACE_LIMIT.

range->pci_addr is what you call 'bus' above. Since you want 'window', you
have to pass 'bus'+'offset', or 'range->pci_addr + io_offset'. Normally, one
of the two would be zero, while the other is equal to 'window'.

	Arnd
--
To unsubscribe from this list: send the line "unsubscribe linux-pci" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Will Deacon Feb. 19, 2014, 3:25 p.m. UTC | #18
Hi Arnd,

On Wed, Feb 19, 2014 at 02:17:56PM +0000, Arnd Bergmann wrote:
> On Wednesday 19 February 2014 11:07:19 Will Deacon wrote:
> > I've probably just confused myself, but passing the pci_addr to
> > pci_ioremap_io doesn't make sense to me. 
> 
> I think the confusion is that there are two different things we call
> offset here. The calculation of the offset you pass into
> pci_ioremap_io() is correct AFAICT now, but it's not what you are
> supposed to pass into pci_add_resource_offset() or what we normally
> put into pci_host_bridge_window->offset.
> 
> > My understanding is that:
> > 
> >   cpu = bus + offset
> 
> Right. This would be the case for mem_offset.
> 
> > In the case of I/O, the offset is really:
> > 
> >   offset = (PCI_IO_VIRT_BASE - bus) + window
> 
> I can't seem to make sense of this calculation. PCI_IO_VIRT_BASE
> is a pointer to the start of the virtual window. You can add offsets
> to the pointer, but subtracting a number from it is not a well-defined
> operation.
> 
> > where window is determined by the simple allocator I wrote.
> 
> And your allocator calls it offset, which is what confused me.
> 
> > Now, the __io macro takes care of PCI_IO_VIRT_BASE so we don't actually care
> > about that when adding the PCI I/O resources, instead we'll just pass:
> > 
> >   offset = window - bus
> 
> Yes, this would be the io_offset that you pass into pci_add_resource_offset().
> 
> > and then pci_ioremap_io will just want the window offset, since that's added
> > directly on to PCI_IO_VIRT_BASE for the ioremap_page_range.
> 
> Yes.
> 
> > If I call pci_ioremap_io(range->pci_addr, ...) then I'm going to trip a BUG_ON
> > unless the pci_addr is within IO_SPACE_LIMIT.
> 
> range->pci_addr is what you call 'bus' above. Since you want 'window', you
> have to pass 'bus'+'offset', or 'range->pci_addr + io_offset'. Normally, one
> of the two would be zero, while the other is equal to 'window'.

Good, so we're in agreement! The issue indeed seems to be that there are
multiple names for the same things :)

Will
--
To unsubscribe from this list: send the line "unsubscribe linux-pci" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/Documentation/devicetree/bindings/pci/host-generic-pci.txt b/Documentation/devicetree/bindings/pci/host-generic-pci.txt
new file mode 100644
index 000000000000..03e1640a7602
--- /dev/null
+++ b/Documentation/devicetree/bindings/pci/host-generic-pci.txt
@@ -0,0 +1,88 @@ 
+* Generic PCI host controller
+
+Firmware-initialised PCI host controllers and PCI emulations, such as the
+virtio-pci implementations found in kvmtool and other para-virtualised
+systems, do not require driver support for complexities such as regulator
+and clock management. In fact, the controller may not even require the
+configuration of a control interface by the operating system, instead
+presenting a set of fixed windows describing a subset of IO, Memory and
+Configuration Spaces.
+
+Such a controller can be described purely in terms of the standardized device
+tree bindings communicated in pci.txt:
+
+- compatible     : Must be "pci-host-cam-generic" or "pci-host-ecam-generic"
+                   depending on the layout of configuration space (CAM vs
+                   ECAM respectively).
+
+- device_type    : Must be "pci".
+
+- ranges         : As described in IEEE Std 1275-1994, but must provide
+                   at least a definition of non-prefetchable memory. One
+                   or both of prefetchable Memory and IO Space may also
+                   be provided.
+
+- bus-range      : Optional property (also described in IEEE Std 1275-1994)
+                   to indicate the range of bus numbers for this controller.
+                   If absent, defaults to <0 255> (i.e. all buses).
+
+- #address-cells : Must be 3.
+
+- #size-cells    : Must be 2.
+
+- reg            : The Configuration Space base address and size, as accessed
+                   from the parent bus.
+
+Configuration Space is assumed to be memory-mapped (as opposed to being
+accessed via an ioport) and laid out with a direct correspondence to the
+geography of a PCI bus address by concatenating the various components to
+form an offset.
+
+For CAM, this 24-bit offset is:
+
+        cfg_offset(bus, device, function, register) =
+                   bus << 16 | device << 11 | function << 8 | register
+
+Whilst ECAM extends this by 4 bits to accomodate 4k of function space:
+
+        cfg_offset(bus, device, function, register) =
+                   bus << 20 | device << 15 | function << 12 | register
+
+Interrupt mapping is exactly as described in `Open Firmware Recommended
+Practice: Interrupt Mapping' and requires the following properties:
+
+- #interrupt-cells   : Must be 1
+
+- interrupt-map      : <see aforementioned specification>
+
+- interrupt-map-mask : <see aforementioned specification>
+
+
+Example:
+
+pci {
+    compatible = "pci-host-cam-generic"
+    device_type = "pci";
+    #address-cells = <3>;
+    #size-cells = <2>;
+    bus-range = <0x0 0x1>;
+
+    // CPU_PHYSICAL(2)  SIZE(2)
+    reg = <0x0 0x40000000  0x0 0x1000000>;
+
+    // BUS_ADDRESS(3)  CPU_PHYSICAL(2)  SIZE(2)
+    ranges = <0x1000000 0x0 0x00000000  0x0 0x00000000  0x0 0x00010000>,
+             <0x2000000 0x0 0x41000000  0x0 0x41000000  0x0 0x3f000000>;
+
+
+    #interrupt-cells = <0x1>;
+
+    // PCI_DEVICE(3)  INT#(1)  CONTROLLER(PHANDLE)  CONTROLLER_DATA(3)
+    interrupt-map = <  0x0 0x0 0x0  0x1  &gic  0x0 0x4 0x1
+                     0x800 0x0 0x0  0x1  &gic  0x0 0x5 0x1
+                    0x1000 0x0 0x0  0x1  &gic  0x0 0x6 0x1
+                    0x1800 0x0 0x0  0x1  &gic  0x0 0x7 0x1>;
+
+    // PCI_DEVICE(3)  INT#(1)
+    interrupt-map-mask = <0xf800 0x0 0x0  0x7>;
+}
diff --git a/drivers/pci/host/Kconfig b/drivers/pci/host/Kconfig
index 47d46c6d8468..f91637d47065 100644
--- a/drivers/pci/host/Kconfig
+++ b/drivers/pci/host/Kconfig
@@ -33,4 +33,11 @@  config PCI_RCAR_GEN2
 	  There are 3 internal PCI controllers available with a single
 	  built-in EHCI/OHCI host controller present on each one.
 
+config PCI_HOST_GENERIC
+	bool "Generic PCI host controller"
+	depends on ARM && OF
+	help
+	  Say Y here if you want to support a simple generic PCI host
+	  controller, such as the one emulated by kvmtool.
+
 endmenu
diff --git a/drivers/pci/host/Makefile b/drivers/pci/host/Makefile
index 13fb3333aa05..bd1bf1ab4ac8 100644
--- a/drivers/pci/host/Makefile
+++ b/drivers/pci/host/Makefile
@@ -4,3 +4,4 @@  obj-$(CONFIG_PCI_IMX6) += pci-imx6.o
 obj-$(CONFIG_PCI_MVEBU) += pci-mvebu.o
 obj-$(CONFIG_PCI_TEGRA) += pci-tegra.o
 obj-$(CONFIG_PCI_RCAR_GEN2) += pci-rcar-gen2.o
+obj-$(CONFIG_PCI_HOST_GENERIC) += pci-host-generic.o
diff --git a/drivers/pci/host/pci-host-generic.c b/drivers/pci/host/pci-host-generic.c
new file mode 100644
index 000000000000..d843b546a8a1
--- /dev/null
+++ b/drivers/pci/host/pci-host-generic.c
@@ -0,0 +1,381 @@ 
+/*
+ * Simple, generic PCI host controller driver targetting firmware-initialised
+ * systems and virtual machines (e.g. the PCI emulation provided by kvmtool).
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Copyright (C) 2014 ARM Limited
+ *
+ * Author: Will Deacon <will.deacon@arm.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_pci.h>
+#include <linux/platform_device.h>
+
+struct gen_pci_cfg_bus_ops {
+	u32 bus_shift;
+	void __iomem *(*map_bus)(struct pci_bus *, unsigned int, int);
+};
+
+struct gen_pci_cfg_windows {
+	struct resource				res;
+	struct resource				bus_range;
+	void __iomem				**win;
+
+	const struct gen_pci_cfg_bus_ops	*ops;
+};
+
+struct gen_pci {
+	struct pci_host_bridge			host;
+	struct gen_pci_cfg_windows		cfg;
+};
+
+static void __iomem *gen_pci_map_cfg_bus_cam(struct pci_bus *bus,
+					     unsigned int devfn,
+					     int where)
+{
+	struct pci_sys_data *sys = bus->sysdata;
+	struct gen_pci *pci = sys->private_data;
+	resource_size_t idx = bus->number - pci->cfg.bus_range.start;
+
+	return pci->cfg.win[idx] + ((devfn << 8) | where);
+}
+
+static struct gen_pci_cfg_bus_ops gen_pci_cfg_cam_bus_ops = {
+	.bus_shift	= 16,
+	.map_bus	= gen_pci_map_cfg_bus_cam,
+};
+
+static void __iomem *gen_pci_map_cfg_bus_ecam(struct pci_bus *bus,
+					      unsigned int devfn,
+					      int where)
+{
+	struct pci_sys_data *sys = bus->sysdata;
+	struct gen_pci *pci = sys->private_data;
+	resource_size_t idx = bus->number - pci->cfg.bus_range.start;
+
+	return pci->cfg.win[idx] + ((devfn << 12) | where);
+}
+
+static struct gen_pci_cfg_bus_ops gen_pci_cfg_ecam_bus_ops = {
+	.bus_shift	= 20,
+	.map_bus	= gen_pci_map_cfg_bus_ecam,
+};
+
+static int gen_pci_config_read(struct pci_bus *bus, unsigned int devfn,
+				int where, int size, u32 *val)
+{
+	void __iomem *addr;
+	struct pci_sys_data *sys = bus->sysdata;
+	struct gen_pci *pci = sys->private_data;
+
+	addr = pci->cfg.ops->map_bus(bus, devfn, where);
+
+	switch (size) {
+	case 1:
+		*val = readb(addr);
+		break;
+	case 2:
+		*val = readw(addr);
+		break;
+	default:
+		*val = readl(addr);
+	}
+
+	return PCIBIOS_SUCCESSFUL;
+}
+
+static int gen_pci_config_write(struct pci_bus *bus, unsigned int devfn,
+				 int where, int size, u32 val)
+{
+	void __iomem *addr;
+	struct pci_sys_data *sys = bus->sysdata;
+	struct gen_pci *pci = sys->private_data;
+
+	addr = pci->cfg.ops->map_bus(bus, devfn, where);
+
+	switch (size) {
+	case 1:
+		writeb(val, addr);
+		break;
+	case 2:
+		writew(val, addr);
+		break;
+	default:
+		writel(val, addr);
+	}
+
+	return PCIBIOS_SUCCESSFUL;
+}
+
+static struct pci_ops gen_pci_ops = {
+	.read	= gen_pci_config_read,
+	.write	= gen_pci_config_write,
+};
+
+static struct pci_host_bridge_window *
+gen_pci_alloc_init_window(struct device *dev, struct of_pci_range *range,
+			  struct device_node *np)
+{
+	struct pci_host_bridge_window *win;
+
+	win = devm_kzalloc(dev, sizeof(*win), GFP_KERNEL);
+	if (!win)
+		return NULL;
+
+	win->res = devm_kmalloc(dev, sizeof(*win->res), GFP_KERNEL);
+	if (!win->res)
+		goto out_free_win;
+
+	of_pci_range_to_resource(range, np, win->res);
+	INIT_LIST_HEAD(&win->list);
+	return win;
+
+out_free_win:
+	devm_kfree(dev, win);
+	return NULL;
+}
+
+static void gen_pci_free_window(struct device *dev,
+				struct pci_host_bridge_window *win)
+{
+	devm_kfree(dev, win->res);
+	devm_kfree(dev, win);
+}
+
+static int gen_pci_alloc_io_offset(u32 sz, resource_size_t *offset)
+{
+	static DECLARE_BITMAP(io_map, (IO_SPACE_LIMIT + 1) / SZ_64K);
+	int idx, num_wins;
+
+	if (sz > SZ_64K)
+		return -ENOSPC;
+
+	num_wins = (IO_SPACE_LIMIT + 1) / SZ_64K;
+	idx = 0;
+	do {
+		idx = find_next_zero_bit(io_map, num_wins, idx);
+		if (idx == num_wins)
+			return -ENOSPC;
+	} while (test_and_set_bit(idx, io_map));
+
+	*offset = idx * SZ_64K;
+	return 0;
+}
+
+static const struct of_device_id gen_pci_of_match[] = {
+	{ .compatible = "pci-host-cam-generic",
+	  .data = &gen_pci_cfg_cam_bus_ops },
+
+	{ .compatible = "pci-host-ecam-generic",
+	  .data = &gen_pci_cfg_ecam_bus_ops },
+
+	{ },
+};
+MODULE_DEVICE_TABLE(of, gen_pci_of_match);
+
+static int gen_pci_setup(int nr, struct pci_sys_data *sys)
+{
+	int err, res_valid;
+	u8 bus_max;
+	struct pci_host_bridge_window *win, *tmp;
+	struct resource *bus_range;
+	resource_size_t busn;
+	const char *type;
+	struct of_pci_range range;
+	struct of_pci_range_parser parser;
+	const struct of_device_id *of_id;
+	struct gen_pci *pci = sys->private_data;
+	struct device *dev = pci->host.dev.parent;
+	struct device_node *np = dev->of_node;
+
+	type = of_get_property(np, "device_type", NULL);
+	if (!type || strcmp(type, "pci")) {
+		dev_err(dev, "invalid \"device_type\" %s\n", type);
+		return -EINVAL;
+	}
+
+	if (of_pci_range_parser_init(&parser, np)) {
+		dev_err(dev, "missing \"ranges\" property\n");
+		return -EINVAL;
+	}
+
+	if (of_pci_parse_bus_range(np, &pci->cfg.bus_range))
+		pci->cfg.bus_range = (struct resource) {
+			.name	= np->name,
+			.start	= 0,
+			.end	= 0xff,
+			.flags	= IORESOURCE_BUS,
+		};
+
+	err = of_address_to_resource(np, 0, &pci->cfg.res);
+	if (err) {
+		dev_err(dev, "missing \"reg\" property\n");
+		return err;
+	}
+
+	pci->cfg.win = devm_kcalloc(dev, resource_size(&pci->cfg.bus_range),
+				    sizeof(*pci->cfg.win), GFP_KERNEL);
+	if (!pci->cfg.win)
+		return -ENOMEM;
+
+	of_id = of_match_node(gen_pci_of_match, np);
+	pci->cfg.ops = of_id->data;
+	INIT_LIST_HEAD(&pci->host.windows);
+
+	/* Limit the bus-range to fit within reg */
+	bus_max = pci->cfg.bus_range.start +
+		  (resource_size(&pci->cfg.res) >> pci->cfg.ops->bus_shift) - 1;
+	pci->cfg.bus_range.end = min_t(resource_size_t, pci->cfg.bus_range.end,
+				       bus_max);
+
+	/* Create windows from the ranges property */
+	for_each_of_pci_range(&parser, &range) {
+		resource_size_t offset;
+		u32 restype = range.flags & IORESOURCE_TYPE_BITS;
+
+		if (restype == IORESOURCE_IO) {
+			err = gen_pci_alloc_io_offset(range.size, &offset);
+			if (err) {
+				dev_warn(dev,
+					 "failed to allocate IO window of %lld bytes\n",
+					 range.size);
+				continue;
+			}
+		} else if (restype == IORESOURCE_MEM) {
+			offset = range.cpu_addr - range.pci_addr;
+		} else {
+			dev_warn(dev,
+				"ignoring unknown/unsupported resource type %x\n",
+				 restype);
+			continue;
+		}
+
+		win = gen_pci_alloc_init_window(dev, &range, np);
+		if (!win) {
+			err = -ENOMEM;
+			goto out_free_res;
+		}
+
+		win->offset = offset;
+		list_add(&win->list, &pci->host.windows);
+	}
+
+	/* Map our Configuration Space windows */
+	if (!request_mem_region(pci->cfg.res.start,
+				resource_size(&pci->cfg.res),
+				"Configuration Space"))
+		goto out_free_res;
+
+	bus_range = &pci->cfg.bus_range;
+	for (busn = bus_range->start; busn <= bus_range->end; ++busn) {
+		u32 idx = busn - bus_range->start;
+		u32 sz = 1 << pci->cfg.ops->bus_shift;
+
+		pci->cfg.win[idx] = devm_ioremap(dev,
+						 pci->cfg.res.start + busn * sz,
+						 sz);
+		if (!pci->cfg.win[idx]) {
+			err = -ENOMEM;
+			goto out_unmap_cfg;
+		}
+	}
+
+	/* Register our I/O and Memory resources */
+	res_valid = 0;
+	list_for_each_entry(win, &pci->host.windows, list) {
+		struct resource *parent;
+
+		if (resource_type(win->res) == IORESOURCE_IO) {
+			parent = &ioport_resource;
+			err = pci_ioremap_io(win->offset, win->res->start);
+			if (err)
+				goto out_release_res;
+		} else {
+			res_valid |= !(win->res->flags & IORESOURCE_PREFETCH);
+			parent = &iomem_resource;
+		}
+
+		err = request_resource(parent, win->res);
+		if (err)
+			goto out_release_res;
+
+		pci_add_resource_offset(&sys->resources, win->res, win->offset);
+	}
+
+	if (!res_valid) {
+		dev_err(dev, "non-prefetchable memory resource required\n");
+		err = -EINVAL;
+		goto out_release_res;
+	}
+
+	/* Register bus resource */
+	pci_add_resource(&sys->resources, bus_range);
+	return 1;
+
+out_release_res:
+	list_for_each_entry_continue_reverse(win, &pci->host.windows, list)
+		release_resource(win->res);
+out_unmap_cfg:
+	while (busn-- > bus_range->start)
+		devm_iounmap(dev, pci->cfg.win[busn-bus_range->start]);
+	release_resource(&pci->cfg.res);
+out_free_res:
+	list_for_each_entry_safe(win, tmp, &pci->host.windows, list)
+		gen_pci_free_window(dev, win);
+	devm_kfree(dev, pci->cfg.win);
+	return err;
+}
+
+static int gen_pci_probe(struct platform_device *pdev)
+{
+	struct hw_pci hw;
+	struct gen_pci *pci;
+	struct device *dev = &pdev->dev;
+
+	if (!dev->of_node)
+		return -ENODEV;
+
+	pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL);
+	if (!pci)
+		return -ENOMEM;
+
+	pci->host.dev.parent = dev;
+	hw = (struct hw_pci) {
+		.nr_controllers	= 1,
+		.private_data	= (void **)&pci,
+		.setup		= gen_pci_setup,
+		.map_irq	= of_irq_parse_and_map_pci,
+		.ops		= &gen_pci_ops,
+	};
+
+	pci_common_init_dev(dev, &hw);
+	return 0;
+}
+
+static struct platform_driver gen_pci_driver = {
+	.driver = {
+		.name = "pci-host-generic",
+		.owner = THIS_MODULE,
+		.of_match_table = gen_pci_of_match,
+	},
+	.probe = gen_pci_probe,
+};
+module_platform_driver(gen_pci_driver);
+
+MODULE_DESCRIPTION("Generic PCI host driver");
+MODULE_AUTHOR("Will Deacon <will.deacon@arm.com>");
+MODULE_LICENSE("GPLv2");