📄 sata_sil24.c
字号:
if (rc) { ata_link_printk(link, KERN_ERR, "hardreset failed (port not ready)\n"); return rc; } return sata_pmp_std_hardreset(link, class, deadline);}static void sil24_freeze(struct ata_port *ap){ void __iomem *port = ap->ioaddr.cmd_addr; /* Port-wide IRQ mask in HOST_CTRL doesn't really work, clear * PORT_IRQ_ENABLE instead. */ writel(0xffff, port + PORT_IRQ_ENABLE_CLR);}static void sil24_thaw(struct ata_port *ap){ void __iomem *port = ap->ioaddr.cmd_addr; u32 tmp; /* clear IRQ */ tmp = readl(port + PORT_IRQ_STAT); writel(tmp, port + PORT_IRQ_STAT); /* turn IRQ back on */ writel(DEF_PORT_IRQ, port + PORT_IRQ_ENABLE_SET);}static void sil24_error_intr(struct ata_port *ap){ void __iomem *port = ap->ioaddr.cmd_addr; struct sil24_port_priv *pp = ap->private_data; struct ata_queued_cmd *qc = NULL; struct ata_link *link; struct ata_eh_info *ehi; int abort = 0, freeze = 0; u32 irq_stat; /* on error, we need to clear IRQ explicitly */ irq_stat = readl(port + PORT_IRQ_STAT); writel(irq_stat, port + PORT_IRQ_STAT); /* first, analyze and record host port events */ link = &ap->link; ehi = &link->eh_info; ata_ehi_clear_desc(ehi); ata_ehi_push_desc(ehi, "irq_stat 0x%08x", irq_stat); if (irq_stat & PORT_IRQ_SDB_NOTIFY) { ata_ehi_push_desc(ehi, "SDB notify"); sata_async_notification(ap); } if (irq_stat & (PORT_IRQ_PHYRDY_CHG | PORT_IRQ_DEV_XCHG)) { ata_ehi_hotplugged(ehi); ata_ehi_push_desc(ehi, "%s", irq_stat & PORT_IRQ_PHYRDY_CHG ? "PHY RDY changed" : "device exchanged"); freeze = 1; } if (irq_stat & PORT_IRQ_UNK_FIS) { ehi->err_mask |= AC_ERR_HSM; ehi->action |= ATA_EH_SOFTRESET; ata_ehi_push_desc(ehi, "unknown FIS"); freeze = 1; } /* deal with command error */ if (irq_stat & PORT_IRQ_ERROR) { struct sil24_cerr_info *ci = NULL; unsigned int err_mask = 0, action = 0; u32 context, cerr; int pmp; abort = 1; /* DMA Context Switch Failure in Port Multiplier Mode * errata. If we have active commands to 3 or more * devices, any error condition on active devices can * corrupt DMA context switching. */ if (ap->nr_active_links >= 3) { ehi->err_mask |= AC_ERR_OTHER; ehi->action |= ATA_EH_HARDRESET; ata_ehi_push_desc(ehi, "PMP DMA CS errata"); pp->do_port_rst = 1; freeze = 1; } /* find out the offending link and qc */ if (ap->nr_pmp_links) { context = readl(port + PORT_CONTEXT); pmp = (context >> 5) & 0xf; if (pmp < ap->nr_pmp_links) { link = &ap->pmp_link[pmp]; ehi = &link->eh_info; qc = ata_qc_from_tag(ap, link->active_tag); ata_ehi_clear_desc(ehi); ata_ehi_push_desc(ehi, "irq_stat 0x%08x", irq_stat); } else { err_mask |= AC_ERR_HSM; action |= ATA_EH_HARDRESET; freeze = 1; } } else qc = ata_qc_from_tag(ap, link->active_tag); /* analyze CMD_ERR */ cerr = readl(port + PORT_CMD_ERR); if (cerr < ARRAY_SIZE(sil24_cerr_db)) ci = &sil24_cerr_db[cerr]; if (ci && ci->desc) { err_mask |= ci->err_mask; action |= ci->action; if (action & ATA_EH_RESET_MASK) freeze = 1; ata_ehi_push_desc(ehi, "%s", ci->desc); } else { err_mask |= AC_ERR_OTHER; action |= ATA_EH_SOFTRESET; freeze = 1; ata_ehi_push_desc(ehi, "unknown command error %d", cerr); } /* record error info */ if (qc) { sil24_read_tf(ap, qc->tag, &pp->tf); qc->err_mask |= err_mask; } else ehi->err_mask |= err_mask; ehi->action |= action; /* if PMP, resume */ if (ap->nr_pmp_links) writel(PORT_CS_PMP_RESUME, port + PORT_CTRL_STAT); } /* freeze or abort */ if (freeze) ata_port_freeze(ap); else if (abort) { if (qc) ata_link_abort(qc->dev->link); else ata_port_abort(ap); }}static void sil24_finish_qc(struct ata_queued_cmd *qc){ struct ata_port *ap = qc->ap; struct sil24_port_priv *pp = ap->private_data; if (qc->flags & ATA_QCFLAG_RESULT_TF) sil24_read_tf(ap, qc->tag, &pp->tf);}static inline void sil24_host_intr(struct ata_port *ap){ void __iomem *port = ap->ioaddr.cmd_addr; u32 slot_stat, qc_active; int rc; /* If PCIX_IRQ_WOC, there's an inherent race window between * clearing IRQ pending status and reading PORT_SLOT_STAT * which may cause spurious interrupts afterwards. This is * unavoidable and much better than losing interrupts which * happens if IRQ pending is cleared after reading * PORT_SLOT_STAT. */ if (ap->flags & SIL24_FLAG_PCIX_IRQ_WOC) writel(PORT_IRQ_COMPLETE, port + PORT_IRQ_STAT); slot_stat = readl(port + PORT_SLOT_STAT); if (unlikely(slot_stat & HOST_SSTAT_ATTN)) { sil24_error_intr(ap); return; } qc_active = slot_stat & ~HOST_SSTAT_ATTN; rc = ata_qc_complete_multiple(ap, qc_active, sil24_finish_qc); if (rc > 0) return; if (rc < 0) { struct ata_eh_info *ehi = &ap->link.eh_info; ehi->err_mask |= AC_ERR_HSM; ehi->action |= ATA_EH_SOFTRESET; ata_port_freeze(ap); return; } /* spurious interrupts are expected if PCIX_IRQ_WOC */ if (!(ap->flags & SIL24_FLAG_PCIX_IRQ_WOC) && ata_ratelimit()) ata_port_printk(ap, KERN_INFO, "spurious interrupt " "(slot_stat 0x%x active_tag %d sactive 0x%x)\n", slot_stat, ap->link.active_tag, ap->link.sactive);}static irqreturn_t sil24_interrupt(int irq, void *dev_instance){ struct ata_host *host = dev_instance; void __iomem *host_base = host->iomap[SIL24_HOST_BAR]; unsigned handled = 0; u32 status; int i; status = readl(host_base + HOST_IRQ_STAT); if (status == 0xffffffff) { printk(KERN_ERR DRV_NAME ": IRQ status == 0xffffffff, " "PCI fault or device removal?\n"); goto out; } if (!(status & IRQ_STAT_4PORTS)) goto out; spin_lock(&host->lock); for (i = 0; i < host->n_ports; i++) if (status & (1 << i)) { struct ata_port *ap = host->ports[i]; if (ap && !(ap->flags & ATA_FLAG_DISABLED)) { sil24_host_intr(ap); handled++; } else printk(KERN_ERR DRV_NAME ": interrupt from disabled port %d\n", i); } spin_unlock(&host->lock); out: return IRQ_RETVAL(handled);}static void sil24_error_handler(struct ata_port *ap){ struct sil24_port_priv *pp = ap->private_data; if (sil24_init_port(ap)) ata_eh_freeze_port(ap); /* perform recovery */ sata_pmp_do_eh(ap, ata_std_prereset, sil24_softreset, sil24_hardreset, ata_std_postreset, sata_pmp_std_prereset, sil24_pmp_softreset, sil24_pmp_hardreset, sata_pmp_std_postreset); pp->do_port_rst = 0;}static void sil24_post_internal_cmd(struct ata_queued_cmd *qc){ struct ata_port *ap = qc->ap; /* make DMA engine forget about the failed command */ if ((qc->flags & ATA_QCFLAG_FAILED) && sil24_init_port(ap)) ata_eh_freeze_port(ap);}static int sil24_port_start(struct ata_port *ap){ struct device *dev = ap->host->dev; struct sil24_port_priv *pp; union sil24_cmd_block *cb; size_t cb_size = sizeof(*cb) * SIL24_MAX_CMDS; dma_addr_t cb_dma; int rc; pp = devm_kzalloc(dev, sizeof(*pp), GFP_KERNEL); if (!pp) return -ENOMEM; pp->tf.command = ATA_DRDY; cb = dmam_alloc_coherent(dev, cb_size, &cb_dma, GFP_KERNEL); if (!cb) return -ENOMEM; memset(cb, 0, cb_size); rc = ata_pad_alloc(ap, dev); if (rc) return rc; pp->cmd_block = cb; pp->cmd_block_dma = cb_dma; ap->private_data = pp; return 0;}static void sil24_init_controller(struct ata_host *host){ void __iomem *host_base = host->iomap[SIL24_HOST_BAR]; u32 tmp; int i; /* GPIO off */ writel(0, host_base + HOST_FLASH_CMD); /* clear global reset & mask interrupts during initialization */ writel(0, host_base + HOST_CTRL); /* init ports */ for (i = 0; i < host->n_ports; i++) { struct ata_port *ap = host->ports[i]; void __iomem *port = ap->ioaddr.cmd_addr; /* Initial PHY setting */ writel(0x20c, port + PORT_PHY_CFG); /* Clear port RST */ tmp = readl(port + PORT_CTRL_STAT); if (tmp & PORT_CS_PORT_RST) { writel(PORT_CS_PORT_RST, port + PORT_CTRL_CLR); tmp = ata_wait_register(port + PORT_CTRL_STAT, PORT_CS_PORT_RST, PORT_CS_PORT_RST, 10, 100); if (tmp & PORT_CS_PORT_RST) dev_printk(KERN_ERR, host->dev, "failed to clear port RST\n"); } /* configure port */ sil24_config_port(ap); } /* Turn on interrupts */ writel(IRQ_STAT_4PORTS, host_base + HOST_CTRL);}static int sil24_init_one(struct pci_dev *pdev, const struct pci_device_id *ent){ extern int __MARKER__sil24_cmd_block_is_sized_wrongly; static int printed_version; struct ata_port_info pi = sil24_port_info[ent->driver_data]; const struct ata_port_info *ppi[] = { &pi, NULL }; void __iomem * const *iomap; struct ata_host *host; int i, rc; u32 tmp; /* cause link error if sil24_cmd_block is sized wrongly */ if (sizeof(union sil24_cmd_block) != PAGE_SIZE) __MARKER__sil24_cmd_block_is_sized_wrongly = 1; if (!printed_version++) dev_printk(KERN_DEBUG, &pdev->dev, "version " DRV_VERSION "\n"); /* acquire resources */ rc = pcim_enable_device(pdev); if (rc) return rc; rc = pcim_iomap_regions(pdev, (1 << SIL24_HOST_BAR) | (1 << SIL24_PORT_BAR), DRV_NAME); if (rc) return rc; iomap = pcim_iomap_table(pdev); /* apply workaround for completion IRQ loss on PCI-X errata */ if (pi.flags & SIL24_FLAG_PCIX_IRQ_WOC) { tmp = readl(iomap[SIL24_HOST_BAR] + HOST_CTRL); if (tmp & (HOST_CTRL_TRDY | HOST_CTRL_STOP | HOST_CTRL_DEVSEL)) dev_printk(KERN_INFO, &pdev->dev, "Applying completion IRQ loss on PCI-X " "errata fix\n"); else pi.flags &= ~SIL24_FLAG_PCIX_IRQ_WOC; } /* allocate and fill host */ host = ata_host_alloc_pinfo(&pdev->dev, ppi, SIL24_FLAG2NPORTS(ppi[0]->flags)); if (!host) return -ENOMEM; host->iomap = iomap; for (i = 0; i < host->n_ports; i++) { struct ata_port *ap = host->ports[i]; size_t offset = ap->port_no * PORT_REGS_SIZE; void __iomem *port = iomap[SIL24_PORT_BAR] + offset; host->ports[i]->ioaddr.cmd_addr = port; host->ports[i]->ioaddr.scr_addr = port + PORT_SCONTROL; ata_port_pbar_desc(ap, SIL24_HOST_BAR, -1, "host"); ata_port_pbar_desc(ap, SIL24_PORT_BAR, offset, "port"); } /* configure and activate the device */ if (!pci_set_dma_mask(pdev, DMA_64BIT_MASK)) { rc = pci_set_consistent_dma_mask(pdev, DMA_64BIT_MASK); if (rc) { rc = pci_set_consistent_dma_mask(pdev, DMA_32BIT_MASK); if (rc) { dev_printk(KERN_ERR, &pdev->dev, "64-bit DMA enable failed\n"); return rc; } } } else { rc = pci_set_dma_mask(pdev, DMA_32BIT_MASK); if (rc) { dev_printk(KERN_ERR, &pdev->dev, "32-bit DMA enable failed\n"); return rc; } rc = pci_set_consistent_dma_mask(pdev, DMA_32BIT_MASK); if (rc) { dev_printk(KERN_ERR, &pdev->dev, "32-bit consistent DMA enable failed\n"); return rc; } } sil24_init_controller(host); pci_set_master(pdev); return ata_host_activate(host, pdev->irq, sil24_interrupt, IRQF_SHARED, &sil24_sht);}#ifdef CONFIG_PMstatic int sil24_pci_device_resume(struct pci_dev *pdev){ struct ata_host *host = dev_get_drvdata(&pdev->dev); void __iomem *host_base = host->iomap[SIL24_HOST_BAR]; int rc; rc = ata_pci_device_do_resume(pdev); if (rc) return rc; if (pdev->dev.power.power_state.event == PM_EVENT_SUSPEND) writel(HOST_CTRL_GLOBAL_RST, host_base + HOST_CTRL); sil24_init_controller(host); ata_host_resume(host); return 0;}static int sil24_port_resume(struct ata_port *ap){ sil24_config_pmp(ap, ap->nr_pmp_links); return 0;}#endifstatic int __init sil24_init(void){ return pci_register_driver(&sil24_pci_driver);}static void __exit sil24_exit(void){ pci_unregister_driver(&sil24_pci_driver);}MODULE_AUTHOR("Tejun Heo");MODULE_DESCRIPTION("Silicon Image 3124/3132 SATA low-level driver");MODULE_LICENSE("GPL");MODULE_DEVICE_TABLE(pci, sil24_pci_tbl);module_init(sil24_init);module_exit(sil24_exit);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -