diff options
Diffstat (limited to 'arch/powerpc/platforms/powernv/pci.c')
| -rw-r--r-- | arch/powerpc/platforms/powernv/pci.c | 228 | 
1 files changed, 203 insertions, 25 deletions
diff --git a/arch/powerpc/platforms/powernv/pci.c b/arch/powerpc/platforms/powernv/pci.c index 85bb66d7f93..a70bc1e385e 100644 --- a/arch/powerpc/platforms/powernv/pci.c +++ b/arch/powerpc/platforms/powernv/pci.c @@ -144,6 +144,112 @@ static void pnv_teardown_msi_irqs(struct pci_dev *pdev)  }  #endif /* CONFIG_PCI_MSI */ +static void pnv_pci_dump_p7ioc_diag_data(struct pnv_phb *phb) +{ +	struct OpalIoP7IOCPhbErrorData *data = &phb->diag.p7ioc; +	int i; + +	pr_info("PHB %d diagnostic data:\n", phb->hose->global_number); + +	pr_info("  brdgCtl              = 0x%08x\n", data->brdgCtl); + +	pr_info("  portStatusReg        = 0x%08x\n", data->portStatusReg); +	pr_info("  rootCmplxStatus      = 0x%08x\n", data->rootCmplxStatus); +	pr_info("  busAgentStatus       = 0x%08x\n", data->busAgentStatus); + +	pr_info("  deviceStatus         = 0x%08x\n", data->deviceStatus); +	pr_info("  slotStatus           = 0x%08x\n", data->slotStatus); +	pr_info("  linkStatus           = 0x%08x\n", data->linkStatus); +	pr_info("  devCmdStatus         = 0x%08x\n", data->devCmdStatus); +	pr_info("  devSecStatus         = 0x%08x\n", data->devSecStatus); + +	pr_info("  rootErrorStatus      = 0x%08x\n", data->rootErrorStatus); +	pr_info("  uncorrErrorStatus    = 0x%08x\n", data->uncorrErrorStatus); +	pr_info("  corrErrorStatus      = 0x%08x\n", data->corrErrorStatus); +	pr_info("  tlpHdr1              = 0x%08x\n", data->tlpHdr1); +	pr_info("  tlpHdr2              = 0x%08x\n", data->tlpHdr2); +	pr_info("  tlpHdr3              = 0x%08x\n", data->tlpHdr3); +	pr_info("  tlpHdr4              = 0x%08x\n", data->tlpHdr4); +	pr_info("  sourceId             = 0x%08x\n", data->sourceId); + +	pr_info("  errorClass           = 0x%016llx\n", data->errorClass); +	pr_info("  correlator           = 0x%016llx\n", data->correlator); + +	pr_info("  p7iocPlssr           = 0x%016llx\n", data->p7iocPlssr); +	pr_info("  p7iocCsr             = 0x%016llx\n", data->p7iocCsr); +	pr_info("  lemFir               = 0x%016llx\n", data->lemFir); +	pr_info("  lemErrorMask         = 0x%016llx\n", data->lemErrorMask); +	pr_info("  lemWOF               = 0x%016llx\n", data->lemWOF); +	pr_info("  phbErrorStatus       = 0x%016llx\n", data->phbErrorStatus); +	pr_info("  phbFirstErrorStatus  = 0x%016llx\n", data->phbFirstErrorStatus); +	pr_info("  phbErrorLog0         = 0x%016llx\n", data->phbErrorLog0); +	pr_info("  phbErrorLog1         = 0x%016llx\n", data->phbErrorLog1); +	pr_info("  mmioErrorStatus      = 0x%016llx\n", data->mmioErrorStatus); +	pr_info("  mmioFirstErrorStatus = 0x%016llx\n", data->mmioFirstErrorStatus); +	pr_info("  mmioErrorLog0        = 0x%016llx\n", data->mmioErrorLog0); +	pr_info("  mmioErrorLog1        = 0x%016llx\n", data->mmioErrorLog1); +	pr_info("  dma0ErrorStatus      = 0x%016llx\n", data->dma0ErrorStatus); +	pr_info("  dma0FirstErrorStatus = 0x%016llx\n", data->dma0FirstErrorStatus); +	pr_info("  dma0ErrorLog0        = 0x%016llx\n", data->dma0ErrorLog0); +	pr_info("  dma0ErrorLog1        = 0x%016llx\n", data->dma0ErrorLog1); +	pr_info("  dma1ErrorStatus      = 0x%016llx\n", data->dma1ErrorStatus); +	pr_info("  dma1FirstErrorStatus = 0x%016llx\n", data->dma1FirstErrorStatus); +	pr_info("  dma1ErrorLog0        = 0x%016llx\n", data->dma1ErrorLog0); +	pr_info("  dma1ErrorLog1        = 0x%016llx\n", data->dma1ErrorLog1); + +	for (i = 0; i < OPAL_P7IOC_NUM_PEST_REGS; i++) { +		if ((data->pestA[i] >> 63) == 0 && +		    (data->pestB[i] >> 63) == 0) +			continue; +		pr_info("  PE[%3d] PESTA        = 0x%016llx\n", i, data->pestA[i]); +		pr_info("          PESTB        = 0x%016llx\n", data->pestB[i]); +	} +} + +static void pnv_pci_dump_phb_diag_data(struct pnv_phb *phb) +{ +	switch(phb->model) { +	case PNV_PHB_MODEL_P7IOC: +		pnv_pci_dump_p7ioc_diag_data(phb); +		break; +	default: +		pr_warning("PCI %d: Can't decode this PHB diag data\n", +			   phb->hose->global_number); +	} +} + +static void pnv_pci_handle_eeh_config(struct pnv_phb *phb, u32 pe_no) +{ +	unsigned long flags, rc; +	int has_diag; + +	spin_lock_irqsave(&phb->lock, flags); + +	rc = opal_pci_get_phb_diag_data(phb->opal_id, phb->diag.blob, PNV_PCI_DIAG_BUF_SIZE); +	has_diag = (rc == OPAL_SUCCESS); + +	rc = opal_pci_eeh_freeze_clear(phb->opal_id, pe_no, +				       OPAL_EEH_ACTION_CLEAR_FREEZE_ALL); +	if (rc) { +		pr_warning("PCI %d: Failed to clear EEH freeze state" +			   " for PE#%d, err %ld\n", +			   phb->hose->global_number, pe_no, rc); + +		/* For now, let's only display the diag buffer when we fail to clear +		 * the EEH status. We'll do more sensible things later when we have +		 * proper EEH support. We need to make sure we don't pollute ourselves +		 * with the normal errors generated when probing empty slots +		 */ +		if (has_diag) +			pnv_pci_dump_phb_diag_data(phb); +		else +			pr_warning("PCI %d: No diag data available\n", +				   phb->hose->global_number); +	} + +	spin_unlock_irqrestore(&phb->lock, flags); +} +  static void pnv_pci_config_check_eeh(struct pnv_phb *phb, struct pci_bus *bus,  				     u32 bdfn)  { @@ -165,15 +271,8 @@ static void pnv_pci_config_check_eeh(struct pnv_phb *phb, struct pci_bus *bus,  	}  	cfg_dbg(" -> EEH check, bdfn=%04x PE%d fstate=%x\n",  		bdfn, pe_no, fstate); -	if (fstate != 0) { -		rc = opal_pci_eeh_freeze_clear(phb->opal_id, pe_no, -					      OPAL_EEH_ACTION_CLEAR_FREEZE_ALL); -		if (rc) { -			pr_warning("PCI %d: Failed to clear EEH freeze state" -				   " for PE#%d, err %lld\n", -				   phb->hose->global_number, pe_no, rc); -		} -	} +	if (fstate != 0) +		pnv_pci_handle_eeh_config(phb, pe_no);  }  static int pnv_pci_read_config(struct pci_bus *bus, @@ -257,12 +356,54 @@ struct pci_ops pnv_pci_ops = {  	.write = pnv_pci_write_config,  }; + +static void pnv_tce_invalidate(struct iommu_table *tbl, +			       u64 *startp, u64 *endp) +{ +	u64 __iomem *invalidate = (u64 __iomem *)tbl->it_index; +	unsigned long start, end, inc; + +	start = __pa(startp); +	end = __pa(endp); + + +	/* BML uses this case for p6/p7/galaxy2: Shift addr and put in node */ +	if (tbl->it_busno) { +		start <<= 12; +		end <<= 12; +		inc = 128 << 12; +		start |= tbl->it_busno; +		end |= tbl->it_busno; +	} +	/* p7ioc-style invalidation, 2 TCEs per write */ +	else if (tbl->it_type & TCE_PCI_SWINV_PAIR) { +		start |= (1ull << 63); +		end |= (1ull << 63); +		inc = 16; +	} +	/* Default (older HW) */ +	else +		inc = 128; + +	end |= inc - 1;		/* round up end to be different than start */ + +	mb(); /* Ensure above stores are visible */ +	while (start <= end) { +		__raw_writeq(start, invalidate); +		start += inc; +	} +	/* The iommu layer will do another mb() for us on build() and +	 * we don't care on free() +	 */ +} + +  static int pnv_tce_build(struct iommu_table *tbl, long index, long npages,  			 unsigned long uaddr, enum dma_data_direction direction,  			 struct dma_attrs *attrs)  {  	u64 proto_tce; -	u64 *tcep; +	u64 *tcep, *tces;  	u64 rpn;  	proto_tce = TCE_PCI_READ; // Read allowed @@ -270,25 +411,33 @@ static int pnv_tce_build(struct iommu_table *tbl, long index, long npages,  	if (direction != DMA_TO_DEVICE)  		proto_tce |= TCE_PCI_WRITE; -	tcep = ((u64 *)tbl->it_base) + index; +	tces = tcep = ((u64 *)tbl->it_base) + index - tbl->it_offset; +	rpn = __pa(uaddr) >> TCE_SHIFT; -	while (npages--) { -		/* can't move this out since we might cross LMB boundary */ -		rpn = (virt_to_abs(uaddr)) >> TCE_SHIFT; -		*tcep = proto_tce | (rpn & TCE_RPN_MASK) << TCE_RPN_SHIFT; +	while (npages--) +		*(tcep++) = proto_tce | (rpn++ << TCE_RPN_SHIFT); + +	/* Some implementations won't cache invalid TCEs and thus may not +	 * need that flush. We'll probably turn it_type into a bit mask +	 * of flags if that becomes the case +	 */ +	if (tbl->it_type & TCE_PCI_SWINV_CREATE) +		pnv_tce_invalidate(tbl, tces, tcep - 1); -		uaddr += TCE_PAGE_SIZE; -		tcep++; -	}  	return 0;  }  static void pnv_tce_free(struct iommu_table *tbl, long index, long npages)  { -	u64 *tcep = ((u64 *)tbl->it_base) + index; +	u64 *tcep, *tces; + +	tces = tcep = ((u64 *)tbl->it_base) + index - tbl->it_offset;  	while (npages--)  		*(tcep++) = 0; + +	if (tbl->it_type & TCE_PCI_SWINV_FREE) +		pnv_tce_invalidate(tbl, tces, tcep - 1);  }  void pnv_pci_setup_iommu_table(struct iommu_table *tbl, @@ -308,13 +457,14 @@ static struct iommu_table * __devinit  pnv_pci_setup_bml_iommu(struct pci_controller *hose)  {  	struct iommu_table *tbl; -	const __be64 *basep; +	const __be64 *basep, *swinvp;  	const __be32 *sizep;  	basep = of_get_property(hose->dn, "linux,tce-base", NULL);  	sizep = of_get_property(hose->dn, "linux,tce-size", NULL);  	if (basep == NULL || sizep == NULL) { -		pr_err("PCI: %s has missing tce entries !\n", hose->dn->full_name); +		pr_err("PCI: %s has missing tce entries !\n", +		       hose->dn->full_name);  		return NULL;  	}  	tbl = kzalloc_node(sizeof(struct iommu_table), GFP_KERNEL, hose->node); @@ -323,6 +473,15 @@ pnv_pci_setup_bml_iommu(struct pci_controller *hose)  	pnv_pci_setup_iommu_table(tbl, __va(be64_to_cpup(basep)),  				  be32_to_cpup(sizep), 0);  	iommu_init_table(tbl, hose->node); + +	/* Deal with SW invalidated TCEs when needed (BML way) */ +	swinvp = of_get_property(hose->dn, "linux,tce-sw-invalidate-info", +				 NULL); +	if (swinvp) { +		tbl->it_busno = swinvp[1]; +		tbl->it_index = (unsigned long)ioremap(swinvp[0], 8); +		tbl->it_type = TCE_PCI_SWINV_CREATE | TCE_PCI_SWINV_FREE; +	}  	return tbl;  } @@ -356,6 +515,13 @@ static void __devinit pnv_pci_dma_dev_setup(struct pci_dev *pdev)  		pnv_pci_dma_fallback_setup(hose, pdev);  } +/* Fixup wrong class code in p7ioc root complex */ +static void __devinit pnv_p7ioc_rc_quirk(struct pci_dev *dev) +{ +	dev->class = PCI_CLASS_BRIDGE_PCI << 8; +} +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_IBM, 0x3b9, pnv_p7ioc_rc_quirk); +  static int pnv_pci_probe_mode(struct pci_bus *bus)  {  	struct pci_controller *hose = pci_bus_to_host(bus); @@ -400,12 +566,24 @@ void __init pnv_pci_init(void)  		init_pci_config_tokens();  		find_and_init_phbs();  #endif /* CONFIG_PPC_POWERNV_RTAS */ -	} else { -		/* OPAL is here, do our normal stuff */ +	} +	/* OPAL is here, do our normal stuff */ +	else { +		int found_ioda = 0; + +		/* Look for IODA IO-Hubs. We don't support mixing IODA +		 * and p5ioc2 due to the need to change some global +		 * probing flags +		 */ +		for_each_compatible_node(np, NULL, "ibm,ioda-hub") { +			pnv_pci_init_ioda_hub(np); +			found_ioda = 1; +		}  		/* Look for p5ioc2 IO-Hubs */ -		for_each_compatible_node(np, NULL, "ibm,p5ioc2") -			pnv_pci_init_p5ioc2_hub(np); +		if (!found_ioda) +			for_each_compatible_node(np, NULL, "ibm,p5ioc2") +				pnv_pci_init_p5ioc2_hub(np);  	}  	/* Setup the linkage between OF nodes and PHBs */  |