The series of patches intend to support PCI slot for PowerPC PowerNV platform,
which is running on top of skiboot firmware. The patchset requires corresponding
changes from skiboot firmware, which is sent to skiboot@lists.ozlabs.org
for review. The PCI slots are exposed by skiboot with device node properties,
and kernel utilizes those properties to populated PCI slots accordingly.
The original PCI infrastructure on PowerNV platform can't support hotplug
because the PE is assigned during PHB fixup time, which is called for once
during system boot time. For this, the PCI infrastructure on PowerNV platform
has been reworked for a lot. After that, the PE and its corresponding resources
(IODT, M32DT, M64 segments, DMA32 and bypass window) are assigned upon updating
PCI bridge's resources, which might decide PE# assigned to the PE (e.g. M64
resources, on P8 strictly speaking). Each PE will maintain a reference count,
which is (number of child PCI devices + 1). That indicates when last child PCI
device leaves the PE, the PE and its included resources will be relased and put
back into free pool again. With this design, the PE will be released when EEH PE
is released. PATCH[1 - 24] are related to this part.
From skiboot perspective, PCI slot is providing (hot/fundamental/complete)
resets to EEH. The kernel gets to know if skiboot supports various reset on one
particular PCI slot through device-tree node. If it does, EEH will utilize the
functionality provided by skiboot. Besides, the device-tree nodes have to change
in order to support PCI hotplug. For example, when one PCI adapter inserted to
one slot, its device-tree node should be added to the system dynamically. Conversely,
the device-tree node should be removed from the system when the PCI adapter is going
to be offline. Since pci_dn and eeh_dev have same life cyle as PCI device nodes,
they should be added/removed accordingly during PCI hotplug. PATCH[25 - 38] are
doing the related work.
The OF driver is changed to support unflattening FDT blob for sub-stree, which
is covered by PATCH[39 - 41].
The last patch is the standalone PCI hotplug driver for PowerNV platform. When
removing PCI adapter from one PCI slot, which is invoked by command in userland,
the skiboot will power off the slot to save power and remove all device-tree
nodes for all PCI devices behind the slot. Conversely, the Power to the slot
is turned on, the PCI devices behind the slot is rescanned, and the device-tree
nodes for those newly detected PCI devices will be built in skiboot. For both
of cases, one message will be sent to kernel by skiboot so that the kernel
can adjust the device-tree accordingly. At the same time, the kernel also have
to deallocate or allocate PE# and its related resources (PE# and so on) for the
removed/added PCI devices.
Changelog
=========
v5:
* Rebased to 4.1.rc6 and some unmerged patches as below:
Alexey's DDW patchset (v11);
Gavin's EEH error injection support (in mpe's next branch);
Richard's EEH cleanup patches (in mpe's next branch);
Richard's EEH support for VF (v7);
Gavin's misc EEH fixes for 4.2;
* The revision bases on skiboot corresponding patches (v7):
https://patchwork.ozlabs.org/patch/480437/
* Utilize OF overlay to update device-tree with help of newly introduced
OPAL API opal_get_overlay_dt().
* Split patches for easy review according to aik's comments.
* Fix coding style from checkpatchc.pl as pointed by aik.
* Code cleanup and misc fixup according to aik's input.
v4:
* Rebased to 4.1.RC1
* Added API to unflatten FDT blob to device node sub-tree, which is attached
the indicated parent device node. The original mechanism based on formatted
string stream has been dropped.
* The PATCH[v3 09/21] ("powerpc/eeh: Delay probing EEH device during hotplug")
was picked up sent to linux-ppc@ separately for review as Richard's "VF EEH
Support" depends on that.
v3:
* Rebased to 4.1.RC0
* PowerNV PCI infrasturcture is total refactored in order to support PCI
hotplug. The PowerNV hotplug driver is also reworked a lot because of
the changes in skiboot in order to support PCI hotplug.
Gavin Shan (42):
PCI: Add pcibios_setup_bridge()
powerpc/powernv: Enable M64 on P7IOC
powerpc/powernv: M64 support improvement
powerpc/powernv: Trace consumed IO and M32 segments by PE
powerpc/powernv: Simplify pnv_ioda_setup_pe_seg()
powerpc/powernv: Improve IO and M32 mapping
powerpc/powernv: Calculate PHB's DMA weight dynamically
powerpc/powernv: DMA32 cleanup
powerpc/powernv: pnv_ioda_setup_dma() configure one PE only
powerpc/powernv: Trace DMA32 segments consumed by PE
powerpc/powernv: Increase PE# capacity
powerpc/pci: Cleanup on pci_controller_ops
powerpc/pci: Override pcibios_setup_bridge()
powerpc/powernv: Allocate PE# in deasending order
powerpc/powernv: Reserve PE# for root bus
powerpc/powernv: Create PEs dynamically
powerpc/powernv: PE oriented during configuration
powerpc/powernv: Helper function pnv_ioda_init_pe()
powerpc/powernv: Remove DMA32 list of PEs
powerpc/powernv: Rename pnv_ioda_get_pe() to pnv_ioda_dev_to_pe()
powerpc/powernv: Drop pnv_ioda_setup_dev_PE()
powerpc/powernv: Move functions around
powerpc/powernv: Cleanup on pnv_pci_ioda2_release_dma_pe()
powerpc/powernv: Release PEs dynamically
powerpc/powernv: Supports slot ID
powerpc/powernv: Use PCI slot reset infrastructure
powerpc/powernv: Simplify pnv_eeh_reset()
powerpc/powernv: Don't cover root bus in pnv_pci_reset_secondary_bus()
powerpc/powernv: Issue fundamental reset in
pnv_pci_reset_secondary_bus()
powerpc/pci: Don't scan empty slot
powerpc/pci: Move pcibios_find_pci_bus() around
powerpc/powernv: Introduce pnv_pci_poll()
powerpc/powernv: Functions to get/reset PCI slot status
powerpc/pci: Delay creating pci_dn
powerpc/pci: Create eeh_dev while creating pci_dn
powerpc/pci: Export traverse_pci_device_nodes()
powerpc/pci: Update bridge windows on PCI plugging
powerpc/powernv: Select OF_OVERLAY
drivers/of: Unflatten nodes equal or deeper than specified level
drivers/of: Allow to specify root node in of_fdt_unflatten_tree()
drivers/of: Return allocated memory chunk from of_fdt_unflatten_tree()
pci/hotplug: PowerPC PowerNV PCI hotplug driver
MAINTAINERS | 6 +
arch/powerpc/include/asm/eeh.h | 6 +-
arch/powerpc/include/asm/opal-api.h | 8 +-
arch/powerpc/include/asm/opal.h | 8 +-
arch/powerpc/include/asm/pci-bridge.h | 14 +-
arch/powerpc/include/asm/pnv-pci.h | 7 +
arch/powerpc/include/asm/ppc-pci.h | 8 +-
arch/powerpc/kernel/eeh_dev.c | 20 +-
arch/powerpc/kernel/pci-common.c | 16 +-
arch/powerpc/kernel/pci-hotplug.c | 44 +-
arch/powerpc/kernel/pci_dn.c | 91 +-
arch/powerpc/platforms/maple/pci.c | 35 +-
arch/powerpc/platforms/pasemi/pci.c | 3 -
arch/powerpc/platforms/powermac/pci.c | 39 +-
arch/powerpc/platforms/powernv/Kconfig | 1 +
arch/powerpc/platforms/powernv/eeh-powernv.c | 180 +--
arch/powerpc/platforms/powernv/opal-wrappers.S | 4 +
arch/powerpc/platforms/powernv/pci-ioda.c | 1770 ++++++++++++++----------
arch/powerpc/platforms/powernv/pci.c | 90 +-
arch/powerpc/platforms/powernv/pci.h | 59 +-
arch/powerpc/platforms/pseries/msi.c | 4 +-
arch/powerpc/platforms/pseries/pci_dlpar.c | 32 -
arch/powerpc/platforms/pseries/setup.c | 9 +-
drivers/of/fdt.c | 85 +-
drivers/of/unittest.c | 2 +-
drivers/pci/hotplug/Kconfig | 12 +
drivers/pci/hotplug/Makefile | 4 +
drivers/pci/hotplug/powernv_php.c | 140 ++
drivers/pci/hotplug/powernv_php.h | 90 ++
drivers/pci/hotplug/powernv_php_slot.c | 732 ++++++++++
drivers/pci/setup-bus.c | 5 +
include/linux/of_fdt.h | 5 +-
include/linux/pci.h | 1 +
33 files changed, 2559 insertions(+), 971 deletions(-)
create mode 100644 drivers/pci/hotplug/powernv_php.c
create mode 100644 drivers/pci/hotplug/powernv_php.h
create mode 100644 drivers/pci/hotplug/powernv_php_slot.c
--
2.1.0
Currently, PowerPC PowerNV platform utilizes ppc_md.pcibios_fixup(),
which is called for once after PCI probing and resource assignment
are completed, to allocate platform required resources for PCI devices:
PE#, IO and MMIO mapping, DMA address translation (TCE) table etc.
Obviously, it's not hotplug friendly.
The patch adds weak function pcibios_setup_bridge(), which is called
by pci_setup_bridge(). PowerPC PowerNV platform will reuse the function
to assign above platform required resources to newly added PCI devices,
in order to support PCI hotplug in subsequent patches.
Signed-off-by: Gavin Shan <redacted>
---
v5:
* Corrected subject as Bjorn suggested
* pci_setup_bridge() calls pcibios_setup_bridge() and __pci_setup_bridge()
---
drivers/pci/setup-bus.c | 5 +++++
include/linux/pci.h | 1 +
2 files changed, 6 insertions(+)
The patch enables M64 window on P7IOC, which has been enabled on
PHB3. Different from PHB3 where 16 M64 BARs are supported and each
of them can be owned by one particular PE# exclusively or divided
evenly to 256 segments, each P7IOC PHB has 16 M64 BARs and each
of them are divided into 8 segments. So each P7IOC PHB can support
128 M64 segments only. Also, P7IOC has M64DT, which helps mapping
one particular M64 segment# to arbitrary PE#. PHB3 doesn't have
M64DT, indicating that one M64 segment can only be pinned to the
fixed PE#. In order to have similar logic to support M64 for PHB3
and P7IOC, we just provide 128 M64 (16 BARs) segments and fixed
mapping between PE# and M64 segment# on P7IOC. In turn, we just
need different phb->init_m64() hooks for P7IOC and PHB3 to support
M64.
Signed-off-by: Gavin Shan <redacted>
---
v5:
* Pinned OPAL API return value type to "int64_t"
* Don't initialize M64 callbacks for unknown PHB type
* Fixed comments as suggested by aik
* Fixed coding style complained by checkpatch.pl
---
arch/powerpc/platforms/powernv/pci-ioda.c | 110 ++++++++++++++++++++++++++----
1 file changed, 98 insertions(+), 12 deletions(-)
@@ -174,6 +174,69 @@ static void pnv_ioda_free_pe(struct pnv_phb *phb, int pe)clear_bit(pe,phb->ioda.pe_alloc);}+staticintpnv_ioda1_init_m64(structpnv_phb*phb)+{+structresource*r;+intseg;++/* There are as many M64 segments as the maximum number+*ofPEs,whichis128.+*/+for(seg=0;seg<phb->ioda.total_pe;seg+=8){+unsignedlongbase;+int64_trc;++base=phb->ioda.m64_base+seg*phb->ioda.m64_segsize;+rc=opal_pci_set_phb_mem_window(phb->opal_id,+OPAL_M64_WINDOW_TYPE,+seg/8,+base,+0,/* unused */+8*phb->ioda.m64_segsize);+if(rc!=OPAL_SUCCESS){+pr_warn(" Error %lld setting M64 PHB#%d-BAR#%d\n",+rc,phb->hose->global_number,seg/8);+gotofail;+}++rc=opal_pci_phb_mmio_enable(phb->opal_id,+OPAL_M64_WINDOW_TYPE,+seg/8,+OPAL_ENABLE_M64_SPLIT);+if(rc!=OPAL_SUCCESS){+pr_warn(" Error %lld enabling M64 PHB#%d-BAR#%d\n",+rc,phb->hose->global_number,seg/8);+gotofail;+}+}++/* Strip off the segment used by the reserved PE, which+*isexpectedtobe0orlastsupportedPE#.ThePHB's+*firstmemorywindowtracesthe32-bitsMMIOrange+*whilethesecondonetracesthe64-bitsprefetchable+*MMIOrangethatthePHBsupports.+*/+r=&phb->hose->mem_resources[1];+if(phb->ioda.reserved_pe==0)+r->start+=phb->ioda.m64_segsize;+elseif(phb->ioda.reserved_pe==(phb->ioda.total_pe-1))+r->end-=phb->ioda.m64_segsize;+else+pr_warn(" Cannot strip M64 segment for reserved PE#%d\n",+phb->ioda.reserved_pe);++return0;++fail:+for(;seg>=0;seg-=8)+opal_pci_phb_mmio_enable(phb->opal_id,+OPAL_M64_WINDOW_TYPE,+seg/8,+OPAL_DISABLE_M64);++return-EIO;+}+/* The default M64 BAR is shared by all PEs */staticintpnv_ioda2_init_m64(structpnv_phb*phb){
@@ -355,6 +418,26 @@ done:pe->master=master_pe;list_add_tail(&pe->list,&master_pe->slaves);}++/* P7IOC supports M64DT, which helps mapping M64 segment+*tooneparticularPE#.However,PHB3hasfixedmapping+*betweenM64segmentandPE#.Inordertohavesamelogic+*forP7IOCandPHB3,weenforcefixedmappingbetweenM64+*segmentandPE#onP7IOC.+*/+if(phb->type==PNV_PHB_IODA1){+int64_trc;++rc=opal_pci_map_pe_mmio_window(phb->opal_id,+pe->pe_number,+OPAL_M64_WINDOW_TYPE,+pe->pe_number/8,+pe->pe_number%8);+if(rc!=OPAL_SUCCESS)+pr_warn("%s: Error %lld mapping M64 for PHB#%d-PE#%d\n",+__func__,rc,phb->hose->global_number,+pe->pe_number);+}}kfree(pe_alloc);
@@ -369,12 +452,6 @@ static void __init pnv_ioda_parse_m64_window(struct pnv_phb *phb)constu32*r;u64pci_addr;-/* FIXME: Support M64 for P7IOC */-if(phb->type!=PNV_PHB_IODA2){-pr_info(" Not support M64 window\n");-return;-}-if(!firmware_has_feature(FW_FEATURE_OPALv3)){pr_info(" Firmware too old to support M64 window\n");return;
@@ -403,9 +480,18 @@ static void __init pnv_ioda_parse_m64_window(struct pnv_phb *phb)/* Use last M64 BAR to cover M64 window */phb->ioda.m64_bar_idx=15;-phb->init_m64=pnv_ioda2_init_m64;-phb->reserve_m64_pe=pnv_ioda2_reserve_m64_pe;-phb->pick_m64_pe=pnv_ioda2_pick_m64_pe;+phb->reserve_m64_pe=pnv_ioda_reserve_m64_pe;+phb->pick_m64_pe=pnv_ioda_pick_m64_pe;+switch(phb->type){+casePNV_PHB_IODA1:+phb->init_m64=pnv_ioda1_init_m64;+break;+casePNV_PHB_IODA2:+phb->init_m64=pnv_ioda2_init_m64;+break;+default:+pr_debug(" Cannot support M64 for unknown type of PHB\n");+}}staticvoidpnv_ioda_freeze_pe(structpnv_phb*phb,intpe_no)
We're having the hardware (on PHB3) or software enforced (on P7IOC)
limitation: M64 segment#x can only be assigned to PE#x. IO and M32
segment can be mapped to arbitrary PE# via IODT and M32DT. It means
the PE number should be x if M64 segment#x has been assigned to the
PE. Also, each PE owns one M64 segment at most. Currently, we are
reserving PE# according to root port's M64 window. It won't be reliable
once we extend M64 windows of root port, or the upstream port of the
PCIE switch behind root port to PHB's M64 window, in order to support
PCI hotplug in future.
The patch reserves PE# for M64 segments according to the M64 resources
of the PCI devices (not bridges) contained in the PE. Besides, it's
always worthy to trace the M64 segments consumed by the PE, which can
be released at PCI unplugging time.
Signed-off-by: Gavin Shan <redacted>
---
v5:
* Made the changelog more descriptive on the fixed M64 seg# mapping
* Dropped unnecessary and corrected comments pointed by aik
* Replace "pe_bitsmap" with "pe_bitmap"
* Fixed coding style complained by checkpatch.pl
---
arch/powerpc/platforms/powernv/pci-ioda.c | 189 ++++++++++++++++++------------
arch/powerpc/platforms/powernv/pci.h | 10 +-
2 files changed, 121 insertions(+), 78 deletions(-)
@@ -294,28 +294,78 @@ fail:return-EIO;}-staticvoidpnv_ioda_reserve_m64_pe(structpnv_phb*phb)+/* We extend the M64 window of root port, or the upstream bridge port+*ofthePCIEswitchbehindrootport.Soweshouldn'treservePEs+*forM64resourcesbecausethereareno(normal)PCIdevicesconsuming+*M64resourcesonthePCIbusesleadingfromrootport,ortheupstream+*bridgeport.ThefunctionreturnstrueiftheindicatedPCIbusneeds+*reservedPEsbecauseofM64resourcesinadvance.Otherwise,the+*functionreturnsfalse.+*/+staticboolpnv_ioda_need_m64_pe(structpnv_phb*phb,+structpci_bus*bus){-resource_size_tsgsz=phb->ioda.m64_segsize;+if(!bus||pci_is_root_bus(bus))+returnfalse;++/* Bus leading from root port. We need check what types of PCI+*devicesonthebus.Ifit'sconnectingPCIbridge,wedon't+*needreserveM64PEsforit.Otherwise,westillneedtodo+*that.+*/+if(pci_is_root_bus(bus->self->bus)){+structpci_dev*pdev;++list_for_each_entry(pdev,&bus->devices,bus_list){+if(pdev->hdr_type==PCI_HEADER_TYPE_NORMAL)+returntrue;+}++returnfalse;+}++/* Bus leading from the upstream bridge port on top level */+if(pci_is_root_bus(bus->self->bus->self->bus))+returnfalse;++returntrue;+}++staticvoidpnv_ioda_reserve_m64_pe(structpnv_phb*phb,+structpci_bus*bus)+{+resource_size_tsegsz=phb->ioda.m64_segsize;structpci_dev*pdev;structresource*r;-intbase,step,i;+unsignedlongpe_no,limit;+inti;-/*-*RootbusalwayshasfullM64rangeandrootporthas-*M64rangeusedinreality.Sowe'recheckingrootport-*insteadofrootbus.+if(!pnv_ioda_need_m64_pe(phb,bus))+return;++/* The bridge's M64 window might have been extended to the+*PHB'sM64windowinordertosupportPCIhotplug.Sothe+*bridge'sM64windowisn'treliabletobeusedforpicking+*PE#foritsleadingPCIbus.WehavetochecktheM64+*resourcesconsumedbythePCIdevices,whichseatonthe+*PCIbus.*/-list_for_each_entry(pdev,&phb->hose->bus->devices,bus_list){-for(i=0;i<PCI_BRIDGE_RESOURCE_NUM;i++){-r=&pdev->resource[PCI_BRIDGE_RESOURCES+i];-if(!r->parent||-!pnv_pci_is_mem_pref_64(r->flags))+list_for_each_entry(pdev,&bus->devices,bus_list){+for(i=0;i<PCI_NUM_RESOURCES;i++){+#ifdef CONFIG_PCI_IOV+if(i>=PCI_IOV_RESOURCES&&i<=PCI_IOV_RESOURCE_END)+continue;+#endif+r=&pdev->resource[i];+if(!r->flags||r->start>=r->end||+!r->parent||!pnv_pci_is_mem_pref_64(r->flags))continue;-base=(r->start-phb->ioda.m64_base)/sgsz;-for(step=0;step<resource_size(r)/sgsz;step++)-pnv_ioda_reserve_pe(phb,base+step);+pe_no=(r->start-phb->ioda.m64_base)/segsz;+limit=ALIGN(r->end-phb->ioda.m64_base,segsz)/+segsz;+for(;pe_no<limit;pe_no++)+pnv_ioda_reserve_pe(phb,pe_no);}}}
@@ -327,85 +377,63 @@ static int pnv_ioda_pick_m64_pe(struct pnv_phb *phb,structpci_dev*pdev;structresource*r;structpnv_ioda_pe*master_pe,*pe;-unsignedlongsize,*pe_alloc;-boolfound;-intstart,i,j;--/* Root bus shouldn't use M64 */-if(pci_is_root_bus(bus))-returnIODA_INVALID_PE;--/* We support only one M64 window on each bus */-found=false;-pci_bus_for_each_resource(bus,r,i){-if(r&&r->parent&&-pnv_pci_is_mem_pref_64(r->flags)){-found=true;-break;-}-}+unsignedlongsize,*pe_bitmap;+unsignedlongpe_no,limit;+inti;-/* No M64 window found ? */-if(!found)+if(!pnv_ioda_need_m64_pe(phb,bus))returnIODA_INVALID_PE;/* Allocate bitmap */size=_ALIGN_UP(phb->ioda.total_pe/8,sizeof(unsignedlong));-pe_alloc=kzalloc(size,GFP_KERNEL);-if(!pe_alloc){-pr_warn("%s: Out of memory !\n",-__func__);+pe_bitmap=kzalloc(size,GFP_KERNEL);+if(!pe_bitmap)returnIODA_INVALID_PE;-}-/*-*FigureoutreservedPEnumbersbythePE-*theitschildPEs.-*/-start=(r->start-phb->ioda.m64_base)/segsz;-for(i=0;i<resource_size(r)/segsz;i++)-set_bit(start+i,pe_alloc);--if(all)-gotodone;--/*-*IfthePEdoesn'tcoverallsubordinatebuses,-*weneedsubtractfromreservedPEsforchildren.+/* The bridge's M64 window might be extended to PHB's M64+*windowbyintentiontosupportPCIhotplug.Sowehave+*tochecktheM64resourcesconsumedbythePCIdevices+*onthePCIbus.*/list_for_each_entry(pdev,&bus->devices,bus_list){-if(!pdev->subordinate)-continue;+for(i=0;i<PCI_NUM_RESOURCES;i++){+#ifdef CONFIG_PCI_IOV+if(i>=PCI_IOV_RESOURCES&&+i<=PCI_IOV_RESOURCE_END)+continue;+#endif+/* Don't scan bridge's window if the PE+*doesn'tcontainitssubordinatebus.+*/+if(!all&&i>=PCI_BRIDGE_RESOURCES&&+i<=PCI_BRIDGE_RESOURCE_END)+continue;-pci_bus_for_each_resource(pdev->subordinate,r,i){-if(!r||!r->parent||-!pnv_pci_is_mem_pref_64(r->flags))+r=&pdev->resource[i];+if(!r->flags||r->start>=r->end||+!r->parent||!pnv_pci_is_mem_pref_64(r->flags))continue;-start=(r->start-phb->ioda.m64_base)/segsz;-for(j=0;j<resource_size(r)/segsz;j++)-clear_bit(start+j,pe_alloc);-}-}+pe_no=(r->start-phb->ioda.m64_base)/segsz;+limit=ALIGN(r->end-phb->ioda.m64_base,segsz)/+segsz;+for(;pe_no<limit;pe_no++)+set_bit(pe_no,pe_bitmap);+}+}-/*-*thecurrentbusmightnotownM64windowandthat'sall-*contributedbyitschildbuses.Forthecase,weneedn't-*pickM64dependentPE#.-*/-if(bitmap_empty(pe_alloc,phb->ioda.total_pe)){-kfree(pe_alloc);+/* No M64 window found ? */+if(bitmap_empty(pe_bitmap,phb->ioda.total_pe)){+kfree(pe_bitmap);returnIODA_INVALID_PE;}-/*-*FigureoutthemasterPEandputallslavePEstomaster-*PE'slisttoformcompoundPE.+/* Figure out the master PE and put all slave PEs+*tomasterPE'slisttoformcompoundPE.*/-done:master_pe=NULL;i=-1;-while((i=find_next_bit(pe_alloc,phb->ioda.total_pe,i+1))<+while((i=find_next_bit(pe_bitmap,phb->ioda.total_pe,i+1))<phb->ioda.total_pe){pe=&phb->ioda.pe_array[i];
@@ -419,6 +447,13 @@ done:list_add_tail(&pe->list,&master_pe->slaves);}+/* Reserve the M64 segment, which should be available. Also,+*thoseM64segmentsconsumedbyslavePEsarecontributed+*tothemasterPE.+*/+BUG_ON(test_and_set_bit(pe->pe_number,phb->ioda.m64_segmap));+BUG_ON(test_and_set_bit(pe->pe_number,master_pe->m64_segmap));+/* P7IOC supports M64DT, which helps mapping M64 segment*tooneparticularPE#.However,PHB3hasfixedmapping*betweenM64segmentandPE#.Inordertohavesamelogic
@@ -49,6 +49,13 @@ struct pnv_ioda_pe {/* PE number */unsignedintpe_number;+/* IO/M32/M64 segments consumed by the PE. Each PE can+*haveoneM64segmentatmost,butM64segmentsconsumed+*byslavePEswillbecontributedtothemasterPE.One+*PEcanownmultipleIOandM32segments.+*/+unsignedlongm64_segmap[8];+/* "Weight" assigned to the PE for the sake of DMA resource*allocations*/
For P7IOC, the whole available DMA32 space, which is below the
MEM32 space, is divided evenly into 256MB segments. How many
continuous segments assigned to one particular PE depends on
the PE's DMA weight that is figured out from the type of each
PCI devices contained in the PE, and PHB's DMA weight which is
accumulative DMA weight of PEs contained in the PHB. It means
that the PHB's DMA weight calculation depends on existing PEs,
which works perfectly now, but not hotplug friendly. As the
whole available DMA32 space can be assigned to one PE on PHB3,
so we don't have the issue on PHB3.
The patch calculates PHB's DMA weight based on the PCI devices
contained in the PHB dynamically so that it's hotplug friendly.
At the meanwhile, the patch removes the code handling DMA weight
for PHB3 in pnv_ioda_setup_dma().
Signed-off-by: Gavin Shan <redacted>
---
v5:
* Split from PATCH[v4 5/21]
* Fixed line over 80 characters reported from checkpatch.pl
---
arch/powerpc/platforms/powernv/pci-ioda.c | 90 +++++++++++++++----------------
arch/powerpc/platforms/powernv/pci.h | 6 ---
2 files changed, 44 insertions(+), 52 deletions(-)
@@ -979,8 +979,11 @@ static void pnv_ioda_link_pe_by_weight(struct pnv_phb *phb,list_add_tail(&pe->dma_link,&phb->ioda.pe_dma_list);}-staticunsignedintpnv_ioda_dma_weight(structpci_dev*dev)+staticunsignedintpnv_ioda_dev_dma_weight(structpci_dev*dev){+structpci_controller*hose=pci_bus_to_host(dev->bus);+structpnv_phb*phb=hose->private_data;+/* This is quite simplistic. The "base" weight of a device*is10.0meansnoDMAistobeaccountedforit.*/
@@ -993,14 +996,34 @@ static unsigned int pnv_ioda_dma_weight(struct pci_dev *dev)if(dev->class==PCI_CLASS_SERIAL_USB_UHCI||dev->class==PCI_CLASS_SERIAL_USB_OHCI||dev->class==PCI_CLASS_SERIAL_USB_EHCI)-return3;+return3*phb->ioda.tce32_count;/* Increase the weight of RAID (includes Obsidian) */if((dev->class>>8)==PCI_CLASS_STORAGE_RAID)-return15;+return15*phb->ioda.tce32_count;/* Default */-return10;+return10*phb->ioda.tce32_count;+}++staticint__pnv_ioda_phb_dma_weight(structpci_dev*pdev,void*data)+{+unsignedint*dma_weight=data;++*dma_weight+=pnv_ioda_dev_dma_weight(pdev);+return0;+}++staticunsignedintpnv_ioda_phb_dma_weight(structpnv_phb*phb)+{+unsignedintdma_weight=0;++if(!phb->hose->bus)+returndma_weight;++pci_walk_bus(phb->hose->bus,+__pnv_ioda_phb_dma_weight,&dma_weight);+returndma_weight;}#ifdef CONFIG_PCI_IOV
@@ -1222,14 +1245,6 @@ static void pnv_ioda_setup_bus_PE(struct pci_bus *bus, int all)/* Put PE to the list */list_add_tail(&pe->list,&phb->ioda.pe_list);-/* Account for one DMA PE if at least one DMA capable device exist-*belowthebridge-*/-if(pe->dma_weight!=0){-phb->ioda.dma_weight+=pe->dma_weight;-phb->ioda.dma_pe_count++;-}-/* Link the PE */pnv_ioda_link_pe_by_weight(phb,pe);}
@@ -2546,24 +2561,13 @@ static void pnv_pci_ioda2_setup_dma_pe(struct pnv_phb *phb,staticvoidpnv_ioda_setup_dma(structpnv_phb*phb){structpci_controller*hose=phb->hose;-unsignedintresidual,remaining,segs,tw,base;structpnv_ioda_pe*pe;+unsignedintdma_weight;-/* If we have more PE# than segments available, hand out one-*perPEuntilwerunoutandlettherestfail.Ifnot,-*thenweassignatleastonesegmentperPE,plusmorebased-*ontheamountofdevicesunderthatPE-*/-if(phb->ioda.dma_pe_count>phb->ioda.tce32_count)-residual=0;-else-residual=phb->ioda.tce32_count--phb->ioda.dma_pe_count;--pr_info("PCI: Domain %04x has %ld available 32-bit DMA segments\n",-hose->global_number,phb->ioda.tce32_count);-pr_info("PCI: %d PE# for a total weight of %d\n",-phb->ioda.dma_pe_count,phb->ioda.dma_weight);+/* Calculate the PHB's DMA weight */+dma_weight=pnv_ioda_phb_dma_weight(phb);+pr_info("PCI%04x has %ld DMA32 segments, total weight %d\n",+hose->global_number,phb->ioda.tce32_count,dma_weight);pnv_pci_ioda_setup_opal_tce_kill(phb);
@@ -185,12 +185,6 @@ struct pnv_phb {/* 32-bit TCE tables allocation */unsignedlongtce32_count;-/* Total "weight" for the sake of DMA resources-*allocation-*/-unsignedintdma_weight;-unsignedintdma_pe_count;-/* Sorted list of used PE's, sorted at*bootforresourceallocationpurposes*/
The patch cleans up DMA32 in pci-ioda.c. It shouldn't introduce
behavioural changes:
* Rename various fields in "struct pnv_phb" and "struct pnv_ioda_pe"
as 32-bits DMA should be related to "DMA", not "TCE", and move
them around to reflect their relationship and their relative
importance.
* Removed struct pnv_ioda_pe::tce32_segcount.
Signed-off-by: Gavin Shan <redacted>
---
v5:
* Split from PATCH[v4 5/21]
---
arch/powerpc/platforms/powernv/pci-ioda.c | 48 +++++++++++++++----------------
arch/powerpc/platforms/powernv/pci.h | 13 +++------
2 files changed, 28 insertions(+), 33 deletions(-)
@@ -2061,7 +2061,7 @@ static void pnv_pci_ioda_setup_dma_pe(struct pnv_phb *phb,/* XXX FIXME: Allocate multi-level tables on PHB3 *//* We shouldn't already have a 32-bit DMA associated */-if(WARN_ON(pe->tce32_seg>=0))+if(WARN_ON(pe->dma32_seg>=0))return;tbl=pnv_pci_table_alloc(phb->hose->node);
@@ -2070,7 +2070,7 @@ static void pnv_pci_ioda_setup_dma_pe(struct pnv_phb *phb,pnv_pci_link_table_and_group(phb->hose->node,0,tbl,&pe->table_group);/* Grab a 32-bit TCE table */-pe->tce32_seg=base;+pe->dma32_seg=base;pe_info(pe," Setting up 32-bit TCE table at %08x..%08x\n",(base<<28),((base+segs)<<28)-1);
@@ -2131,8 +2131,8 @@ static void pnv_pci_ioda_setup_dma_pe(struct pnv_phb *phb,return;fail:/* XXX Failure: Try to fallback to 64-bit only ? */-if(pe->tce32_seg>=0)-pe->tce32_seg=-1;+if(pe->dma32_seg>=0)+pe->dma32_seg=-1;if(tce_mem)__free_pages(tce_mem,get_order(TCE32_TABLE_SIZE*segs));if(tbl){
@@ -2520,7 +2520,7 @@ static void pnv_pci_ioda2_setup_dma_pe(struct pnv_phb *phb,int64_trc;/* We shouldn't already have a 32-bit DMA associated */-if(WARN_ON(pe->tce32_seg>=0))+if(WARN_ON(pe->dma32_seg>=0))return;/* TVE #1 is selected by PCI address bit 59 */
@@ -2530,7 +2530,7 @@ static void pnv_pci_ioda2_setup_dma_pe(struct pnv_phb *phb,pe->pe_number);/* The PE will reserve all possible 32-bits space */-pe->tce32_seg=0;+pe->dma32_seg=0;pe_info(pe,"Setting up 32-bit TCE table at 0..%08x\n",phb->ioda.m32_pci_base);
@@ -3314,7 +3314,7 @@ static void __init pnv_pci_init_ioda_phb(struct device_node *np,mutex_init(&phb->ioda.pe_list_mutex);/* Calculate how many 32-bit TCE segments we have */-phb->ioda.tce32_count=phb->ioda.m32_pci_base>>28;+phb->ioda.dma32_segcount=phb->ioda.m32_pci_base>>28;#if 0 /* We should really do that ... */rc=opal_pci_set_phb_mem_window(opal->phb_id,
@@ -58,15 +58,10 @@ struct pnv_ioda_pe {unsignedlongm32_segmap[8];unsignedlongm64_segmap[8];-/* "Weight" assigned to the PE for the sake of DMA resource-*allocations-*/-unsignedintdma_weight;-/* "Base" iommu table, ie, 4K TCEs, 32-bit DMA */-inttce32_seg;-inttce32_segcount;structiommu_table_grouptable_group;+intdma32_seg;+unsignedintdma32_weight;/* 64-bit TCE bypass region */booltce_bypass_enabled;
@@ -182,8 +177,8 @@ struct pnv_phb {*/unsignedcharpe_rmap[0x10000];-/* 32-bit TCE tables allocation */-unsignedlongtce32_count;+/* Number of 32-bit DMA segments */+unsignedlongdma32_segcount;/* Sorted list of used PE's, sorted at*bootforresourceallocationpurposes
The original implementation of pnv_ioda_setup_dma() iterates the
list of PEs and configures the DMA32 space for them one by one.
The function was designed to be called during PHB fixup time.
When configuring PE's DMA32 space in pcibios_setup_bridge(), in
order to support PCI hotplug, we have to have the function PE
oriented.
The patch introduces one more argument "struct pnv_ioda_pe *pe"
to pnv_ioda_setup_dma(). The caller, pnv_pci_ioda_setup_DMA(),
gets PE from the list and passes to it. The patch shouldn't
cause logic changes.
Signed-off-by: Gavin Shan <redacted>
---
v5:
* Split from PATCH[v4 06/21]
---
arch/powerpc/platforms/powernv/pci-ioda.c | 60 ++++++++++++++-----------------
1 file changed, 27 insertions(+), 33 deletions(-)
Each PHB maintains an array helping to translate RID (Request
ID) to PE# with the assumption that PE# takes 8 bits, indicating
that we can't have more than 256 PEs. However, pci_dn->pe_number
already had 4-bytes for the PE#.
The patch extends the PE# capacity so that each of them will be
4-bytes long. Then we can use IODA_INVALID_PE to check one entry
in phb->pe_rmap[] is valid or not.
Signed-off-by: Gavin Shan <redacted>
---
v5:
* Split from [PATCH v5 v4 06/21]
---
arch/powerpc/platforms/powernv/pci-ioda.c | 5 ++++-
arch/powerpc/platforms/powernv/pci.h | 5 ++---
2 files changed, 6 insertions(+), 4 deletions(-)
@@ -175,11 +175,10 @@ struct pnv_phb {structlist_headpe_list;structmutexpe_list_mutex;-/* Reverse map of PEs, will have to extend if-*wearetosupportmorethan256PEs,indexed+/* Reverse map of PEs, indexed by*bus{bus,devfn}*/-unsignedcharpe_rmap[0x10000];+intpe_rmap[0x10000];/* Number of 32-bit DMA segments */unsignedlongdma32_segcount;
Each PHB maintains one instance of "struct pci_controller_ops",
which includes various callbacks called by PCI subsystem. In the
definition of this struct, some callbacks have explicit names for
its arguments, but the left don't have.
The patch removes all explicit names of the arguments to the
callbacks in "struct pci_controller_ops" to keep the code look
consistent.
Cc: Daniel Axtens <redacted>
Signed-off-by: Gavin Shan <redacted>
---
v5:
* Newly introduced
---
arch/powerpc/include/asm/pci-bridge.h | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
The available PE#, represented by a bitmap in the PHB, is allocated
in asending order. It conflicts with the fact that M64 segments are
assigned in same order. In order to avoid the conflict, the patch
allocates PE# in deasending order.
Signed-off-by: Gavin Shan <redacted>
---
v5:
* Split from [PATCH v5 v4 06/21]
---
arch/powerpc/platforms/powernv/pci-ioda.c | 21 +++++++++++++--------
1 file changed, 13 insertions(+), 8 deletions(-)
Except pnv_ioda_configure_pe(), all PE configuration related functions
are already PE oriented. The patch changes the return value from PE
number to PE instance for its callee for the purpose.
Signed-off-by: Gavin Shan <redacted>
---
v5:
* Split from PATCH[v4 07/21]
---
arch/powerpc/platforms/powernv/pci-ioda.c | 44 ++++++++++++++++---------------
arch/powerpc/platforms/powernv/pci.h | 3 ++-
2 files changed, 25 insertions(+), 22 deletions(-)
@@ -132,25 +132,26 @@ static inline bool pnv_pci_is_mem_pref_64(unsigned long flags)(IORESOURCE_MEM_64|IORESOURCE_PREFETCH));}-staticvoidpnv_ioda_reserve_pe(structpnv_phb*phb,intpe_no)+staticstructpnv_ioda_pe*pnv_ioda_reserve_pe(structpnv_phb*phb,intpe_no){if(!(pe_no>=0&&pe_no<phb->ioda.total_pe)){pr_warn("%s: Invalid PE %d on PHB#%x\n",__func__,pe_no,phb->hose->global_number);-return;+returnNULL;}if(test_and_set_bit(pe_no,phb->ioda.pe_alloc)){pr_warn("%s: PE %d was assigned on PHB#%x\n",__func__,pe_no,phb->hose->global_number);-return;+returnNULL;}phb->ioda.pe_array[pe_no].phb=phb;phb->ioda.pe_array[pe_no].pe_number=pe_no;+return&phb->ioda.pe_array[pe_no];}-staticintpnv_ioda_alloc_pe(structpnv_phb*phb)+staticstructpnv_ioda_pe*pnv_ioda_alloc_pe(structpnv_phb*phb){unsignedlongpe_no;unsignedlonglimit=phb->ioda.total_pe-1;
@@ -163,12 +164,12 @@ static int pnv_ioda_alloc_pe(struct pnv_phb *phb)break;if(--limit>=phb->ioda.total_pe)-returnIODA_INVALID_PE;+returnNULL;}while(1);phb->ioda.pe_array[pe_no].phb=phb;phb->ioda.pe_array[pe_no].pe_number=pe_no;-returnpe_no;+return&phb->ioda.pe_array[pe_no];}staticvoidpnv_ioda_free_pe(structpnv_phb*phb,intpe)
@@ -401,13 +402,13 @@ static int pnv_ioda_pick_m64_pe(struct pnv_phb *phb,inti;if(!pnv_ioda_need_m64_pe(phb,bus))-returnIODA_INVALID_PE;+returnNULL;/* Allocate bitmap */size=_ALIGN_UP(phb->ioda.total_pe/8,sizeof(unsignedlong));pe_bitmap=kzalloc(size,GFP_KERNEL);if(!pe_bitmap)-returnIODA_INVALID_PE;+returnNULL;/* The bridge's M64 window might be extended to PHB's M64*windowbyintentiontosupportPCIhotplug.Sowehave
@@ -444,7 +445,7 @@ static int pnv_ioda_pick_m64_pe(struct pnv_phb *phb,/* No M64 window found ? */if(bitmap_empty(pe_bitmap,phb->ioda.total_pe)){kfree(pe_bitmap);-returnIODA_INVALID_PE;+returnNULL;}/* Figure out the master PE and put all slave PEs
@@ -495,7 +496,7 @@ static int pnv_ioda_pick_m64_pe(struct pnv_phb *phb,}kfree(pe_bitmap);-returnmaster_pe->pe_number;+returnmaster_pe;}staticvoid__initpnv_ioda_parse_m64_window(structpnv_phb*phb)
@@ -1224,7 +1225,7 @@ static struct pnv_ioda_pe *pnv_ioda_setup_bus_PE(struct pci_bus *bus, int all){structpci_controller*hose=pci_bus_to_host(bus);structpnv_phb*phb=hose->private_data;-structpnv_ioda_pe*pe;+structpnv_ioda_pe*pe=NULL;intpe_num=IODA_INVALID_PE;/* For partial hotplug case, the PE instance hasn't been destroyed
@@ -1240,24 +1241,25 @@ static struct pnv_ioda_pe *pnv_ioda_setup_bus_PE(struct pci_bus *bus, int all)}/* PE number for root bus should have been reserved */-if(pci_is_root_bus(bus))-pe_num=phb->ioda.root_pe;+if(pci_is_root_bus(bus)&&+phb->ioda.root_pe!=IODA_INVALID_PE)+pe=&phb->ioda.pe_array[phb->ioda.root_pe];/* Check if PE is determined by M64 */-if(pe_num==IODA_INVALID_PE&&phb->pick_m64_pe)-pe_num=phb->pick_m64_pe(phb,bus,all);+if(!pe&&phb->pick_m64_pe)+pe=phb->pick_m64_pe(phb,bus,all);/* The PE number isn't pinned by M64 */-if(pe_num==IODA_INVALID_PE)-pe_num=pnv_ioda_alloc_pe(phb);+if(!pe)+pe=pnv_ioda_alloc_pe(phb);-if(pe_num==IODA_INVALID_PE){+if(!pe){pr_warning("%s: Not enough PE# available for PCI bus %04x:%02x\n",__func__,pci_domain_nr(bus),bus->number);returnNULL;}-pe=&phb->ioda.pe_array[pe_num];+pe_num=pe->pe_number;pe->flags|=(all?PNV_IODA_PE_BUS_ALL:PNV_IODA_PE_BUS);pe->pbus=bus;pe->pdev=NULL;
PEs were put into the list, maintained by PHB, according its DMA32
weight. After that, the list was iterated to initialize PE's DMA
capability. For now, the PE is created and its DMA capability is
initialized right way. So we don't need the list and the patch
removes that.
Signed-off-by: Gavin Shan <redacted>
---
v5:
* Newly introduced
---
arch/powerpc/platforms/powernv/pci-ioda.c | 18 ------------------
arch/powerpc/platforms/powernv/pci.h | 6 ------
2 files changed, 24 deletions(-)
@@ -1296,9 +1282,6 @@ static struct pnv_ioda_pe *pnv_ioda_setup_bus_PE(struct pci_bus *bus, int all)/* Put PE to the list */list_add_tail(&pe->list,&phb->ioda.pe_list);-/* Link the PE */-pnv_ioda_link_pe_by_weight(phb,pe);-returnpe;}
pnv_ioda_get_pe() indicates it's increasing refcount to the given
PE instance from the name. However, it gets the instance of the
PE, which contains the indicated PCI device. The patch renames it
to pnv_ioda_dev_to_pe() to reflect its purpose.
Signed-off-by: Gavin Shan <redacted>
---
v5:
* Split from PATCH[v4 07/21]
* Fixed "do not use assignment in if condition" from checkpatch.pl
---
arch/powerpc/platforms/powernv/pci-ioda.c | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
@@ -702,7 +702,7 @@ static int pnv_ioda_get_pe_state(struct pnv_phb *phb, int pe_no)*butinthemeantime,weneedtoprotectthemtoavoidwarnings*/#ifdef CONFIG_PCI_MSI-staticstructpnv_ioda_pe*pnv_ioda_get_pe(structpci_dev*dev)+staticstructpnv_ioda_pe*pnv_ioda_dev_to_pe(structpci_dev*dev){structpci_controller*hose=pci_bus_to_host(dev->bus);structpnv_phb*phb=hose->private_data;
@@ -2671,7 +2671,7 @@ int pnv_phb_to_cxl_mode(struct pci_dev *dev, uint64_t mode)structpnv_ioda_pe*pe;intrc;-pe=pnv_ioda_get_pe(dev);+pe=pnv_ioda_dev_to_pe(dev);if(!pe)return-ENODEV;
@@ -2787,7 +2787,8 @@ int pnv_cxl_ioda_msi_setup(struct pci_dev *dev, unsigned int hwirq,structpnv_ioda_pe*pe;intrc;-if(!(pe=pnv_ioda_get_pe(dev)))+pe=pnv_ioda_dev_to_pe(dev);+if(!pe)return-ENODEV;/* Assign XIVE to PE */
Nobody is using the this function. The patch drops it.
Signed-off-by: Gavin Shan <redacted>
Reviewed-by: Alexey Kardashevskiy <redacted>
---
v5:
* Derived from PATCH[v4 08/21]
---
arch/powerpc/platforms/powernv/pci-ioda.c | 71 -------------------------------
1 file changed, 71 deletions(-)
@@ -1111,77 +1111,6 @@ static int pnv_pci_vf_resource_shift(struct pci_dev *dev, int offset)}#endif /* CONFIG_PCI_IOV */-#if 0-staticstructpnv_ioda_pe*pnv_ioda_setup_dev_PE(structpci_dev*dev)-{-structpci_controller*hose=pci_bus_to_host(dev->bus);-structpnv_phb*phb=hose->private_data;-structpci_dn*pdn=pci_get_pdn(dev);-structpnv_ioda_pe*pe;-intpe_num;--if(!pdn){-pr_err("%s: Device tree node not associated properly\n",-pci_name(dev));-returnNULL;-}-if(pdn->pe_number!=IODA_INVALID_PE)-returnNULL;--/* PE#0 has been pre-set */-if(dev->bus->number==0)-pe_num=0;-else-pe_num=pnv_ioda_alloc_pe(phb);-if(pe_num==IODA_INVALID_PE){-pr_warning("%s: Not enough PE# available, disabling device\n",-pci_name(dev));-returnNULL;-}--/* NOTE: We get only one ref to the pci_dev for the pdn, not for the-*pointerinthePEdatastructure,bothshouldbedestroyedatthe-*sametime.However,thisneedstobelookedatmorecloselyagain-*onceweactuallystartremovingthings(Hotplug,SR-IOV,...)-*-*AtsomepointwewanttoremovethePDNcompletelyanyways-*/-pe=&phb->ioda.pe_array[pe_num];-pci_dev_get(dev);-pdn->pcidev=dev;-pdn->pe_number=pe_num;-pe->pdev=dev;-pe->pbus=NULL;-pe->tce32_seg=-1;-pe->mve_number=-1;-pe->rid=dev->bus->number<<8|pdn->devfn;--pe_info(pe,"Associated device to PE\n");--if(pnv_ioda_configure_pe(phb,pe)){-/* XXX What do we do here ? */-if(pe_num)-pnv_ioda_free_pe(phb,pe_num);-pdn->pe_number=IODA_INVALID_PE;-pe->pdev=NULL;-pci_dev_put(dev);-returnNULL;-}--/* Assign a DMA weight to the device */-pe->dma_weight=pnv_ioda_dma_weight(dev);-if(pe->dma_weight!=0){-phb->ioda.dma_weight+=pe->dma_weight;-phb->ioda.dma_pe_count++;-}--/* Link the PE */-pnv_ioda_link_pe_by_weight(phb,pe);--returnpe;-}-#endif /* Useful for SRIOV case */-staticvoidpnv_ioda_setup_same_PE(structpci_bus*bus,structpnv_ioda_pe*pe){structpci_dev*dev;
The patch applies cleanup on pnv_pci_ioda2_release_dma_pe():
* Rename it to pnv_pci_ioda2_release_pe_dma() to match the
function names used to release resources for one PE in the
subsequent patches.
* Remove the parameter of PCI device, which is used to figure
out device node. VFs don't have associated device nodes in
SRIOV case. For other cases, the device node can be figured
out from the PCI bus or device the PE was allocated for.
Signed-off-by: Gavin Shan <redacted>
---
v5:
* Newly introduced
---
arch/powerpc/platforms/powernv/pci-ioda.c | 26 +++++++++++++++++++-------
1 file changed, 19 insertions(+), 7 deletions(-)
PowerNV platform is running on top of skiboot firmware, which has
changes supporting PCI slots. PCI slots are identified by PHB's
OPAL ID (PHB slot) or combo of that and PCI slot ID. The patch
changes argument names of opal_pci_reset() and opal_pci_poll()
to reflect the firmware's change. pnv_eeh_phb_poll() is also
renamed to pnv_eeh_poll() to reflect the firmware's change.
Signed-off-by: Gavin Shan <redacted>
---
v5:
* Split from PATCH[v4 09/21]
---
arch/powerpc/include/asm/opal.h | 4 ++--
arch/powerpc/platforms/powernv/eeh-powernv.c | 8 ++++----
2 files changed, 6 insertions(+), 6 deletions(-)
@@ -743,12 +743,12 @@ static int pnv_eeh_get_state(struct eeh_pe *pe, int *delay)returnret;}-statics64pnv_eeh_phb_poll(structpnv_phb*phb)+statics64pnv_eeh_poll(uint64_tid){s64rc=OPAL_HARDWARE;while(1){-rc=opal_pci_poll(phb->opal_id);+rc=opal_pci_poll(id,NULL);if(rc<=0)break;
@@ -788,7 +788,7 @@ int pnv_eeh_phb_reset(struct pci_controller *hose, int option)*resetfollowedbyhotresetonrootbus.Sowealso*needthePCIbussettlementdelay.*/-rc=pnv_eeh_phb_poll(phb);+rc=pnv_eeh_poll(phb->opal_id);if(option==EEH_RESET_DEACTIVATE){if(system_state<SYSTEM_RUNNING)udelay(1000*EEH_PE_RST_SETTLE_TIME);
@@ -831,7 +831,7 @@ static int pnv_eeh_root_reset(struct pci_controller *hose, int option)gotoout;/* Poll state of the PHB until the request is done */-rc=pnv_eeh_phb_poll(phb);+rc=pnv_eeh_poll(phb->opal_id);if(option==EEH_RESET_DEACTIVATE)msleep(EEH_PE_RST_SETTLE_TIME);out:
The skiboot firmware might provide the capability of resetting PCI
slot by property "ibm,reset-by-firmware" on the PCI slot associated
device node. The patch checks on the property and route the reset
to firmware if the property exists. Otherwise, we fail back to the
old path as before.
Signed-off-by: Gavin Shan <redacted>
---
v5:
* Derived from PATCH[v4 09/21]
---
arch/powerpc/platforms/powernv/eeh-powernv.c | 44 +++++++++++++++++++++++++++-
1 file changed, 43 insertions(+), 1 deletion(-)
@@ -892,6 +892,48 @@ static int pnv_eeh_bridge_reset(struct pci_dev *dev, int option)return0;}+staticintpnv_eeh_bridge_reset(structpci_dev*pdev,intoption)+{+structpci_controller*hose;+structpnv_phb*phb;+structdevice_node*dn=pdev?pci_device_to_OF_node(pdev):NULL;+uint64_tid=(0x1ul<<60);+uint8_tscope;+int64_trc;++/*+*Ifthefirmwarecan'thandleit,wewillissuehotreset+*onthesecondarybusdespitetherequestedresettype.+*/+if(!dn||!of_get_property(dn,"ibm,reset-by-firmware",NULL))+return__pnv_eeh_bridge_reset(pdev,option);++/* The firmware can handle the request */+switch(option){+caseEEH_RESET_HOT:+scope=OPAL_RESET_PCI_HOT;+break;+caseEEH_RESET_FUNDAMENTAL:+scope=OPAL_RESET_PCI_FUNDAMENTAL;+break;+caseEEH_RESET_DEACTIVATE:+return0;+default:+dev_warn(&pdev->dev,"%s: Unsupported reset %d\n",+__func__,option);+return-EINVAL;+}++hose=pci_bus_to_host(pdev->bus);+phb=hose->private_data;+id|=(pdev->bus->number<<24)|(pdev->devfn<<16)|phb->opal_id;+rc=opal_pci_reset(id,scope,OPAL_ASSERT_RESET);+if(rc>0)+rc=pnv_eeh_poll(id);++return(rc==OPAL_SUCCESS)?0:-EIO;+}+staticvoidpnv_eeh_wait_for_pending(structpci_dn*pdn,intpos,u16mask,boolaf_flr_rst){
There should have an upstream bridge for the PCI bus for which
pnv_pci_reset_secondary_bus() is called. It's impossible to call
the function for root buses. So we needn't do reset for root buses
in pnv_pci_reset_secondary_bus() and simply drop the logic.
Signed-off-by: Gavin Shan <redacted>
---
v5:
* Split from PATCH[v4 10/21]
---
arch/powerpc/platforms/powernv/eeh-powernv.c | 12 ++----------
1 file changed, 2 insertions(+), 10 deletions(-)
There might have PCI devices, under the specified PCI bus, asking
for fundamental reset. The patch iterates all PCI devices under
the specified PCI bus and issue fundamental reset to the PCI bus
if any PCI device is asking for that. Otherwise, hot reset is
issued to the PCI bus.
Signed-off-by: Gavin Shan <redacted>
---
v5:
* Derived from PATCH[v4 10/21]
---
arch/powerpc/platforms/powernv/eeh-powernv.c | 25 ++++++++++++++++++++++++-
1 file changed, 24 insertions(+), 1 deletion(-)
In hotplug case, function pcibios_add_pci_devices() is called to
rescan the specified PCI bus, which might not have any child devices.
Access to the PCI bus's child device node will cause kernel crash
without exception. The patch adds condition of skipping scanning
PCI bus without child devices, in order to avoid kernel crash.
Signed-off-by: Gavin Shan <redacted>
---
v5:
* Derived from PATCH[v4 11/21]
---
arch/powerpc/kernel/pci-hotplug.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
The patch moves pcibios_find_pci_bus() to PPC kerenl directory so
that it can be reused by hotplug code for pSeries and PowerNV
platform at the same time.
Signed-off-by: Gavin Shan <redacted>
Acked-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
---
v5:
* Derived from PATCH[v4 12/21]
---
arch/powerpc/kernel/pci-hotplug.c | 36 ++++++++++++++++++++++++++++++
arch/powerpc/platforms/pseries/pci_dlpar.c | 32 --------------------------
2 files changed, 36 insertions(+), 32 deletions(-)
The patch obsoletes pnv_eeh_poll() with pnv_pci_poll():
* The return value from last OPAL API is passed to the
pnv_pci_poll() and handled there.
* More information (e.g. PCI slot power status) is retrieved
if the last argument is valid.
Signed-off-by: Gavin Shan <redacted>
---
v5:
* Derived from PATCH[v4 13/21]
---
arch/powerpc/platforms/powernv/eeh-powernv.c | 46 ++++++----------------------
arch/powerpc/platforms/powernv/pci.c | 21 +++++++++++++
arch/powerpc/platforms/powernv/pci.h | 1 +
3 files changed, 31 insertions(+), 37 deletions(-)
@@ -743,28 +743,11 @@ static int pnv_eeh_get_state(struct eeh_pe *pe, int *delay)returnret;}-statics64pnv_eeh_poll(uint64_tid)-{-s64rc=OPAL_HARDWARE;--while(1){-rc=opal_pci_poll(id,NULL);-if(rc<=0)-break;--if(system_state<SYSTEM_RUNNING)-udelay(1000*rc);-else-msleep(rc);-}--returnrc;-}-intpnv_eeh_phb_reset(structpci_controller*hose,intoption){structpnv_phb*phb=hose->private_data;s64rc=OPAL_HARDWARE;+intret;pr_debug("%s: Reset PHB#%x, option=%d\n",__func__,hose->global_number,option);
@@ -779,8 +762,6 @@ int pnv_eeh_phb_reset(struct pci_controller *hose, int option)rc=opal_pci_reset(phb->opal_id,OPAL_RESET_PHB_COMPLETE,OPAL_DEASSERT_RESET);-if(rc<0)-gotoout;/**PollstateofthePHBuntiltherequestisdone
@@ -788,24 +769,22 @@ int pnv_eeh_phb_reset(struct pci_controller *hose, int option)*resetfollowedbyhotresetonrootbus.Sowealso*needthePCIbussettlementdelay.*/-rc=pnv_eeh_poll(phb->opal_id);-if(option==EEH_RESET_DEACTIVATE){+ret=pnv_pci_poll(phb->opal_id,rc,NULL);+if(option==EEH_RESET_DEACTIVATE&&!ret){if(system_state<SYSTEM_RUNNING)udelay(1000*EEH_PE_RST_SETTLE_TIME);elsemsleep(EEH_PE_RST_SETTLE_TIME);}-out:-if(rc!=OPAL_SUCCESS)-return-EIO;-return0;+returnret;}staticintpnv_eeh_root_reset(structpci_controller*hose,intoption){structpnv_phb*phb=hose->private_data;s64rc=OPAL_HARDWARE;+intret;pr_debug("%s: Reset PHB#%x, option=%d\n",__func__,hose->global_number,option);
@@ -827,18 +806,13 @@ static int pnv_eeh_root_reset(struct pci_controller *hose, int option)rc=opal_pci_reset(phb->opal_id,OPAL_RESET_PCI_HOT,OPAL_DEASSERT_RESET);-if(rc<0)-gotoout;/* Poll state of the PHB until the request is done */-rc=pnv_eeh_poll(phb->opal_id);-if(option==EEH_RESET_DEACTIVATE)+ret=pnv_pci_poll(phb->opal_id,rc,NULL);+if(option==EEH_RESET_DEACTIVATE&&!ret)msleep(EEH_PE_RST_SETTLE_TIME);-out:-if(rc!=OPAL_SUCCESS)-return-EIO;-return0;+returnret;}staticint__pnv_eeh_bridge_reset(structpci_dev*dev,intoption)
@@ -928,10 +902,8 @@ static int pnv_eeh_bridge_reset(struct pci_dev *pdev, int option)phb=hose->private_data;id|=(pdev->bus->number<<24)|(pdev->devfn<<16)|phb->opal_id;rc=opal_pci_reset(id,scope,OPAL_ASSERT_RESET);-if(rc>0)-rc=pnv_eeh_poll(id);-return(rc==OPAL_SUCCESS)?0:-EIO;+returnpnv_pci_poll(id,rc,NULL);}staticvoidpnv_eeh_wait_for_pending(structpci_dn*pdn,intpos,
The patch exports 4 functions, which base on corresponding OPAL
APIs to get or set PCI slot status. Those functions are going to
be used by PCI hotplug module in subsequent patches:
pnv_pci_get_overlay_dt() opal_get_overlay_dt()
pnv_pci_get_presence_status() opal_pci_get_presence_status()
pnv_pci_get_power_status() opal_pci_get_power_status()
pnv_pci_set_power_status() opal_pci_set_power_status()
Besides, the patch also exports pnv_pci_hotplug_notifier_{register,
unregister}() to allow registration and unregistration of PCI hotplug
notifier, which will be used to receive PCI hotplug message from skiboot
firmware.
Signed-off-by: Gavin Shan <redacted>
---
v5:
* Derived from PATCH[v4 14/21]
* No polling required for pnv_pci_get_presence_status()
* Separate functions for registration and unregistration of PCI
hotplug notifier
* int64_t for value returned from OPAL API
---
arch/powerpc/include/asm/opal-api.h | 8 +++-
arch/powerpc/include/asm/opal.h | 4 ++
arch/powerpc/include/asm/pnv-pci.h | 7 +++
arch/powerpc/platforms/powernv/opal-wrappers.S | 4 ++
arch/powerpc/platforms/powernv/pci.c | 66 ++++++++++++++++++++++++++
5 files changed, 88 insertions(+), 1 deletion(-)
The eeh_dev is always created based on pci_dn, but with initcall
core_initcall_sync(). The patch creates eeh_dev when pci_dn is
created, indicating they have same life cycle.
Signed-off-by: Gavin Shan <redacted>
---
v5:
* Derived from PATCH[v4 16/21]
---
arch/powerpc/include/asm/eeh.h | 6 ++++--
arch/powerpc/kernel/eeh_dev.c | 18 ++++--------------
arch/powerpc/kernel/pci_dn.c | 12 ++++++++++++
arch/powerpc/platforms/pseries/setup.c | 6 +-----
4 files changed, 21 insertions(+), 21 deletions(-)
@@ -80,16 +80,8 @@ void *eeh_dev_init(struct pci_dn *pdn, void *data)*/voideeh_dev_phb_init_dynamic(structpci_controller*phb){-structpci_dn*root=phb->pci_data;-/* EEH PE for PHB */eeh_phb_pe_create(phb);--/* EEH device for PHB */-eeh_dev_init(root,phb);--/* EEH devices for children OF nodes */-traverse_pci_dn(root,eeh_dev_init,phb);}/**
@@ -105,8 +97,6 @@ static int __init eeh_dev_phb_init(void)list_for_each_entry_safe(phb,tmp,&hose_list,list_node)eeh_dev_phb_init_dynamic(phb);-pr_info("EEH: devices created\n");-return0;}
@@ -261,12 +261,8 @@ static int pci_dn_reconfig_notifier(struct notifier_block *nb, unsigned long actswitch(action){caseOF_RECONFIG_ATTACH_NODE:pci=np->parent->data;-if(pci){+if(pci)update_dn_pci_info(np,pci->phb);--/* Create EEH device for the OF node */-eeh_dev_init(PCI_DN(np),pci->phb);-}break;default:err=NOTIFY_DONE;
During the PCI plugging event, the PCI devices are rescanned and
their IO and MMIO resources are reassigned. However, the PowerNV
platform will assign PE# based on that, which depends on updating
to window of bridge of the PE's primary bus.
The patch updates the windows of bridge of PE's primary bus if
we have valid bridge. Otherwise, we assume it's root bus or SRIOV
virtual bus and PE won't be assigned during PCI plugging time.
Signed-off-by: Gavin Shan <redacted>
---
v5:
* Derived from PATCH[v4 18/21]
---
arch/powerpc/kernel/pci-common.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
The device tree nodes will be changed dynamically on PCI hotplug
events on PowerNV platform with the help of overlay mechanism.
The patch enables CONFIG_OF_OVERLAY on PowerNV platform to support
that.
Signed-off-by: Gavin Shan <redacted>
---
v5:
* Derived from PATCH[v4 20/21]
* Enables OF_OVERLAY instead of OF_DYNAMIC
---
arch/powerpc/platforms/powernv/Kconfig | 1 +
1 file changed, 1 insertion(+)
The patch introduces one more argument to of_fdt_unflatten_tree()
to specify the root node for the FDT blob, which is going to be
unflattened. In the result, the function can be used to unflatten
FDT blob, which represents device sub-tree in subsequent patches.
Signed-off-by: Gavin Shan <redacted>
---
v5:
* Newly introduced
---
drivers/of/fdt.c | 26 ++++++++++++++++++--------
drivers/of/unittest.c | 2 +-
include/linux/of_fdt.h | 3 ++-
3 files changed, 21 insertions(+), 10 deletions(-)
@@ -425,7 +434,7 @@ static void __unflatten_device_tree(void *blob,/* First pass, scan for size */start=0;size=(unsignedlong)unflatten_dt_node(blob,NULL,&start,-NULL,NULL,true);+dad,NULL,true);size=ALIGN(size,4);pr_debug(" size is %lx, allocating...\n",size);
@@ -440,7 +449,7 @@ static void __unflatten_device_tree(void *blob,/* Second pass, do actual unflattening */start=0;-unflatten_dt_node(blob,mem,&start,NULL,mynodes,false);+unflatten_dt_node(blob,mem,&start,dad,mynodes,false);if(be32_to_cpup(mem+size)!=0xdeadbeef)pr_warning("End of tree marker overwritten: %08x\n",be32_to_cpup(mem+size));
@@ -1095,7 +1105,7 @@ bool __init early_init_dt_scan(void *params)*/void__initunflatten_device_tree(void){-__unflatten_device_tree(initial_boot_params,&of_root,+__unflatten_device_tree(initial_boot_params,NULL,&of_root,early_init_dt_alloc_memory_arch);/* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */
@@ -907,7 +907,7 @@ static int __init unittest_data_add(void)"not running tests\n",__func__);return-ENOMEM;}-of_fdt_unflatten_tree(unittest_data,&unittest_data_node);+of_fdt_unflatten_tree(unittest_data,NULL,&unittest_data_node);if(!unittest_data_node){pr_warn("%s: No tree to attach; not running tests\n",__func__);return-ENODATA;
The patch changes of_fdt_unflatten_tree() so that it returns the
allocated memory chunk for unflattened device-tree, which can be
released once it's obsoleted.
Signed-off-by: Gavin Shan <redacted>
---
v5:
* Newly introduced
---
drivers/of/fdt.c | 21 +++++++++++----------
include/linux/of_fdt.h | 6 +++---
2 files changed, 14 insertions(+), 13 deletions(-)
The patch intends to add standalone driver to support PCI hotplug
for PowerPC PowerNV platform, which runs on top of skiboot firmware.
The firmware identified hotpluggable slots and marked their device
tree node with proper "ibm,slot-pluggable" and "ibm,reset-by-firmware".
The driver simply scans device-tree to create/register PCI hotplug slot
accordingly.
If the skiboot firmware doesn't support slot status retrieval, the PCI
slot device node shouldn't have property "ibm,reset-by-firmware". In
that case, none of valid PCI slots will be detected from device tree.
The skiboot firmware doesn't export the capability to access attention
LEDs yet and it's something for TBD.
Signed-off-by: Gavin Shan <redacted>
---
v5:
* Use OF OVERLAY to update the device-tree
* Removed unnecessary header files
* More meaningful return value from powernv_php_register_one()
* Use pnv_pci_hotplug_notifier_{register, unregister}()
* Decimal values for slot's states
* Removed struct powernv_php_slot::release()
* Merged two bool arguments to one for powernv_php_slot_enable()
* Rename release_device_nodes_info() to remove_device_nodes_info()
* Don't check on "!len" in slot_power_on_handler()
* Handle return value in get_adapter_status() as suggested by aik
* Drop invalid attention status in set_attention_status()
* Renaming functions
* Fixed coding style and added entry in MAINTAINERS reported by
checkpatch.pl
---
MAINTAINERS | 6 +
drivers/pci/hotplug/Kconfig | 12 +
drivers/pci/hotplug/Makefile | 4 +
drivers/pci/hotplug/powernv_php.c | 140 +++++++
drivers/pci/hotplug/powernv_php.h | 90 ++++
drivers/pci/hotplug/powernv_php_slot.c | 732 +++++++++++++++++++++++++++++++++
6 files changed, 984 insertions(+)
create mode 100644 drivers/pci/hotplug/powernv_php.c
create mode 100644 drivers/pci/hotplug/powernv_php.h
create mode 100644 drivers/pci/hotplug/powernv_php_slot.c
@@ -0,0 +1,732 @@+/*+*PCIHotplugDriverforPowerPCPowerNVplatform.+*+*CopyrightGavinShan,IBMCorporation2015.+*+*Thisprogramisfreesoftware;youcanredistributeitand/ormodify+*itunderthetermsoftheGNUGeneralPublicLicenseaspublishedby+*theFreeSoftwareFoundation;eitherversion2oftheLicense,or+*(atyouroption)anylaterversion.+*/++#include<linux/module.h>++#include<asm/opal.h>+#include<asm/pnv-pci.h>+#include<asm/ppc-pci.h>++#include"powernv_php.h"++staticLIST_HEAD(php_slot_list);+staticDEFINE_SPINLOCK(php_slot_lock);++/*+*Removefirmwaredataforallchilddevicenodesofthe+*indicatedone.+*/+staticvoidremove_child_pdn(structdevice_node*np)+{+structdevice_node*child;++for_each_child_of_node(np,child){+/* In depth first */+remove_child_pdn(child);++remove_pci_device_node_info(child);+}+}++/*+*Removeallsubordinatedevicenodesoftheindicatedone.+*Thosedevicenodesindeepestpathshouldbereleasedfirstly.+*/+staticintremove_child_device_nodes(structdevice_node*parent)+{+structdevice_node*np,*child;+intret=0;++/* If the device node has children, remove them firstly */+for_each_child_of_node(parent,np){+ret=remove_child_device_nodes(np);+if(ret)+returnret;++/* The device shouldn't have alive children */+child=of_get_next_child(np,NULL);+if(child){+of_node_put(child);+of_node_put(np);+pr_err("%s: Alive children of node <%s>\n",+__func__,of_node_full_name(np));+return-EBUSY;+}++/* Detach the device node */+of_detach_node(np);+of_node_put(np);+}++return0;+}++/*+*Thefunctionprocessesthemessagesentbyfirmware+*toremovealldevicetreenodesbeneaththeslot's+*nodes,andtheassociatedauxillarydata.+*/+staticvoidslot_power_off_handler(structpowernv_php_slot*slot)+{+intret;++/* Release the firmware data for the child device nodes */+remove_child_pdn(slot->dn);++/*+*Releasethechilddevicenodes.Ifthesub-treewas+*builtwiththehelpofoverlay,wejustneedrevert+*thechangesintroducedbytheoverlay+*/+if(slot->overlay_id>=0){+ret=of_overlay_destroy(slot->overlay_id);+if(ret)+pr_warn("%s: Error %d destroying overlay %d\n",+__func__,ret,slot->overlay_id);+slot->overlay_id=-1;+}else{+ret=remove_child_device_nodes(slot->dn);+if(ret)+pr_warn("%s: Error %d releasing children of <%s>\n",+__func__,ret,of_node_full_name(slot->dn));+}++/* Confirm status change */+slot->status_confirmed=1;+wake_up_interruptible(&slot->queue);+}++staticvoidslot_power_on_handler(structpowernv_php_slot*slot)+{+structdevice_node*nodes[3]={NULL,NULL,NULL};+structproperty*prop=NULL;+void*fdt=NULL,*dt=NULL;+phandlehandle;+uint64_tlen;+inti,ret;++/* Build overlay sub-tree */+for(i=0;i<ARRAY_SIZE(nodes);i++){+nodes[i]=kzalloc(sizeof(structdevice_node),GFP_KERNEL);+if(!nodes[i])+gotoout;++of_node_init(nodes[i]);+if(i>0){+nodes[i-1]->child=nodes[i];+nodes[i]->parent=nodes[i-1];+}+}++/* Target property for parent node */+prop=kzalloc(sizeof(structproperty),GFP_KERNEL);+if(!prop)+gotoout;+prop->name=kstrdup("target",GFP_KERNEL);+if(!prop->name)+gotoout;+prop->value=kzalloc(sizeof(phandle),GFP_KERNEL);+if(!prop->value)+gotoout;+handle=cpu_to_be32(slot->dn->phandle);+memcpy(prop->value,&handle,sizeof(phandle));+prop->length=sizeof(phandle);+nodes[1]->properties=prop;++/* Names for overlay node */+nodes[2]->name=kstrdup("__overlay__",GFP_KERNEL);+if(!nodes[2]->name)+gotoout;+nodes[2]->full_name=kstrdup(of_node_full_name(slot->dn),GFP_KERNEL);+if(!nodes[2]->full_name)+gotoout;++/* Get FDT blob */+slot->dt_counter+=1;+fdt=NULL;+len=0x2000;+while(len<=0x10000){+fdt=kzalloc(len,GFP_KERNEL);+if(!fdt)+break;++ret=pnv_pci_get_overlay_dt(&slot->dt_counter,fdt,len);+if(!ret)+break;++kfree(fdt);+fdt=NULL;+len*=2;+}++if(!fdt)+gotoout;++/* Unflatten device tree blob */+dt=of_fdt_unflatten_tree(fdt,nodes[2],NULL);++/* Apply the overlay tree */+slot->overlay_id=of_overlay_create(nodes[0]);+if(slot->overlay_id<0)+gotoout;++/* Add device node firmware data */+traverse_pci_device_nodes(slot->dn,+add_pci_device_node_info,+pci_bus_to_host(slot->bus));++out:+kfree(dt);+kfree(fdt);+if(nodes[2]){+kfree(nodes[2]->name);+kfree(nodes[2]->full_name);+}+if(prop){+kfree(prop->value);+kfree(prop->name);+}++kfree(prop);+for(i=0;i<ARRAY_SIZE(nodes);i++)+kfree(nodes[i]);++/* Confirm status change */+slot->status_confirmed=1;+wake_up_interruptible(&slot->queue);+}++staticvoidpowernv_php_slot_work(structwork_struct*data)+{+structpowernv_php_slot*slot=container_of(data,+structpowernv_php_slot,+work);+uint64_tphp_event=be64_to_cpu(slot->msg->params[0]);++switch(php_event){+case0:/* Slot power off */+slot_power_off_handler(slot);+break;+case1:/* Slot power on */+slot_power_on_handler(slot);+break;+default:+pr_warn("%s: Unsupported hotplug event %lld\n",+__func__,php_event);+}++of_node_put(slot->dn);+}++intpowernv_php_msg_handler(structnotifier_block*nb,+unsignedlongtype,void*message)+{+phandleh;+structdevice_node*np;+structpowernv_php_slot*slot;+structopal_msg*msg=message;++/* Check the message type */+if(type!=OPAL_MSG_PCI_HOTPLUG){+pr_warn("%s: Wrong message type %ld received!\n",+__func__,type);+returnNOTIFY_DONE;+}++/* Find the device node */+h=(phandle)be64_to_cpu(msg->params[1]);+np=of_find_node_by_phandle(h);+if(!np){+pr_warn("%s: No device node for phandle 0x%08x\n",+__func__,h);+returnNOTIFY_DONE;+}++/* Find the slot */+slot=powernv_php_slot_find(np);+if(!slot){+pr_warn("%s: No slot found for node <%s>\n",+__func__,of_node_full_name(np));+of_node_put(np);+returnNOTIFY_DONE;+}++/* Schedule the work */+slot->msg=msg;+schedule_work(&slot->work);+returnNOTIFY_OK;+}++staticintset_power_status(structhotplug_slot*php_slot,u8val)+{+structpowernv_php_slot*slot=php_slot->private;+intret;++/* Retrieve the counter of device tree */+ret=pnv_pci_get_overlay_dt(&slot->dt_counter,NULL,0);+if(ret){+pr_warn("%s: Error %d getting DT counter for slot %016llx\n",+__func__,ret,slot->id);+returnret;+}++/* Set power status */+slot->status_confirmed=0;+ret=pnv_pci_set_power_status(slot->id,val);+if(ret){+pr_warn("%s: Error %d powering %s slot %016llx\n",+__func__,ret,val?"on":"off",slot->id);+returnret;+}++/* Waiting until the device tree is updated */+ret=wait_event_timeout(slot->queue,+!slot->status_confirmed,+10*HZ);+if(ret){+pr_warn("%s: Error %d completing power-%s slot %016llx\n",+__func__,ret,val?"on":"off",slot->id);+returnret;+}++return0;+}++staticintget_power_status(structhotplug_slot*php_slot,u8*val)+{+structpowernv_php_slot*slot=php_slot->private;+uint8_tstate;+intret;++/*+*Retrievepowerstatusfromfirmware.Ifwefail+*gettingthat,thepowerstatusfailsbackto+*beon.+*/+ret=pnv_pci_get_power_status(slot->id,&state);+if(ret){+*val=POWERNV_PHP_SLOT_POWER_ON;+pr_warn("%s: Error %d getting power status of slot %016llx\n",+__func__,ret,slot->id);+}else{+*val=state?POWERNV_PHP_SLOT_POWER_ON:+POWERNV_PHP_SLOT_POWER_OFF;+php_slot->info->power_status=*val;+}++return0;+}++staticintget_adapter_status(structhotplug_slot*php_slot,u8*val)+{+structpowernv_php_slot*slot=php_slot->private;+uint8_tstate;+intret;++/*+*Retrievepresencestatusfromfirmware.Ifwecan't+*getthat,itwillfailbacktobeempty.+*/+ret=pnv_pci_get_presence_status(slot->id,&state);+if(ret>=0){+ret=0;+*val=state?POWERNV_PHP_SLOT_PRESENT:+POWERNV_PHP_SLOT_EMPTY;+php_slot->info->adapter_status=*val;+ret=0;+}else{+*val=POWERNV_PHP_SLOT_EMPTY;+pr_warn("%s: Error %d getting presence of slot %016llx\n",+__func__,ret,slot->id);+}++returnret;+}++staticintset_attention_status(structhotplug_slot*php_slot,u8val)+{+/* The default operation would to turn on the attention */+switch(val){+casePOWERNV_PHP_SLOT_ATTEN_OFF:+casePOWERNV_PHP_SLOT_ATTEN_ON:+casePOWERNV_PHP_SLOT_ATTEN_IND:+casePOWERNV_PHP_SLOT_ATTEN_ACT:+break;+default:+pr_warn("%s: Invalid attention status 0x%02x\n",+__func__,val);+return-EINVAL;+}++/* FIXME: Make it real once firmware supports it */+php_slot->info->attention_status=val;++return0;+}++intpowernv_php_slot_enable(structhotplug_slot*php_slot,boolrescan)+{+structpowernv_php_slot*slot=php_slot->private;+uint8_tpresence,power_status;+intret;++/* Check if the slot has been configured */+if(slot->state!=POWERNV_PHP_SLOT_STATE_REGISTER)+return0;++/* Retrieve slot presence status */+ret=php_slot->ops->get_adapter_status(php_slot,&presence);+if(ret){+pr_warn("%s: Error %d getting presence of slot %016llx\n",+__func__,ret,slot->id);+returnret;+}++/* Proceed if there have nothing behind the slot */+if(presence==POWERNV_PHP_SLOT_EMPTY)+gotoscan;++/*+*Ifwedon'tdetectsomethingbehindtheslot,weneed+*makesurethepowersuplytotheslotison.Otherwise,+*theslotdownstreamPCIelinkturnshouldbedown.+*+*Onthefirsttime,wedon'tchangethepowerstatusto+*boostsystembootwithassumptionthatthefirmware+*suppliesconsistentslotpowerstatus:emptyslotalways+*hasitspoweroffandnon-emptyslothasitspoweron.+*/+if(!slot->check_power_status){+slot->check_power_status=1;+gotoscan;+}++/* Check the power status. Scan the slot if that's already on */+ret=php_slot->ops->get_power_status(php_slot,&power_status);+if(ret){+pr_warn("%s: Error %d getting power status of slot %016llx\n",+__func__,ret,slot->id);+returnret;+}+if(power_status==POWERNV_PHP_SLOT_POWER_ON)+gotoscan;++/* Power is off, turn it on and then scan the slot */+ret=set_power_status(php_slot,POWERNV_PHP_SLOT_POWER_ON);+if(ret){+pr_warn("%s: Error %d powering on slot %016llx\n",+__func__,ret,slot->id);+returnret;+}++scan:+switch(presence){+casePOWERNV_PHP_SLOT_PRESENT:+if(rescan){+pci_lock_rescan_remove();+pcibios_add_pci_devices(slot->bus);+pci_unlock_rescan_remove();+}++/* Rescan for child hotpluggable slots */+slot->state=POWERNV_PHP_SLOT_STATE_POPULATED;+if(rescan)+powernv_php_register(slot->dn);+break;+casePOWERNV_PHP_SLOT_EMPTY:+slot->state=POWERNV_PHP_SLOT_STATE_POPULATED;+break;+default:+pr_warn("%s: Invalid presence status %d of slot %016llx\n",+__func__,presence,slot->id);+return-EINVAL;+}++return0;+}++staticintenable_slot(structhotplug_slot*php_slot)+{+returnpowernv_php_slot_enable(php_slot,true);+}++staticintdisable_slot(structhotplug_slot*php_slot)+{+structpowernv_php_slot*slot=php_slot->private;+uint8_tpower_status;+intret;++if(slot->state!=POWERNV_PHP_SLOT_STATE_POPULATED)+return0;++/* Remove all devices behind the slot */+pci_lock_rescan_remove();+pcibios_remove_pci_devices(slot->bus);+pci_unlock_rescan_remove();++/* Detach the child hotpluggable slots */+powernv_php_unregister(slot->dn);++/*+*Checkthepowerstatusandturnitoffifnecessary.Ifwe+*failtogetthepowerstatus,thepowerwillbeforcedto+*beoff.+*/+ret=php_slot->ops->get_power_status(php_slot,&power_status);+if(ret||power_status==POWERNV_PHP_SLOT_POWER_ON){+ret=set_power_status(php_slot,POWERNV_PHP_SLOT_POWER_OFF);+if(ret)+pr_warn("%s: Error %d powering off slot %016llx\n",+__func__,ret,slot->id);+}++/* Update slot state */+slot->state=POWERNV_PHP_SLOT_STATE_REGISTER;+return0;+}++staticstructhotplug_slot_opsphp_slot_ops={+.get_power_status=get_power_status,+.get_adapter_status=get_adapter_status,+.set_attention_status=set_attention_status,+.enable_slot=enable_slot,+.disable_slot=disable_slot,+};++staticstructpowernv_php_slot*php_slot_match(structdevice_node*dn,+structpowernv_php_slot*slot)+{+structpowernv_php_slot*target,*tmp;++if(slot->dn==dn)+returnslot;++list_for_each_entry(tmp,&slot->children,link){+target=php_slot_match(dn,tmp);+if(target)+returntarget;+}++returnNULL;+}++structpowernv_php_slot*powernv_php_slot_find(structdevice_node*dn)+{+structpowernv_php_slot*slot,*tmp;+unsignedlongflags;++spin_lock_irqsave(&php_slot_lock,flags);+list_for_each_entry(tmp,&php_slot_list,link){+slot=php_slot_match(dn,tmp);+if(slot){+spin_unlock_irqrestore(&php_slot_lock,flags);+returnslot;+}+}+spin_unlock_irqrestore(&php_slot_lock,flags);++returnNULL;+}++voidpowernv_php_slot_free(structkref*kref)+{+structpowernv_php_slot*slot=to_powernv_php_slot(kref);++WARN_ON(!list_empty(&slot->children));+kfree(slot->name);+kfree(slot);+}++staticvoidphp_slot_release(structhotplug_slot*hp_slot)+{+structpowernv_php_slot*slot=hp_slot->private;+unsignedlongflags;++/* Remove from global or child list */+spin_lock_irqsave(&php_slot_lock,flags);+list_del(&slot->link);+spin_unlock_irqrestore(&php_slot_lock,flags);++/* Detach from parent */+powernv_php_slot_put(slot);+powernv_php_slot_put(slot->parent);+}++staticboolphp_slot_get_id(structdevice_node*dn,+uint64_t*id)+{+structdevice_node*parent=dn;+const__be64*prop64;+const__be32*prop32;++/*+*ThehotpluggableslotalwayshasacompoundId,which+*consistsof16-bitsPHBId,16bitsbus/slot/function+*number,andcompoundindicator+*/+*id=(0x1ul<<63);++/* Bus/Slot/Function number */+prop32=of_get_property(dn,"reg",NULL);+if(!prop32)+returnfalse;+*id|=((of_read_number(prop32,1)&0x00ffff00)<<8);++/* PHB Id */+while((parent=of_get_parent(parent))){+if(!PCI_DN(parent)){+of_node_put(parent);+break;+}++if(!of_device_is_compatible(parent,"ibm,ioda2-phb")&&+!of_device_is_compatible(parent,"ibm,ioda-phb")){+of_node_put(parent);+continue;+}++prop64=of_get_property(parent,"ibm,opal-phbid",NULL);+if(!prop64){+of_node_put(parent);+returnfalse;+}++*id|=be64_to_cpup(prop64);+of_node_put(parent);+returntrue;+}++returnfalse;+}++structpowernv_php_slot*powernv_php_slot_alloc(structdevice_node*dn)+{+structpci_bus*bus;+structpowernv_php_slot*slot;+constchar*label;+uint64_tid;+intslot_no;+size_tsize;+void*pmem;++/* Slot name */+label=of_get_property(dn,"ibm,slot-label",NULL);+if(!label)+returnNULL;++/* Slot indentifier */+if(!php_slot_get_id(dn,&id))+returnNULL;++/* PCI bus */+bus=pcibios_find_pci_bus(dn);+if(!bus)+returnNULL;++/* Slot number */+if(dn->child&&PCI_DN(dn->child))+slot_no=PCI_SLOT(PCI_DN(dn->child)->devfn);+else+slot_no=-1;++/* Allocate slot */+size=sizeof(structpowernv_php_slot)++sizeof(structhotplug_slot)++sizeof(structhotplug_slot_info);+pmem=kzalloc(size,GFP_KERNEL);+if(!pmem){+pr_warn("%s: Cannot allocate slot for node %s\n",+__func__,dn->full_name);+returnNULL;+}++/* Assign memory blocks */+slot=pmem;+slot->php_slot=pmem+sizeof(structpowernv_php_slot);+slot->php_slot->info=pmem+sizeof(structpowernv_php_slot)++sizeof(structhotplug_slot);+slot->name=kstrdup(label,GFP_KERNEL);+if(!slot->name){+pr_warn("%s: Cannot populate name for node %s\n",+__func__,dn->full_name);+kfree(pmem);+returnNULL;+}++/* Initialize slot */+kref_init(&slot->kref);+slot->state=POWERNV_PHP_SLOT_STATE_INIT;+slot->dn=dn;+slot->bus=bus;+slot->id=id;+slot->slot_no=slot_no;+slot->overlay_id=-1;+INIT_WORK(&slot->work,powernv_php_slot_work);+init_waitqueue_head(&slot->queue);+slot->check_power_status=0;+slot->status_confirmed=0;+slot->php_slot->ops=&php_slot_ops;+slot->php_slot->release=php_slot_release;+slot->php_slot->private=slot;+INIT_LIST_HEAD(&slot->children);+INIT_LIST_HEAD(&slot->link);++returnslot;+}++intpowernv_php_slot_register(structpowernv_php_slot*slot)+{+structpowernv_php_slot*parent;+structdevice_node*dn=slot->dn;+unsignedlongflags;+intret;++/* Avoid register same slot for twice */+if(powernv_php_slot_find(slot->dn))+return-EEXIST;++/* Register slot */+ret=pci_hp_register(slot->php_slot,slot->bus,+slot->slot_no,slot->name);+if(ret){+pr_warn("%s: Cannot register slot %s (%d)\n",+__func__,slot->name,ret);+returnret;+}++/* Put into global or parent list */+while((dn=of_get_parent(dn))){+if(!PCI_DN(dn)){+of_node_put(dn);+break;+}++parent=powernv_php_slot_find(dn);+if(parent){+of_node_put(dn);+break;+}+}++spin_lock_irqsave(&php_slot_lock,flags);+if(parent){+powernv_php_slot_get(parent);+slot->parent=parent;+list_add_tail(&slot->link,&parent->children);+}else{+list_add_tail(&slot->link,&php_slot_list);+}+spin_unlock_irqrestore(&php_slot_lock,flags);++/* Update slot state */+slot->state=POWERNV_PHP_SLOT_STATE_REGISTER;+return0;+}
On Thu, Jun 04, 2015 at 04:41:30PM +1000, Gavin Shan wrote:
Currently, PowerPC PowerNV platform utilizes ppc_md.pcibios_fixup(),
which is called for once after PCI probing and resource assignment
are completed, to allocate platform required resources for PCI devices:
PE#, IO and MMIO mapping, DMA address translation (TCE) table etc.
Obviously, it's not hotplug friendly.
The patch adds weak function pcibios_setup_bridge(), which is called
by pci_setup_bridge(). PowerPC PowerNV platform will reuse the function
to assign above platform required resources to newly added PCI devices,
in order to support PCI hotplug in subsequent patches.
Signed-off-by: Gavin Shan <redacted>
"Move pcibios_find_pci_bus() from pSeries to generic powerpc code"?
On Thu, Jun 04, 2015 at 04:42:00PM +1000, Gavin Shan wrote:
The patch moves pcibios_find_pci_bus() to PPC kerenl directory so
s/kerenl/kernel/
quoted hunk
that it can be reused by hotplug code for pSeries and PowerNV
platform at the same time.
Signed-off-by: Gavin Shan <redacted>
Acked-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
---
v5:
* Derived from PATCH[v4 12/21]
---
arch/powerpc/kernel/pci-hotplug.c | 36 ++++++++++++++++++++++++++++++
arch/powerpc/platforms/pseries/pci_dlpar.c | 32 --------------------------
2 files changed, 36 insertions(+), 32 deletions(-)
On Fri, Jun 05, 2015 at 02:44:32PM -0500, Bjorn Helgaas wrote:
On Thu, Jun 04, 2015 at 04:41:30PM +1000, Gavin Shan wrote:
quoted
Currently, PowerPC PowerNV platform utilizes ppc_md.pcibios_fixup(),
which is called for once after PCI probing and resource assignment
are completed, to allocate platform required resources for PCI devices:
PE#, IO and MMIO mapping, DMA address translation (TCE) table etc.
Obviously, it's not hotplug friendly.
The patch adds weak function pcibios_setup_bridge(), which is called
by pci_setup_bridge(). PowerPC PowerNV platform will reuse the function
to assign above platform required resources to newly added PCI devices,
in order to support PCI hotplug in subsequent patches.
Signed-off-by: Gavin Shan <redacted>
On Fri, Jun 05, 2015 at 02:47:30PM -0500, Bjorn Helgaas wrote:
"Move pcibios_find_pci_bus() from pSeries to generic powerpc code"?
On Thu, Jun 04, 2015 at 04:42:00PM +1000, Gavin Shan wrote:
quoted
The patch moves pcibios_find_pci_bus() to PPC kerenl directory so
s/kerenl/kernel/
Thanks. I'll fix it in next revision.
Thanks,
Gavin
quoted
that it can be reused by hotplug code for pSeries and PowerNV
platform at the same time.
Signed-off-by: Gavin Shan <redacted>
Acked-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
---
v5:
* Derived from PATCH[v4 12/21]
---
arch/powerpc/kernel/pci-hotplug.c | 36 ++++++++++++++++++++++++++++++
arch/powerpc/platforms/pseries/pci_dlpar.c | 32 --------------------------
2 files changed, 36 insertions(+), 32 deletions(-)
The patch cleans up DMA32 in pci-ioda.c. It shouldn't introduce
behavioural changes:
* Rename various fields in "struct pnv_phb" and "struct pnv_ioda_pe"
as 32-bits DMA should be related to "DMA", not "TCE", and move
them around to reflect their relationship and their relative
importance.
* Removed struct pnv_ioda_pe::tce32_segcount.
Signed-off-by: Gavin Shan <redacted>
---
v5:
* Split from PATCH[v4 5/21]
---
arch/powerpc/platforms/powernv/pci-ioda.c | 48 +++++++++++++++----------------
arch/powerpc/platforms/powernv/pci.h | 13 +++------
2 files changed, 28 insertions(+), 33 deletions(-)
@@ -2061,7 +2061,7 @@ static void pnv_pci_ioda_setup_dma_pe(struct pnv_phb *phb,/* XXX FIXME: Allocate multi-level tables on PHB3 *//* We shouldn't already have a 32-bit DMA associated */-if(WARN_ON(pe->tce32_seg>=0))+if(WARN_ON(pe->dma32_seg>=0))return;tbl=pnv_pci_table_alloc(phb->hose->node);
@@ -2070,7 +2070,7 @@ static void pnv_pci_ioda_setup_dma_pe(struct pnv_phb *phb,pnv_pci_link_table_and_group(phb->hose->node,0,tbl,&pe->table_group);/* Grab a 32-bit TCE table */-pe->tce32_seg=base;+pe->dma32_seg=base;pe_info(pe," Setting up 32-bit TCE table at %08x..%08x\n",(base<<28),((base+segs)<<28)-1);
@@ -2131,8 +2131,8 @@ static void pnv_pci_ioda_setup_dma_pe(struct pnv_phb *phb,return;fail:/* XXX Failure: Try to fallback to 64-bit only ? */-if(pe->tce32_seg>=0)-pe->tce32_seg=-1;+if(pe->dma32_seg>=0)+pe->dma32_seg=-1;if(tce_mem)__free_pages(tce_mem,get_order(TCE32_TABLE_SIZE*segs));if(tbl){
@@ -2520,7 +2520,7 @@ static void pnv_pci_ioda2_setup_dma_pe(struct pnv_phb *phb,int64_trc;/* We shouldn't already have a 32-bit DMA associated */-if(WARN_ON(pe->tce32_seg>=0))+if(WARN_ON(pe->dma32_seg>=0))return;/* TVE #1 is selected by PCI address bit 59 */
@@ -2530,7 +2530,7 @@ static void pnv_pci_ioda2_setup_dma_pe(struct pnv_phb *phb,pe->pe_number);/* The PE will reserve all possible 32-bits space */-pe->tce32_seg=0;+pe->dma32_seg=0;pe_info(pe,"Setting up 32-bit TCE table at 0..%08x\n",phb->ioda.m32_pci_base);
@@ -3314,7 +3314,7 @@ static void __init pnv_pci_init_ioda_phb(struct device_node *np,mutex_init(&phb->ioda.pe_list_mutex);/* Calculate how many 32-bit TCE segments we have */-phb->ioda.tce32_count=phb->ioda.m32_pci_base>>28;+phb->ioda.dma32_segcount=phb->ioda.m32_pci_base>>28;#if 0 /* We should really do that ... */rc=opal_pci_set_phb_mem_window(opal->phb_id,
@@ -58,15 +58,10 @@ struct pnv_ioda_pe {unsignedlongm32_segmap[8];unsignedlongm64_segmap[8];-/* "Weight" assigned to the PE for the sake of DMA resource-*allocations-*/-unsignedintdma_weight;
This belongs to the previous patch, more precisely to the part of the
previous patch which changes stuff for PHB3 and which you want to move to a
separate patch (or to this one, up to you).
-
/* "Base" iommu table, ie, 4K TCEs, 32-bit DMA */
- int tce32_seg;
- int tce32_segcount;
struct iommu_table_group table_group;
+ int dma32_seg;
+ unsigned int dma32_weight;
Tiny comment - you not just renamed the fields but also moved them :)
quoted hunk
/* 64-bit TCE bypass region */
bool tce_bypass_enabled;
@@ -182,8 +177,8 @@ struct pnv_phb { */ unsigned char pe_rmap[0x10000];- /* 32-bit TCE tables allocation */- unsigned long tce32_count;+ /* Number of 32-bit DMA segments */+ unsigned long dma32_segcount; /* Sorted list of used PE's, sorted at * boot for resource allocation purposes
Each PHB maintains an array helping to translate RID (Request
ID) to PE# with the assumption that PE# takes 8 bits, indicating
that we can't have more than 256 PEs. However, pci_dn->pe_number
already had 4-bytes for the PE#.
The patch extends the PE# capacity so that each of them will be
4-bytes long. Then we can use IODA_INVALID_PE to check one entry
in phb->pe_rmap[] is valid or not.
Signed-off-by: Gavin Shan <redacted>
---
v5:
* Split from [PATCH v5 v4 06/21]
---
arch/powerpc/platforms/powernv/pci-ioda.c | 5 ++++-
arch/powerpc/platforms/powernv/pci.h | 5 ++---
2 files changed, 6 insertions(+), 4 deletions(-)
Above you assign IODA_INVALID_PE in a loop and here you just do 0xff for
the entire array. Have a loop here too and assign IODA_INVALID_PE to every
entry:
for (i = 0; i < ARRAY_SIZE(phb->ioda.pe_rmap); ++i)
phb->ioda.pe_rmap[i] = IODA_INVALID_PE;
quoted hunk
+
/* Parse 64-bit MMIO range */
pnv_ioda_parse_m64_window(phb);
@@ -175,11 +175,10 @@ struct pnv_phb {structlist_headpe_list;structmutexpe_list_mutex;-/* Reverse map of PEs, will have to extend if-*wearetosupportmorethan256PEs,indexed+/* Reverse map of PEs, indexed by*bus{bus,devfn}*/-unsignedcharpe_rmap[0x10000];+intpe_rmap[0x10000];
Most time most of the array will be empty and it is 256K per PHB... I
understand we have quite a lot of RAM but still.
/* Number of 32-bit DMA segments */
unsigned long dma32_segcount;
Each PHB maintains one instance of "struct pci_controller_ops",
which includes various callbacks called by PCI subsystem. In the
definition of this struct, some callbacks have explicit names for
its arguments, but the left don't have.
The patch removes all explicit names of the arguments to the
callbacks in "struct pci_controller_ops" to keep the code look
consistent.
imho it is a bad idea. Self-documeted code gets less self-documented - how
do I know what "unsigned long" parameters are for without grepping?
On Wed, Jun 10, 2015 at 02:17:26PM +1000, Alexey Kardashevskiy wrote:
On 06/04/2015 04:41 PM, Gavin Shan wrote:
quoted
The patch cleans up DMA32 in pci-ioda.c. It shouldn't introduce
behavioural changes:
* Rename various fields in "struct pnv_phb" and "struct pnv_ioda_pe"
as 32-bits DMA should be related to "DMA", not "TCE", and move
them around to reflect their relationship and their relative
importance.
* Removed struct pnv_ioda_pe::tce32_segcount.
Signed-off-by: Gavin Shan <redacted>
---
v5:
* Split from PATCH[v4 5/21]
---
arch/powerpc/platforms/powernv/pci-ioda.c | 48 +++++++++++++++----------------
arch/powerpc/platforms/powernv/pci.h | 13 +++------
2 files changed, 28 insertions(+), 33 deletions(-)
@@ -2061,7 +2061,7 @@ static void pnv_pci_ioda_setup_dma_pe(struct pnv_phb *phb,/* XXX FIXME: Allocate multi-level tables on PHB3 *//* We shouldn't already have a 32-bit DMA associated */-if(WARN_ON(pe->tce32_seg>=0))+if(WARN_ON(pe->dma32_seg>=0))return;tbl=pnv_pci_table_alloc(phb->hose->node);
@@ -2070,7 +2070,7 @@ static void pnv_pci_ioda_setup_dma_pe(struct pnv_phb *phb,pnv_pci_link_table_and_group(phb->hose->node,0,tbl,&pe->table_group);/* Grab a 32-bit TCE table */-pe->tce32_seg=base;+pe->dma32_seg=base;pe_info(pe," Setting up 32-bit TCE table at %08x..%08x\n",(base<<28),((base+segs)<<28)-1);
@@ -2131,8 +2131,8 @@ static void pnv_pci_ioda_setup_dma_pe(struct pnv_phb *phb,return;fail:/* XXX Failure: Try to fallback to 64-bit only ? */-if(pe->tce32_seg>=0)-pe->tce32_seg=-1;+if(pe->dma32_seg>=0)+pe->dma32_seg=-1;if(tce_mem)__free_pages(tce_mem,get_order(TCE32_TABLE_SIZE*segs));if(tbl){
@@ -2520,7 +2520,7 @@ static void pnv_pci_ioda2_setup_dma_pe(struct pnv_phb *phb,int64_trc;/* We shouldn't already have a 32-bit DMA associated */-if(WARN_ON(pe->tce32_seg>=0))+if(WARN_ON(pe->dma32_seg>=0))return;/* TVE #1 is selected by PCI address bit 59 */
@@ -2530,7 +2530,7 @@ static void pnv_pci_ioda2_setup_dma_pe(struct pnv_phb *phb,pe->pe_number);/* The PE will reserve all possible 32-bits space */-pe->tce32_seg=0;+pe->dma32_seg=0;pe_info(pe,"Setting up 32-bit TCE table at 0..%08x\n",phb->ioda.m32_pci_base);
@@ -3314,7 +3314,7 @@ static void __init pnv_pci_init_ioda_phb(struct device_node *np,mutex_init(&phb->ioda.pe_list_mutex);/* Calculate how many 32-bit TCE segments we have */-phb->ioda.tce32_count=phb->ioda.m32_pci_base>>28;+phb->ioda.dma32_segcount=phb->ioda.m32_pci_base>>28;#if 0 /* We should really do that ... */rc=opal_pci_set_phb_mem_window(opal->phb_id,
@@ -58,15 +58,10 @@ struct pnv_ioda_pe {unsignedlongm32_segmap[8];unsignedlongm64_segmap[8];-/* "Weight" assigned to the PE for the sake of DMA resource-*allocations-*/-unsignedintdma_weight;
This belongs to the previous patch, more precisely to the part of the
previous patch which changes stuff for PHB3 and which you want to move to a
separate patch (or to this one, up to you).
Ok. I'll try to change the code according to your comments.
quoted
-
/* "Base" iommu table, ie, 4K TCEs, 32-bit DMA */
- int tce32_seg;
- int tce32_segcount;
struct iommu_table_group table_group;
+ int dma32_seg;
+ unsigned int dma32_weight;
Tiny comment - you not just renamed the fields but also moved them :)
Yes, as I explained in the chagelog :-)
* Rename various fields in "struct pnv_phb" and "struct pnv_ioda_pe"
as 32-bits DMA should be related to "DMA", not "TCE", and move
them around to reflect their relationship and their relative
importance.
quoted
/* 64-bit TCE bypass region */
bool tce_bypass_enabled;
@@ -182,8 +177,8 @@ struct pnv_phb { */ unsigned char pe_rmap[0x10000];- /* 32-bit TCE tables allocation */- unsigned long tce32_count;+ /* Number of 32-bit DMA segments */+ unsigned long dma32_segcount; /* Sorted list of used PE's, sorted at * boot for resource allocation purposes
On Wed, Jun 10, 2015 at 02:41:13PM +1000, Alexey Kardashevskiy wrote:
On 06/04/2015 04:41 PM, Gavin Shan wrote:
quoted
Each PHB maintains an array helping to translate RID (Request
ID) to PE# with the assumption that PE# takes 8 bits, indicating
that we can't have more than 256 PEs. However, pci_dn->pe_number
already had 4-bytes for the PE#.
The patch extends the PE# capacity so that each of them will be
4-bytes long. Then we can use IODA_INVALID_PE to check one entry
in phb->pe_rmap[] is valid or not.
Signed-off-by: Gavin Shan <redacted>
---
v5:
* Split from [PATCH v5 v4 06/21]
---
arch/powerpc/platforms/powernv/pci-ioda.c | 5 ++++-
arch/powerpc/platforms/powernv/pci.h | 5 ++---
2 files changed, 6 insertions(+), 4 deletions(-)
Above you assign IODA_INVALID_PE in a loop and here you just do 0xff for the
entire array. Have a loop here too and assign IODA_INVALID_PE to every entry:
for (i = 0; i < ARRAY_SIZE(phb->ioda.pe_rmap); ++i)
phb->ioda.pe_rmap[i] = IODA_INVALID_PE;
Yeah, will change accordingly.
quoted
+
/* Parse 64-bit MMIO range */
pnv_ioda_parse_m64_window(phb);
@@ -175,11 +175,10 @@ struct pnv_phb {structlist_headpe_list;structmutexpe_list_mutex;-/* Reverse map of PEs, will have to extend if-*wearetosupportmorethan256PEs,indexed+/* Reverse map of PEs, indexed by*bus{bus,devfn}*/-unsignedcharpe_rmap[0x10000];+intpe_rmap[0x10000];
Most time most of the array will be empty and it is 256K per PHB... I
understand we have quite a lot of RAM but still.
Indeed, I'll think about how to save memory here, but not in this
patchset.
quoted
/* Number of 32-bit DMA segments */
unsigned long dma32_segcount;
On Wed, Jun 10, 2015 at 02:43:57PM +1000, Alexey Kardashevskiy wrote:
On 06/04/2015 04:41 PM, Gavin Shan wrote:
quoted
Each PHB maintains one instance of "struct pci_controller_ops",
which includes various callbacks called by PCI subsystem. In the
definition of this struct, some callbacks have explicit names for
its arguments, but the left don't have.
The patch removes all explicit names of the arguments to the
callbacks in "struct pci_controller_ops" to keep the code look
consistent.
imho it is a bad idea. Self-documeted code gets less self-documented - how do
I know what "unsigned long" parameters are for without grepping?
Ok. I'll change the function definations to always have explicit
argument names.
From: Grant Likely <hidden> Date: 2015-06-30 18:18:04
On Thu, 4 Jun 2015 16:42:11 +1000
, Gavin Shan [off-list ref]
wrote:
quoted hunk
The patch intends to add standalone driver to support PCI hotplug
for PowerPC PowerNV platform, which runs on top of skiboot firmware.
The firmware identified hotpluggable slots and marked their device
tree node with proper "ibm,slot-pluggable" and "ibm,reset-by-firmware".
The driver simply scans device-tree to create/register PCI hotplug slot
accordingly.
If the skiboot firmware doesn't support slot status retrieval, the PCI
slot device node shouldn't have property "ibm,reset-by-firmware". In
that case, none of valid PCI slots will be detected from device tree.
The skiboot firmware doesn't export the capability to access attention
LEDs yet and it's something for TBD.
Signed-off-by: Gavin Shan <redacted>
---
v5:
* Use OF OVERLAY to update the device-tree
* Removed unnecessary header files
* More meaningful return value from powernv_php_register_one()
* Use pnv_pci_hotplug_notifier_{register, unregister}()
* Decimal values for slot's states
* Removed struct powernv_php_slot::release()
* Merged two bool arguments to one for powernv_php_slot_enable()
* Rename release_device_nodes_info() to remove_device_nodes_info()
* Don't check on "!len" in slot_power_on_handler()
* Handle return value in get_adapter_status() as suggested by aik
* Drop invalid attention status in set_attention_status()
* Renaming functions
* Fixed coding style and added entry in MAINTAINERS reported by
checkpatch.pl
---
MAINTAINERS | 6 +
drivers/pci/hotplug/Kconfig | 12 +
drivers/pci/hotplug/Makefile | 4 +
drivers/pci/hotplug/powernv_php.c | 140 +++++++
drivers/pci/hotplug/powernv_php.h | 90 ++++
drivers/pci/hotplug/powernv_php_slot.c | 732 +++++++++++++++++++++++++++++++++
6 files changed, 984 insertions(+)
create mode 100644 drivers/pci/hotplug/powernv_php.c
create mode 100644 drivers/pci/hotplug/powernv_php.h
create mode 100644 drivers/pci/hotplug/powernv_php_slot.c
@@ -0,0 +1,732 @@+/*+*PCIHotplugDriverforPowerPCPowerNVplatform.+*+*CopyrightGavinShan,IBMCorporation2015.+*+*Thisprogramisfreesoftware;youcanredistributeitand/ormodify+*itunderthetermsoftheGNUGeneralPublicLicenseaspublishedby+*theFreeSoftwareFoundation;eitherversion2oftheLicense,or+*(atyouroption)anylaterversion.+*/++#include<linux/module.h>++#include<asm/opal.h>+#include<asm/pnv-pci.h>+#include<asm/ppc-pci.h>++#include"powernv_php.h"++staticLIST_HEAD(php_slot_list);+staticDEFINE_SPINLOCK(php_slot_lock);++/*+*Removefirmwaredataforallchilddevicenodesofthe+*indicatedone.+*/+staticvoidremove_child_pdn(structdevice_node*np)+{+structdevice_node*child;++for_each_child_of_node(np,child){+/* In depth first */+remove_child_pdn(child);++remove_pci_device_node_info(child);+}+}++/*+*Removeallsubordinatedevicenodesoftheindicatedone.+*Thosedevicenodesindeepestpathshouldbereleasedfirstly.+*/+staticintremove_child_device_nodes(structdevice_node*parent)+{+structdevice_node*np,*child;+intret=0;++/* If the device node has children, remove them firstly */+for_each_child_of_node(parent,np){+ret=remove_child_device_nodes(np);+if(ret)+returnret;++/* The device shouldn't have alive children */+child=of_get_next_child(np,NULL);+if(child){+of_node_put(child);+of_node_put(np);+pr_err("%s: Alive children of node <%s>\n",+__func__,of_node_full_name(np));+return-EBUSY;+}++/* Detach the device node */+of_detach_node(np);+of_node_put(np);+}++return0;+}++/*+*Thefunctionprocessesthemessagesentbyfirmware+*toremovealldevicetreenodesbeneaththeslot's+*nodes,andtheassociatedauxillarydata.+*/+staticvoidslot_power_off_handler(structpowernv_php_slot*slot)+{+intret;++/* Release the firmware data for the child device nodes */+remove_child_pdn(slot->dn);++/*+*Releasethechilddevicenodes.Ifthesub-treewas+*builtwiththehelpofoverlay,wejustneedrevert+*thechangesintroducedbytheoverlay+*/+if(slot->overlay_id>=0){+ret=of_overlay_destroy(slot->overlay_id);+if(ret)+pr_warn("%s: Error %d destroying overlay %d\n",+__func__,ret,slot->overlay_id);+slot->overlay_id=-1;+}else{+ret=remove_child_device_nodes(slot->dn);+if(ret)+pr_warn("%s: Error %d releasing children of <%s>\n",+__func__,ret,of_node_full_name(slot->dn));+}++/* Confirm status change */+slot->status_confirmed=1;+wake_up_interruptible(&slot->queue);+}++staticvoidslot_power_on_handler(structpowernv_php_slot*slot)+{+structdevice_node*nodes[3]={NULL,NULL,NULL};+structproperty*prop=NULL;+void*fdt=NULL,*dt=NULL;+phandlehandle;+uint64_tlen;+inti,ret;++/* Build overlay sub-tree */+for(i=0;i<ARRAY_SIZE(nodes);i++){+nodes[i]=kzalloc(sizeof(structdevice_node),GFP_KERNEL);+if(!nodes[i])+gotoout;++of_node_init(nodes[i]);+if(i>0){+nodes[i-1]->child=nodes[i];+nodes[i]->parent=nodes[i-1];+}+}++/* Target property for parent node */+prop=kzalloc(sizeof(structproperty),GFP_KERNEL);+if(!prop)+gotoout;+prop->name=kstrdup("target",GFP_KERNEL);+if(!prop->name)+gotoout;+prop->value=kzalloc(sizeof(phandle),GFP_KERNEL);+if(!prop->value)+gotoout;+handle=cpu_to_be32(slot->dn->phandle);+memcpy(prop->value,&handle,sizeof(phandle));+prop->length=sizeof(phandle);+nodes[1]->properties=prop;++/* Names for overlay node */+nodes[2]->name=kstrdup("__overlay__",GFP_KERNEL);+if(!nodes[2]->name)+gotoout;+nodes[2]->full_name=kstrdup(of_node_full_name(slot->dn),GFP_KERNEL);+if(!nodes[2]->full_name)+gotoout;
I think you can simplify this driver by using the of_changeset api
instead of of_overlay. of_overlay is a particular data format passed
into the kernel, but it uses of_changeset in the back end. In this case,
you would allocate an of_changeset structure and then do:
of_changeset_init()
of_changeset_attach_node()
/* you might need to create an
* of_changeset_attach_node_subtree() varient */
of_changeset_attach_node()
of_changeset_attach_node()
of_changeset_attach_node()
of_changeset_apply()
of_changeset_destroy() /* frees the structure */
Then you don't have to muck about with creating a DT in the structure
expected by the of_overlay code.
+
+ /* Get FDT blob */
+ slot->dt_counter += 1;
+ fdt = NULL;
+ len = 0x2000;
+ while (len <= 0x10000) {
+ fdt = kzalloc(len, GFP_KERNEL);
+ if (!fdt)
+ break;
+
+ ret = pnv_pci_get_overlay_dt(&slot->dt_counter, fdt, len);
+ if (!ret)
+ break;
+
+ kfree(fdt);
+ fdt = NULL;
+ len *= 2;
+ }
+
+ if (!fdt)
+ goto out;
+
+ /* Unflatten device tree blob */
+ dt = of_fdt_unflatten_tree(fdt, nodes[2], NULL);
+
+ /* Apply the overlay tree */
+ slot->overlay_id = of_overlay_create(nodes[0]);
+ if (slot->overlay_id < 0)
+ goto out;
+
+ /* Add device node firmware data */
+ traverse_pci_device_nodes(slot->dn,
+ add_pci_device_node_info,
+ pci_bus_to_host(slot->bus));
+
+out:
+ kfree(dt);
+ kfree(fdt);
+ if (nodes[2]) {
+ kfree(nodes[2]->name);
+ kfree(nodes[2]->full_name);
+ }
+ if (prop) {
+ kfree(prop->value);
+ kfree(prop->name);
+ }
+
+ kfree(prop);
+ for (i = 0; i < ARRAY_SIZE(nodes); i++)
+ kfree(nodes[i]);
+
+ /* Confirm status change */
+ slot->status_confirmed = 1;
+ wake_up_interruptible(&slot->queue);
+}
+
+static void powernv_php_slot_work(struct work_struct *data)
+{
+ struct powernv_php_slot *slot = container_of(data,
+ struct powernv_php_slot,
+ work);
+ uint64_t php_event = be64_to_cpu(slot->msg->params[0]);
+
+ switch (php_event) {
+ case 0: /* Slot power off */
+ slot_power_off_handler(slot);
+ break;
+ case 1: /* Slot power on */
+ slot_power_on_handler(slot);
+ break;
+ default:
+ pr_warn("%s: Unsupported hotplug event %lld\n",
+ __func__, php_event);
+ }
+
+ of_node_put(slot->dn);
+}
+
+int powernv_php_msg_handler(struct notifier_block *nb,
+ unsigned long type, void *message)
+{
+ phandle h;
+ struct device_node *np;
+ struct powernv_php_slot *slot;
+ struct opal_msg *msg = message;
+
+ /* Check the message type */
+ if (type != OPAL_MSG_PCI_HOTPLUG) {
+ pr_warn("%s: Wrong message type %ld received!\n",
+ __func__, type);
+ return NOTIFY_DONE;
+ }
+
+ /* Find the device node */
+ h = (phandle)be64_to_cpu(msg->params[1]);
+ np = of_find_node_by_phandle(h);
+ if (!np) {
+ pr_warn("%s: No device node for phandle 0x%08x\n",
+ __func__, h);
+ return NOTIFY_DONE;
+ }
+
+ /* Find the slot */
+ slot = powernv_php_slot_find(np);
+ if (!slot) {
+ pr_warn("%s: No slot found for node <%s>\n",
+ __func__, of_node_full_name(np));
+ of_node_put(np);
+ return NOTIFY_DONE;
+ }
+
+ /* Schedule the work */
+ slot->msg = msg;
+ schedule_work(&slot->work);
+ return NOTIFY_OK;
+}
+
+static int set_power_status(struct hotplug_slot *php_slot, u8 val)
+{
+ struct powernv_php_slot *slot = php_slot->private;
+ int ret;
+
+ /* Retrieve the counter of device tree */
+ ret = pnv_pci_get_overlay_dt(&slot->dt_counter, NULL, 0);
+ if (ret) {
+ pr_warn("%s: Error %d getting DT counter for slot %016llx\n",
+ __func__, ret, slot->id);
+ return ret;
+ }
+
+ /* Set power status */
+ slot->status_confirmed = 0;
+ ret = pnv_pci_set_power_status(slot->id, val);
+ if (ret) {
+ pr_warn("%s: Error %d powering %s slot %016llx\n",
+ __func__, ret, val ? "on" : "off", slot->id);
+ return ret;
+ }
+
+ /* Waiting until the device tree is updated */
+ ret = wait_event_timeout(slot->queue,
+ !slot->status_confirmed,
+ 10 * HZ);
+ if (ret) {
+ pr_warn("%s: Error %d completing power-%s slot %016llx\n",
+ __func__, ret, val ? "on" : "off", slot->id);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int get_power_status(struct hotplug_slot *php_slot, u8 *val)
+{
+ struct powernv_php_slot *slot = php_slot->private;
+ uint8_t state;
+ int ret;
+
+ /*
+ * Retrieve power status from firmware. If we fail
+ * getting that, the power status fails back to
+ * be on.
+ */
+ ret = pnv_pci_get_power_status(slot->id, &state);
+ if (ret) {
+ *val = POWERNV_PHP_SLOT_POWER_ON;
+ pr_warn("%s: Error %d getting power status of slot %016llx\n",
+ __func__, ret, slot->id);
+ } else {
+ *val = state ? POWERNV_PHP_SLOT_POWER_ON :
+ POWERNV_PHP_SLOT_POWER_OFF;
+ php_slot->info->power_status = *val;
+ }
+
+ return 0;
+}
+
+static int get_adapter_status(struct hotplug_slot *php_slot, u8 *val)
+{
+ struct powernv_php_slot *slot = php_slot->private;
+ uint8_t state;
+ int ret;
+
+ /*
+ * Retrieve presence status from firmware. If we can't
+ * get that, it will fail back to be empty.
+ */
+ ret = pnv_pci_get_presence_status(slot->id, &state);
+ if (ret >= 0) {
+ ret = 0;
+ *val = state ? POWERNV_PHP_SLOT_PRESENT :
+ POWERNV_PHP_SLOT_EMPTY;
+ php_slot->info->adapter_status = *val;
+ ret = 0;
+ } else {
+ *val = POWERNV_PHP_SLOT_EMPTY;
+ pr_warn("%s: Error %d getting presence of slot %016llx\n",
+ __func__, ret, slot->id);
+ }
+
+ return ret;
+}
+
+static int set_attention_status(struct hotplug_slot *php_slot, u8 val)
+{
+ /* The default operation would to turn on the attention */
+ switch (val) {
+ case POWERNV_PHP_SLOT_ATTEN_OFF:
+ case POWERNV_PHP_SLOT_ATTEN_ON:
+ case POWERNV_PHP_SLOT_ATTEN_IND:
+ case POWERNV_PHP_SLOT_ATTEN_ACT:
+ break;
+ default:
+ pr_warn("%s: Invalid attention status 0x%02x\n",
+ __func__, val);
+ return -EINVAL;
+ }
+
+ /* FIXME: Make it real once firmware supports it */
+ php_slot->info->attention_status = val;
+
+ return 0;
+}
+
+int powernv_php_slot_enable(struct hotplug_slot *php_slot, bool rescan)
+{
+ struct powernv_php_slot *slot = php_slot->private;
+ uint8_t presence, power_status;
+ int ret;
+
+ /* Check if the slot has been configured */
+ if (slot->state != POWERNV_PHP_SLOT_STATE_REGISTER)
+ return 0;
+
+ /* Retrieve slot presence status */
+ ret = php_slot->ops->get_adapter_status(php_slot, &presence);
+ if (ret) {
+ pr_warn("%s: Error %d getting presence of slot %016llx\n",
+ __func__, ret, slot->id);
+ return ret;
+ }
+
+ /* Proceed if there have nothing behind the slot */
+ if (presence == POWERNV_PHP_SLOT_EMPTY)
+ goto scan;
+
+ /*
+ * If we don't detect something behind the slot, we need
+ * make sure the power suply to the slot is on. Otherwise,
+ * the slot downstream PCIe linkturn should be down.
+ *
+ * On the first time, we don't change the power status to
+ * boost system boot with assumption that the firmware
+ * supplies consistent slot power status: empty slot always
+ * has its power off and non-empty slot has its power on.
+ */
+ if (!slot->check_power_status) {
+ slot->check_power_status = 1;
+ goto scan;
+ }
+
+ /* Check the power status. Scan the slot if that's already on */
+ ret = php_slot->ops->get_power_status(php_slot, &power_status);
+ if (ret) {
+ pr_warn("%s: Error %d getting power status of slot %016llx\n",
+ __func__, ret, slot->id);
+ return ret;
+ }
+ if (power_status == POWERNV_PHP_SLOT_POWER_ON)
+ goto scan;
+
+ /* Power is off, turn it on and then scan the slot */
+ ret = set_power_status(php_slot, POWERNV_PHP_SLOT_POWER_ON);
+ if (ret) {
+ pr_warn("%s: Error %d powering on slot %016llx\n",
+ __func__, ret, slot->id);
+ return ret;
+ }
+
+scan:
+ switch (presence) {
+ case POWERNV_PHP_SLOT_PRESENT:
+ if (rescan) {
+ pci_lock_rescan_remove();
+ pcibios_add_pci_devices(slot->bus);
+ pci_unlock_rescan_remove();
+ }
+
+ /* Rescan for child hotpluggable slots */
+ slot->state = POWERNV_PHP_SLOT_STATE_POPULATED;
+ if (rescan)
+ powernv_php_register(slot->dn);
+ break;
+ case POWERNV_PHP_SLOT_EMPTY:
+ slot->state = POWERNV_PHP_SLOT_STATE_POPULATED;
+ break;
+ default:
+ pr_warn("%s: Invalid presence status %d of slot %016llx\n",
+ __func__, presence, slot->id);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int enable_slot(struct hotplug_slot *php_slot)
+{
+ return powernv_php_slot_enable(php_slot, true);
+}
+
+static int disable_slot(struct hotplug_slot *php_slot)
+{
+ struct powernv_php_slot *slot = php_slot->private;
+ uint8_t power_status;
+ int ret;
+
+ if (slot->state != POWERNV_PHP_SLOT_STATE_POPULATED)
+ return 0;
+
+ /* Remove all devices behind the slot */
+ pci_lock_rescan_remove();
+ pcibios_remove_pci_devices(slot->bus);
+ pci_unlock_rescan_remove();
+
+ /* Detach the child hotpluggable slots */
+ powernv_php_unregister(slot->dn);
+
+ /*
+ * Check the power status and turn it off if necessary. If we
+ * fail to get the power status, the power will be forced to
+ * be off.
+ */
+ ret = php_slot->ops->get_power_status(php_slot, &power_status);
+ if (ret || power_status == POWERNV_PHP_SLOT_POWER_ON) {
+ ret = set_power_status(php_slot, POWERNV_PHP_SLOT_POWER_OFF);
+ if (ret)
+ pr_warn("%s: Error %d powering off slot %016llx\n",
+ __func__, ret, slot->id);
+ }
+
+ /* Update slot state */
+ slot->state = POWERNV_PHP_SLOT_STATE_REGISTER;
+ return 0;
+}
+
+static struct hotplug_slot_ops php_slot_ops = {
+ .get_power_status = get_power_status,
+ .get_adapter_status = get_adapter_status,
+ .set_attention_status = set_attention_status,
+ .enable_slot = enable_slot,
+ .disable_slot = disable_slot,
+};
+
+static struct powernv_php_slot *php_slot_match(struct device_node *dn,
+ struct powernv_php_slot *slot)
+{
+ struct powernv_php_slot *target, *tmp;
+
+ if (slot->dn == dn)
+ return slot;
+
+ list_for_each_entry(tmp, &slot->children, link) {
+ target = php_slot_match(dn, tmp);
+ if (target)
+ return target;
+ }
+
+ return NULL;
+}
+
+struct powernv_php_slot *powernv_php_slot_find(struct device_node *dn)
+{
+ struct powernv_php_slot *slot, *tmp;
+ unsigned long flags;
+
+ spin_lock_irqsave(&php_slot_lock, flags);
+ list_for_each_entry(tmp, &php_slot_list, link) {
+ slot = php_slot_match(dn, tmp);
+ if (slot) {
+ spin_unlock_irqrestore(&php_slot_lock, flags);
+ return slot;
+ }
+ }
+ spin_unlock_irqrestore(&php_slot_lock, flags);
+
+ return NULL;
+}
+
+void powernv_php_slot_free(struct kref *kref)
+{
+ struct powernv_php_slot *slot = to_powernv_php_slot(kref);
+
+ WARN_ON(!list_empty(&slot->children));
+ kfree(slot->name);
+ kfree(slot);
+}
+
+static void php_slot_release(struct hotplug_slot *hp_slot)
+{
+ struct powernv_php_slot *slot = hp_slot->private;
+ unsigned long flags;
+
+ /* Remove from global or child list */
+ spin_lock_irqsave(&php_slot_lock, flags);
+ list_del(&slot->link);
+ spin_unlock_irqrestore(&php_slot_lock, flags);
+
+ /* Detach from parent */
+ powernv_php_slot_put(slot);
+ powernv_php_slot_put(slot->parent);
+}
+
+static bool php_slot_get_id(struct device_node *dn,
+ uint64_t *id)
+{
+ struct device_node *parent = dn;
+ const __be64 *prop64;
+ const __be32 *prop32;
+
+ /*
+ * The hotpluggable slot always has a compound Id, which
+ * consists of 16-bits PHB Id, 16 bits bus/slot/function
+ * number, and compound indicator
+ */
+ *id = (0x1ul << 63);
+
+ /* Bus/Slot/Function number */
+ prop32 = of_get_property(dn, "reg", NULL);
+ if (!prop32)
+ return false;
+ *id |= ((of_read_number(prop32, 1) & 0x00ffff00) << 8);
+
+ /* PHB Id */
+ while ((parent = of_get_parent(parent))) {
+ if (!PCI_DN(parent)) {
+ of_node_put(parent);
+ break;
+ }
+
+ if (!of_device_is_compatible(parent, "ibm,ioda2-phb") &&
+ !of_device_is_compatible(parent, "ibm,ioda-phb")) {
+ of_node_put(parent);
+ continue;
+ }
+
+ prop64 = of_get_property(parent, "ibm,opal-phbid", NULL);
+ if (!prop64) {
+ of_node_put(parent);
+ return false;
+ }
+
+ *id |= be64_to_cpup(prop64);
+ of_node_put(parent);
+ return true;
+ }
+
+ return false;
+}
+
+struct powernv_php_slot *powernv_php_slot_alloc(struct device_node *dn)
+{
+ struct pci_bus *bus;
+ struct powernv_php_slot *slot;
+ const char *label;
+ uint64_t id;
+ int slot_no;
+ size_t size;
+ void *pmem;
+
+ /* Slot name */
+ label = of_get_property(dn, "ibm,slot-label", NULL);
+ if (!label)
+ return NULL;
+
+ /* Slot indentifier */
+ if (!php_slot_get_id(dn, &id))
+ return NULL;
+
+ /* PCI bus */
+ bus = pcibios_find_pci_bus(dn);
+ if (!bus)
+ return NULL;
+
+ /* Slot number */
+ if (dn->child && PCI_DN(dn->child))
+ slot_no = PCI_SLOT(PCI_DN(dn->child)->devfn);
+ else
+ slot_no = -1;
+
+ /* Allocate slot */
+ size = sizeof(struct powernv_php_slot) +
+ sizeof(struct hotplug_slot) +
+ sizeof(struct hotplug_slot_info);
+ pmem = kzalloc(size, GFP_KERNEL);
+ if (!pmem) {
+ pr_warn("%s: Cannot allocate slot for node %s\n",
+ __func__, dn->full_name);
+ return NULL;
+ }
+
+ /* Assign memory blocks */
+ slot = pmem;
+ slot->php_slot = pmem + sizeof(struct powernv_php_slot);
+ slot->php_slot->info = pmem + sizeof(struct powernv_php_slot) +
+ sizeof(struct hotplug_slot);
+ slot->name = kstrdup(label, GFP_KERNEL);
+ if (!slot->name) {
+ pr_warn("%s: Cannot populate name for node %s\n",
+ __func__, dn->full_name);
+ kfree(pmem);
+ return NULL;
+ }
+
+ /* Initialize slot */
+ kref_init(&slot->kref);
+ slot->state = POWERNV_PHP_SLOT_STATE_INIT;
+ slot->dn = dn;
+ slot->bus = bus;
+ slot->id = id;
+ slot->slot_no = slot_no;
+ slot->overlay_id = -1;
+ INIT_WORK(&slot->work, powernv_php_slot_work);
+ init_waitqueue_head(&slot->queue);
+ slot->check_power_status = 0;
+ slot->status_confirmed = 0;
+ slot->php_slot->ops = &php_slot_ops;
+ slot->php_slot->release = php_slot_release;
+ slot->php_slot->private = slot;
+ INIT_LIST_HEAD(&slot->children);
+ INIT_LIST_HEAD(&slot->link);
+
+ return slot;
+}
+
+int powernv_php_slot_register(struct powernv_php_slot *slot)
+{
+ struct powernv_php_slot *parent;
+ struct device_node *dn = slot->dn;
+ unsigned long flags;
+ int ret;
+
+ /* Avoid register same slot for twice */
+ if (powernv_php_slot_find(slot->dn))
+ return -EEXIST;
+
+ /* Register slot */
+ ret = pci_hp_register(slot->php_slot, slot->bus,
+ slot->slot_no, slot->name);
+ if (ret) {
+ pr_warn("%s: Cannot register slot %s (%d)\n",
+ __func__, slot->name, ret);
+ return ret;
+ }
+
+ /* Put into global or parent list */
+ while ((dn = of_get_parent(dn))) {
+ if (!PCI_DN(dn)) {
+ of_node_put(dn);
+ break;
+ }
+
+ parent = powernv_php_slot_find(dn);
+ if (parent) {
+ of_node_put(dn);
+ break;
+ }
+ }
+
+ spin_lock_irqsave(&php_slot_lock, flags);
+ if (parent) {
+ powernv_php_slot_get(parent);
+ slot->parent = parent;
+ list_add_tail(&slot->link, &parent->children);
+ } else {
+ list_add_tail(&slot->link, &php_slot_list);
+ }
+ spin_unlock_irqrestore(&php_slot_lock, flags);
+
+ /* Update slot state */
+ slot->state = POWERNV_PHP_SLOT_STATE_REGISTER;
+ return 0;
+}
--
2.1.0
On Tue, Jun 30, 2015 at 07:18:04PM +0100, Grant Likely wrote:
On Thu, 4 Jun 2015 16:42:11 +1000
, Gavin Shan [off-list ref]
wrote:
quoted
The patch intends to add standalone driver to support PCI hotplug
for PowerPC PowerNV platform, which runs on top of skiboot firmware.
The firmware identified hotpluggable slots and marked their device
tree node with proper "ibm,slot-pluggable" and "ibm,reset-by-firmware".
The driver simply scans device-tree to create/register PCI hotplug slot
accordingly.
If the skiboot firmware doesn't support slot status retrieval, the PCI
slot device node shouldn't have property "ibm,reset-by-firmware". In
that case, none of valid PCI slots will be detected from device tree.
The skiboot firmware doesn't export the capability to access attention
LEDs yet and it's something for TBD.
Signed-off-by: Gavin Shan <redacted>
---
v5:
* Use OF OVERLAY to update the device-tree
* Removed unnecessary header files
* More meaningful return value from powernv_php_register_one()
* Use pnv_pci_hotplug_notifier_{register, unregister}()
* Decimal values for slot's states
* Removed struct powernv_php_slot::release()
* Merged two bool arguments to one for powernv_php_slot_enable()
* Rename release_device_nodes_info() to remove_device_nodes_info()
* Don't check on "!len" in slot_power_on_handler()
* Handle return value in get_adapter_status() as suggested by aik
* Drop invalid attention status in set_attention_status()
* Renaming functions
* Fixed coding style and added entry in MAINTAINERS reported by
checkpatch.pl
---
MAINTAINERS | 6 +
drivers/pci/hotplug/Kconfig | 12 +
drivers/pci/hotplug/Makefile | 4 +
drivers/pci/hotplug/powernv_php.c | 140 +++++++
drivers/pci/hotplug/powernv_php.h | 90 ++++
drivers/pci/hotplug/powernv_php_slot.c | 732 +++++++++++++++++++++++++++++++++
6 files changed, 984 insertions(+)
create mode 100644 drivers/pci/hotplug/powernv_php.c
create mode 100644 drivers/pci/hotplug/powernv_php.h
create mode 100644 drivers/pci/hotplug/powernv_php_slot.c
@@ -0,0 +1,732 @@+/*+*PCIHotplugDriverforPowerPCPowerNVplatform.+*+*CopyrightGavinShan,IBMCorporation2015.+*+*Thisprogramisfreesoftware;youcanredistributeitand/ormodify+*itunderthetermsoftheGNUGeneralPublicLicenseaspublishedby+*theFreeSoftwareFoundation;eitherversion2oftheLicense,or+*(atyouroption)anylaterversion.+*/++#include<linux/module.h>++#include<asm/opal.h>+#include<asm/pnv-pci.h>+#include<asm/ppc-pci.h>++#include"powernv_php.h"++staticLIST_HEAD(php_slot_list);+staticDEFINE_SPINLOCK(php_slot_lock);++/*+*Removefirmwaredataforallchilddevicenodesofthe+*indicatedone.+*/+staticvoidremove_child_pdn(structdevice_node*np)+{+structdevice_node*child;++for_each_child_of_node(np,child){+/* In depth first */+remove_child_pdn(child);++remove_pci_device_node_info(child);+}+}++/*+*Removeallsubordinatedevicenodesoftheindicatedone.+*Thosedevicenodesindeepestpathshouldbereleasedfirstly.+*/+staticintremove_child_device_nodes(structdevice_node*parent)+{+structdevice_node*np,*child;+intret=0;++/* If the device node has children, remove them firstly */+for_each_child_of_node(parent,np){+ret=remove_child_device_nodes(np);+if(ret)+returnret;++/* The device shouldn't have alive children */+child=of_get_next_child(np,NULL);+if(child){+of_node_put(child);+of_node_put(np);+pr_err("%s: Alive children of node <%s>\n",+__func__,of_node_full_name(np));+return-EBUSY;+}++/* Detach the device node */+of_detach_node(np);+of_node_put(np);+}++return0;+}++/*+*Thefunctionprocessesthemessagesentbyfirmware+*toremovealldevicetreenodesbeneaththeslot's+*nodes,andtheassociatedauxillarydata.+*/+staticvoidslot_power_off_handler(structpowernv_php_slot*slot)+{+intret;++/* Release the firmware data for the child device nodes */+remove_child_pdn(slot->dn);++/*+*Releasethechilddevicenodes.Ifthesub-treewas+*builtwiththehelpofoverlay,wejustneedrevert+*thechangesintroducedbytheoverlay+*/+if(slot->overlay_id>=0){+ret=of_overlay_destroy(slot->overlay_id);+if(ret)+pr_warn("%s: Error %d destroying overlay %d\n",+__func__,ret,slot->overlay_id);+slot->overlay_id=-1;+}else{+ret=remove_child_device_nodes(slot->dn);+if(ret)+pr_warn("%s: Error %d releasing children of <%s>\n",+__func__,ret,of_node_full_name(slot->dn));+}++/* Confirm status change */+slot->status_confirmed=1;+wake_up_interruptible(&slot->queue);+}++staticvoidslot_power_on_handler(structpowernv_php_slot*slot)+{+structdevice_node*nodes[3]={NULL,NULL,NULL};+structproperty*prop=NULL;+void*fdt=NULL,*dt=NULL;+phandlehandle;+uint64_tlen;+inti,ret;++/* Build overlay sub-tree */+for(i=0;i<ARRAY_SIZE(nodes);i++){+nodes[i]=kzalloc(sizeof(structdevice_node),GFP_KERNEL);+if(!nodes[i])+gotoout;++of_node_init(nodes[i]);+if(i>0){+nodes[i-1]->child=nodes[i];+nodes[i]->parent=nodes[i-1];+}+}++/* Target property for parent node */+prop=kzalloc(sizeof(structproperty),GFP_KERNEL);+if(!prop)+gotoout;+prop->name=kstrdup("target",GFP_KERNEL);+if(!prop->name)+gotoout;+prop->value=kzalloc(sizeof(phandle),GFP_KERNEL);+if(!prop->value)+gotoout;+handle=cpu_to_be32(slot->dn->phandle);+memcpy(prop->value,&handle,sizeof(phandle));+prop->length=sizeof(phandle);+nodes[1]->properties=prop;++/* Names for overlay node */+nodes[2]->name=kstrdup("__overlay__",GFP_KERNEL);+if(!nodes[2]->name)+gotoout;+nodes[2]->full_name=kstrdup(of_node_full_name(slot->dn),GFP_KERNEL);+if(!nodes[2]->full_name)+gotoout;
I think you can simplify this driver by using the of_changeset api
instead of of_overlay. of_overlay is a particular data format passed
into the kernel, but it uses of_changeset in the back end. In this case,
you would allocate an of_changeset structure and then do:
of_changeset_init()
of_changeset_attach_node()
/* you might need to create an
* of_changeset_attach_node_subtree() varient */
of_changeset_attach_node()
of_changeset_attach_node()
of_changeset_attach_node()
of_changeset_apply()
of_changeset_destroy() /* frees the structure */
Then you don't have to muck about with creating a DT in the structure
expected by the of_overlay code.
Yeah, Thanks for the suggestion, Grant. I'm waiting for a usable
4.2.rc1 and integrate the comments I received, then post the new
revision. The changes to use changeset will be included in next
revision.
Thanks,
Gavin
quoted
+
+ /* Get FDT blob */
+ slot->dt_counter += 1;
+ fdt = NULL;
+ len = 0x2000;
+ while (len <= 0x10000) {
+ fdt = kzalloc(len, GFP_KERNEL);
+ if (!fdt)
+ break;
+
+ ret = pnv_pci_get_overlay_dt(&slot->dt_counter, fdt, len);
+ if (!ret)
+ break;
+
+ kfree(fdt);
+ fdt = NULL;
+ len *= 2;
+ }
+
+ if (!fdt)
+ goto out;
+
+ /* Unflatten device tree blob */
+ dt = of_fdt_unflatten_tree(fdt, nodes[2], NULL);
+
+ /* Apply the overlay tree */
+ slot->overlay_id = of_overlay_create(nodes[0]);
+ if (slot->overlay_id < 0)
+ goto out;
+
+ /* Add device node firmware data */
+ traverse_pci_device_nodes(slot->dn,
+ add_pci_device_node_info,
+ pci_bus_to_host(slot->bus));
+
+out:
+ kfree(dt);
+ kfree(fdt);
+ if (nodes[2]) {
+ kfree(nodes[2]->name);
+ kfree(nodes[2]->full_name);
+ }
+ if (prop) {
+ kfree(prop->value);
+ kfree(prop->name);
+ }
+
+ kfree(prop);
+ for (i = 0; i < ARRAY_SIZE(nodes); i++)
+ kfree(nodes[i]);
+
+ /* Confirm status change */
+ slot->status_confirmed = 1;
+ wake_up_interruptible(&slot->queue);
+}
+
+static void powernv_php_slot_work(struct work_struct *data)
+{
+ struct powernv_php_slot *slot = container_of(data,
+ struct powernv_php_slot,
+ work);
+ uint64_t php_event = be64_to_cpu(slot->msg->params[0]);
+
+ switch (php_event) {
+ case 0: /* Slot power off */
+ slot_power_off_handler(slot);
+ break;
+ case 1: /* Slot power on */
+ slot_power_on_handler(slot);
+ break;
+ default:
+ pr_warn("%s: Unsupported hotplug event %lld\n",
+ __func__, php_event);
+ }
+
+ of_node_put(slot->dn);
+}
+
+int powernv_php_msg_handler(struct notifier_block *nb,
+ unsigned long type, void *message)
+{
+ phandle h;
+ struct device_node *np;
+ struct powernv_php_slot *slot;
+ struct opal_msg *msg = message;
+
+ /* Check the message type */
+ if (type != OPAL_MSG_PCI_HOTPLUG) {
+ pr_warn("%s: Wrong message type %ld received!\n",
+ __func__, type);
+ return NOTIFY_DONE;
+ }
+
+ /* Find the device node */
+ h = (phandle)be64_to_cpu(msg->params[1]);
+ np = of_find_node_by_phandle(h);
+ if (!np) {
+ pr_warn("%s: No device node for phandle 0x%08x\n",
+ __func__, h);
+ return NOTIFY_DONE;
+ }
+
+ /* Find the slot */
+ slot = powernv_php_slot_find(np);
+ if (!slot) {
+ pr_warn("%s: No slot found for node <%s>\n",
+ __func__, of_node_full_name(np));
+ of_node_put(np);
+ return NOTIFY_DONE;
+ }
+
+ /* Schedule the work */
+ slot->msg = msg;
+ schedule_work(&slot->work);
+ return NOTIFY_OK;
+}
+
+static int set_power_status(struct hotplug_slot *php_slot, u8 val)
+{
+ struct powernv_php_slot *slot = php_slot->private;
+ int ret;
+
+ /* Retrieve the counter of device tree */
+ ret = pnv_pci_get_overlay_dt(&slot->dt_counter, NULL, 0);
+ if (ret) {
+ pr_warn("%s: Error %d getting DT counter for slot %016llx\n",
+ __func__, ret, slot->id);
+ return ret;
+ }
+
+ /* Set power status */
+ slot->status_confirmed = 0;
+ ret = pnv_pci_set_power_status(slot->id, val);
+ if (ret) {
+ pr_warn("%s: Error %d powering %s slot %016llx\n",
+ __func__, ret, val ? "on" : "off", slot->id);
+ return ret;
+ }
+
+ /* Waiting until the device tree is updated */
+ ret = wait_event_timeout(slot->queue,
+ !slot->status_confirmed,
+ 10 * HZ);
+ if (ret) {
+ pr_warn("%s: Error %d completing power-%s slot %016llx\n",
+ __func__, ret, val ? "on" : "off", slot->id);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int get_power_status(struct hotplug_slot *php_slot, u8 *val)
+{
+ struct powernv_php_slot *slot = php_slot->private;
+ uint8_t state;
+ int ret;
+
+ /*
+ * Retrieve power status from firmware. If we fail
+ * getting that, the power status fails back to
+ * be on.
+ */
+ ret = pnv_pci_get_power_status(slot->id, &state);
+ if (ret) {
+ *val = POWERNV_PHP_SLOT_POWER_ON;
+ pr_warn("%s: Error %d getting power status of slot %016llx\n",
+ __func__, ret, slot->id);
+ } else {
+ *val = state ? POWERNV_PHP_SLOT_POWER_ON :
+ POWERNV_PHP_SLOT_POWER_OFF;
+ php_slot->info->power_status = *val;
+ }
+
+ return 0;
+}
+
+static int get_adapter_status(struct hotplug_slot *php_slot, u8 *val)
+{
+ struct powernv_php_slot *slot = php_slot->private;
+ uint8_t state;
+ int ret;
+
+ /*
+ * Retrieve presence status from firmware. If we can't
+ * get that, it will fail back to be empty.
+ */
+ ret = pnv_pci_get_presence_status(slot->id, &state);
+ if (ret >= 0) {
+ ret = 0;
+ *val = state ? POWERNV_PHP_SLOT_PRESENT :
+ POWERNV_PHP_SLOT_EMPTY;
+ php_slot->info->adapter_status = *val;
+ ret = 0;
+ } else {
+ *val = POWERNV_PHP_SLOT_EMPTY;
+ pr_warn("%s: Error %d getting presence of slot %016llx\n",
+ __func__, ret, slot->id);
+ }
+
+ return ret;
+}
+
+static int set_attention_status(struct hotplug_slot *php_slot, u8 val)
+{
+ /* The default operation would to turn on the attention */
+ switch (val) {
+ case POWERNV_PHP_SLOT_ATTEN_OFF:
+ case POWERNV_PHP_SLOT_ATTEN_ON:
+ case POWERNV_PHP_SLOT_ATTEN_IND:
+ case POWERNV_PHP_SLOT_ATTEN_ACT:
+ break;
+ default:
+ pr_warn("%s: Invalid attention status 0x%02x\n",
+ __func__, val);
+ return -EINVAL;
+ }
+
+ /* FIXME: Make it real once firmware supports it */
+ php_slot->info->attention_status = val;
+
+ return 0;
+}
+
+int powernv_php_slot_enable(struct hotplug_slot *php_slot, bool rescan)
+{
+ struct powernv_php_slot *slot = php_slot->private;
+ uint8_t presence, power_status;
+ int ret;
+
+ /* Check if the slot has been configured */
+ if (slot->state != POWERNV_PHP_SLOT_STATE_REGISTER)
+ return 0;
+
+ /* Retrieve slot presence status */
+ ret = php_slot->ops->get_adapter_status(php_slot, &presence);
+ if (ret) {
+ pr_warn("%s: Error %d getting presence of slot %016llx\n",
+ __func__, ret, slot->id);
+ return ret;
+ }
+
+ /* Proceed if there have nothing behind the slot */
+ if (presence == POWERNV_PHP_SLOT_EMPTY)
+ goto scan;
+
+ /*
+ * If we don't detect something behind the slot, we need
+ * make sure the power suply to the slot is on. Otherwise,
+ * the slot downstream PCIe linkturn should be down.
+ *
+ * On the first time, we don't change the power status to
+ * boost system boot with assumption that the firmware
+ * supplies consistent slot power status: empty slot always
+ * has its power off and non-empty slot has its power on.
+ */
+ if (!slot->check_power_status) {
+ slot->check_power_status = 1;
+ goto scan;
+ }
+
+ /* Check the power status. Scan the slot if that's already on */
+ ret = php_slot->ops->get_power_status(php_slot, &power_status);
+ if (ret) {
+ pr_warn("%s: Error %d getting power status of slot %016llx\n",
+ __func__, ret, slot->id);
+ return ret;
+ }
+ if (power_status == POWERNV_PHP_SLOT_POWER_ON)
+ goto scan;
+
+ /* Power is off, turn it on and then scan the slot */
+ ret = set_power_status(php_slot, POWERNV_PHP_SLOT_POWER_ON);
+ if (ret) {
+ pr_warn("%s: Error %d powering on slot %016llx\n",
+ __func__, ret, slot->id);
+ return ret;
+ }
+
+scan:
+ switch (presence) {
+ case POWERNV_PHP_SLOT_PRESENT:
+ if (rescan) {
+ pci_lock_rescan_remove();
+ pcibios_add_pci_devices(slot->bus);
+ pci_unlock_rescan_remove();
+ }
+
+ /* Rescan for child hotpluggable slots */
+ slot->state = POWERNV_PHP_SLOT_STATE_POPULATED;
+ if (rescan)
+ powernv_php_register(slot->dn);
+ break;
+ case POWERNV_PHP_SLOT_EMPTY:
+ slot->state = POWERNV_PHP_SLOT_STATE_POPULATED;
+ break;
+ default:
+ pr_warn("%s: Invalid presence status %d of slot %016llx\n",
+ __func__, presence, slot->id);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int enable_slot(struct hotplug_slot *php_slot)
+{
+ return powernv_php_slot_enable(php_slot, true);
+}
+
+static int disable_slot(struct hotplug_slot *php_slot)
+{
+ struct powernv_php_slot *slot = php_slot->private;
+ uint8_t power_status;
+ int ret;
+
+ if (slot->state != POWERNV_PHP_SLOT_STATE_POPULATED)
+ return 0;
+
+ /* Remove all devices behind the slot */
+ pci_lock_rescan_remove();
+ pcibios_remove_pci_devices(slot->bus);
+ pci_unlock_rescan_remove();
+
+ /* Detach the child hotpluggable slots */
+ powernv_php_unregister(slot->dn);
+
+ /*
+ * Check the power status and turn it off if necessary. If we
+ * fail to get the power status, the power will be forced to
+ * be off.
+ */
+ ret = php_slot->ops->get_power_status(php_slot, &power_status);
+ if (ret || power_status == POWERNV_PHP_SLOT_POWER_ON) {
+ ret = set_power_status(php_slot, POWERNV_PHP_SLOT_POWER_OFF);
+ if (ret)
+ pr_warn("%s: Error %d powering off slot %016llx\n",
+ __func__, ret, slot->id);
+ }
+
+ /* Update slot state */
+ slot->state = POWERNV_PHP_SLOT_STATE_REGISTER;
+ return 0;
+}
+
+static struct hotplug_slot_ops php_slot_ops = {
+ .get_power_status = get_power_status,
+ .get_adapter_status = get_adapter_status,
+ .set_attention_status = set_attention_status,
+ .enable_slot = enable_slot,
+ .disable_slot = disable_slot,
+};
+
+static struct powernv_php_slot *php_slot_match(struct device_node *dn,
+ struct powernv_php_slot *slot)
+{
+ struct powernv_php_slot *target, *tmp;
+
+ if (slot->dn == dn)
+ return slot;
+
+ list_for_each_entry(tmp, &slot->children, link) {
+ target = php_slot_match(dn, tmp);
+ if (target)
+ return target;
+ }
+
+ return NULL;
+}
+
+struct powernv_php_slot *powernv_php_slot_find(struct device_node *dn)
+{
+ struct powernv_php_slot *slot, *tmp;
+ unsigned long flags;
+
+ spin_lock_irqsave(&php_slot_lock, flags);
+ list_for_each_entry(tmp, &php_slot_list, link) {
+ slot = php_slot_match(dn, tmp);
+ if (slot) {
+ spin_unlock_irqrestore(&php_slot_lock, flags);
+ return slot;
+ }
+ }
+ spin_unlock_irqrestore(&php_slot_lock, flags);
+
+ return NULL;
+}
+
+void powernv_php_slot_free(struct kref *kref)
+{
+ struct powernv_php_slot *slot = to_powernv_php_slot(kref);
+
+ WARN_ON(!list_empty(&slot->children));
+ kfree(slot->name);
+ kfree(slot);
+}
+
+static void php_slot_release(struct hotplug_slot *hp_slot)
+{
+ struct powernv_php_slot *slot = hp_slot->private;
+ unsigned long flags;
+
+ /* Remove from global or child list */
+ spin_lock_irqsave(&php_slot_lock, flags);
+ list_del(&slot->link);
+ spin_unlock_irqrestore(&php_slot_lock, flags);
+
+ /* Detach from parent */
+ powernv_php_slot_put(slot);
+ powernv_php_slot_put(slot->parent);
+}
+
+static bool php_slot_get_id(struct device_node *dn,
+ uint64_t *id)
+{
+ struct device_node *parent = dn;
+ const __be64 *prop64;
+ const __be32 *prop32;
+
+ /*
+ * The hotpluggable slot always has a compound Id, which
+ * consists of 16-bits PHB Id, 16 bits bus/slot/function
+ * number, and compound indicator
+ */
+ *id = (0x1ul << 63);
+
+ /* Bus/Slot/Function number */
+ prop32 = of_get_property(dn, "reg", NULL);
+ if (!prop32)
+ return false;
+ *id |= ((of_read_number(prop32, 1) & 0x00ffff00) << 8);
+
+ /* PHB Id */
+ while ((parent = of_get_parent(parent))) {
+ if (!PCI_DN(parent)) {
+ of_node_put(parent);
+ break;
+ }
+
+ if (!of_device_is_compatible(parent, "ibm,ioda2-phb") &&
+ !of_device_is_compatible(parent, "ibm,ioda-phb")) {
+ of_node_put(parent);
+ continue;
+ }
+
+ prop64 = of_get_property(parent, "ibm,opal-phbid", NULL);
+ if (!prop64) {
+ of_node_put(parent);
+ return false;
+ }
+
+ *id |= be64_to_cpup(prop64);
+ of_node_put(parent);
+ return true;
+ }
+
+ return false;
+}
+
+struct powernv_php_slot *powernv_php_slot_alloc(struct device_node *dn)
+{
+ struct pci_bus *bus;
+ struct powernv_php_slot *slot;
+ const char *label;
+ uint64_t id;
+ int slot_no;
+ size_t size;
+ void *pmem;
+
+ /* Slot name */
+ label = of_get_property(dn, "ibm,slot-label", NULL);
+ if (!label)
+ return NULL;
+
+ /* Slot indentifier */
+ if (!php_slot_get_id(dn, &id))
+ return NULL;
+
+ /* PCI bus */
+ bus = pcibios_find_pci_bus(dn);
+ if (!bus)
+ return NULL;
+
+ /* Slot number */
+ if (dn->child && PCI_DN(dn->child))
+ slot_no = PCI_SLOT(PCI_DN(dn->child)->devfn);
+ else
+ slot_no = -1;
+
+ /* Allocate slot */
+ size = sizeof(struct powernv_php_slot) +
+ sizeof(struct hotplug_slot) +
+ sizeof(struct hotplug_slot_info);
+ pmem = kzalloc(size, GFP_KERNEL);
+ if (!pmem) {
+ pr_warn("%s: Cannot allocate slot for node %s\n",
+ __func__, dn->full_name);
+ return NULL;
+ }
+
+ /* Assign memory blocks */
+ slot = pmem;
+ slot->php_slot = pmem + sizeof(struct powernv_php_slot);
+ slot->php_slot->info = pmem + sizeof(struct powernv_php_slot) +
+ sizeof(struct hotplug_slot);
+ slot->name = kstrdup(label, GFP_KERNEL);
+ if (!slot->name) {
+ pr_warn("%s: Cannot populate name for node %s\n",
+ __func__, dn->full_name);
+ kfree(pmem);
+ return NULL;
+ }
+
+ /* Initialize slot */
+ kref_init(&slot->kref);
+ slot->state = POWERNV_PHP_SLOT_STATE_INIT;
+ slot->dn = dn;
+ slot->bus = bus;
+ slot->id = id;
+ slot->slot_no = slot_no;
+ slot->overlay_id = -1;
+ INIT_WORK(&slot->work, powernv_php_slot_work);
+ init_waitqueue_head(&slot->queue);
+ slot->check_power_status = 0;
+ slot->status_confirmed = 0;
+ slot->php_slot->ops = &php_slot_ops;
+ slot->php_slot->release = php_slot_release;
+ slot->php_slot->private = slot;
+ INIT_LIST_HEAD(&slot->children);
+ INIT_LIST_HEAD(&slot->link);
+
+ return slot;
+}
+
+int powernv_php_slot_register(struct powernv_php_slot *slot)
+{
+ struct powernv_php_slot *parent;
+ struct device_node *dn = slot->dn;
+ unsigned long flags;
+ int ret;
+
+ /* Avoid register same slot for twice */
+ if (powernv_php_slot_find(slot->dn))
+ return -EEXIST;
+
+ /* Register slot */
+ ret = pci_hp_register(slot->php_slot, slot->bus,
+ slot->slot_no, slot->name);
+ if (ret) {
+ pr_warn("%s: Cannot register slot %s (%d)\n",
+ __func__, slot->name, ret);
+ return ret;
+ }
+
+ /* Put into global or parent list */
+ while ((dn = of_get_parent(dn))) {
+ if (!PCI_DN(dn)) {
+ of_node_put(dn);
+ break;
+ }
+
+ parent = powernv_php_slot_find(dn);
+ if (parent) {
+ of_node_put(dn);
+ break;
+ }
+ }
+
+ spin_lock_irqsave(&php_slot_lock, flags);
+ if (parent) {
+ powernv_php_slot_get(parent);
+ slot->parent = parent;
+ list_add_tail(&slot->link, &parent->children);
+ } else {
+ list_add_tail(&slot->link, &php_slot_list);
+ }
+ spin_unlock_irqrestore(&php_slot_lock, flags);
+
+ /* Update slot state */
+ slot->state = POWERNV_PHP_SLOT_STATE_REGISTER;
+ return 0;
+}
--
2.1.0