Re: [PATCH 2.6.15.4 rel.2 1/1] libata: add hotswap to sata_svw

From: Benjamin Herrenschmidt
Date: Thu Feb 16 2006 - 17:39:15 EST


On Thu, 2006-02-16 at 16:09 +0100, Martin Devera wrote:
> From: Martin Devera <devik@xxxxxx>
>
> Add hotswap capability to Serverworks/BroadCom SATA controlers. The
> controler has SIM register and it selects which bits in SATA_ERROR
> register fires interrupt.
> The solution hooks on COMWAKE (plug), PHYRDY change and 10B8B decode
> error (unplug) and calls into Lukasz's hotswap framework.
> The code got one day testing on dual core Athlon64 H8SSL Supermicro
> MoBo with HT-1000 SATA, SMP kernel and two CaviarRE SATA HDDs in
> hotswap bays.
>
> Signed-off-by: Martin Devera <devik@xxxxxx>
> ---
> This patch depends on Lukasz Kosewski's:
> [PATCH 2.6.15-rc7-git3 2/3] libata: framework API for hotswapping drives on libata
> and its intent is mainly wider testing and review

Nice ! I'll review and test it asap. Just give me a few days as I'm
fairly busy lately.

Ben.

> --- a/drivers/scsi/sata_svw.c Wed Feb 15 11:20:22 2006
> +++ b/drivers/scsi/sata_svw.c Thu Feb 16 15:49:31 2006
> @@ -9,6 +9,7 @@
> * Copyright 2003 Benjamin Herrenschmidt <benh@xxxxxxxxxxxxxxxxxxx>
> *
> * Bits from Jeff Garzik, Copyright RedHat, Inc.
> + * Hotplug code by Martin Devera <devik@xxxxxx>
> *
> * This driver probably works with non-Apple versions of the
> * Broadcom chipset...
> @@ -54,7 +55,7 @@
> #endif /* CONFIG_PPC_OF */
>
> #define DRV_NAME "sata_svw"
> -#define DRV_VERSION "1.07"
> +#define DRV_VERSION "1.08"
>
> /* Taskfile registers offsets */
> #define K2_SATA_TF_CMD_OFFSET 0x00
> @@ -81,9 +82,18 @@
> #define K2_SATA_SICR2_OFFSET 0x84
> #define K2_SATA_SIM_OFFSET 0x88
>
> +/* Hotplug interrupt masks */
> +#define K2_SIM_REMOVE_MASK 0x90000
> +#define K2_SIM_INSERT_MASK 0x40000
> +
> /* Port stride */
> #define K2_SATA_PORT_OFFSET 0x100
>
> +struct svw_host
> +{
> + unsigned long not_in_reset_bitmap; /* see k2_hotplug_irq for explanation */
> +};
> +
> static u8 k2_stat_check_status(struct ata_port *ap);
>
>
> @@ -282,6 +292,118 @@ static int k2_sata_proc_info(struct Scsi
> }
> #endif /* CONFIG_PPC_OF */
>
> +/* This shall run every time for all ports when interrupt fires. It
> + returns 1 if hotplug related event was handled. */
> +static int k2_hotplug_irq(struct ata_port *ap,struct svw_host *host)
> +{
> + u32 error = scr_read(ap, SCR_ERROR);
> + error &= K2_SIM_REMOVE_MASK | K2_SIM_INSERT_MASK;
> + if (!error)
> + return 0;
> +
> + /* clear error bits, this must be done in any case or we get
> + unhandled IRQ BUG */
> + scr_write(ap, SCR_ERROR, error);
> +
> + DPRINTK("K2 Got (un)plug event %X (status %X) on dev %d\n",
> + error,scr_read(ap, SCR_STATUS),ap->port_no+1);
> +
> + /* not_in_reset_bitmap has a bit set for each port when the port is
> + outside of reset routine. Because reset generates spurious events
> + we ignore them here. Because not_in_reset_bitmap is zero at init
> + time we also ignore these events until the port is first reset.
> + It is not possible simply to set SIM register to zero because
> + interrupt comming from other port still causes this routine run
> + for all ports and the event will be discovered. */
> + if (!test_bit(ap->port_no,&host->not_in_reset_bitmap)) {
> + DPRINTK("K2 ignoring the event\n");
> + return 1;
> + }
> +
> + if (error & K2_SIM_INSERT_MASK)
> + sata_hot_plug(ap);
> + else /* make sure that the drive is really gone */
> + if (!sata_dev_present(ap))
> + sata_hot_unplug(ap);
> + return 1;
> +}
> +
> +/* Essencialy the same as in libata, we only needed to hook hotplug events. */
> +static irqreturn_t k2_interrupt (int irq, void *dev_instance, struct pt_regs *regs)
> +{
> + struct ata_host_set *host_set = dev_instance;
> + struct svw_host *host = (struct svw_host *)host_set->private_data;
> + unsigned int i;
> + unsigned int handled = 0;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&host_set->lock, flags);
> +
> + for (i = 0; i < host_set->n_ports; i++) {
> + struct ata_port *ap;
> +
> + ap = host_set->ports[i];
> + if (!ap)
> + continue;
> +
> + if (!(ap->flags & (ATA_FLAG_PORT_DISABLED | ATA_FLAG_NOINTR))) {
> + struct ata_queued_cmd *qc;
> +
> + qc = ata_qc_from_tag(ap, ap->active_tag);
> + if (qc && (!(qc->tf.ctl & ATA_NIEN)) &&
> + (qc->flags & ATA_QCFLAG_ACTIVE))
> + handled |= ata_host_intr(ap, qc);
> + }
> + handled |= k2_hotplug_irq(ap,host);
> + }
> +
> + spin_unlock_irqrestore(&host_set->lock, flags);
> +
> + return IRQ_RETVAL(handled);
> +}
> +
> +/* Override phy_reset to setup SIM register. It must be done here because it
> + can trigger IRQ and we must be sure that a handler is installed. And it is
> + at the time when reset is called. */
> +static void k2_phy_reset(struct ata_port *ap)
> +{
> + /* HACK: there is no standard place in port structure to record
> + base pointer - incidentaly cmd_addr is at offset zero ... */
> + void *base = (void*)ap->ioaddr.cmd_addr;
> +
> + struct svw_host *host = (struct svw_host *)ap->host_set->private_data;
> +
> + DPRINTK("K2 reset %d start\n",ap->port_no+1);
> +
> + clear_bit(ap->port_no,&host->not_in_reset_bitmap);
> +
> + /* Not strictly neccessary but why to fire spurious interrupts
> + and then ignore them anyway */
> + writel(0, base + K2_SATA_SIM_OFFSET);
> + sata_phy_reset(ap);
> +
> + /* Clear a magic bit in SCR1 according to Darwin, those help
> + * some funky seagate drives (though so far, those were already
> + * set by the firmware on the machines I had access to)
> + */
> + writel(readl(base + K2_SATA_SICR1_OFFSET) & ~0x00040000,
> + base + K2_SATA_SICR1_OFFSET);
> +
> + /* Make sure that interrupt is triggered when these XXX_MASK bits
> + in SCR_ERROR are set */
> + scr_write(ap, SCR_ERROR, K2_SIM_INSERT_MASK | K2_SIM_REMOVE_MASK);
> + writel(K2_SIM_INSERT_MASK | K2_SIM_REMOVE_MASK, base + K2_SATA_SIM_OFFSET);
> +
> + set_bit(ap->port_no,&host->not_in_reset_bitmap);
> +
> + DPRINTK("K2 reset done\n");
> +}
> +
> +static void k2_host_stop (struct ata_host_set *host_set)
> +{
> + kfree(host_set->private_data);
> + ata_pci_host_stop(host_set);
> +}
>
> static struct scsi_host_template k2_sata_sht = {
> .module = THIS_MODULE,
> @@ -314,7 +436,7 @@ static const struct ata_port_operations
> .check_status = k2_stat_check_status,
> .exec_command = ata_exec_command,
> .dev_select = ata_std_dev_select,
> - .phy_reset = sata_phy_reset,
> + .phy_reset = k2_phy_reset,
> .bmdma_setup = k2_bmdma_setup_mmio,
> .bmdma_start = k2_bmdma_start_mmio,
> .bmdma_stop = ata_bmdma_stop,
> @@ -322,13 +444,13 @@ static const struct ata_port_operations
> .qc_prep = ata_qc_prep,
> .qc_issue = ata_qc_issue_prot,
> .eng_timeout = ata_eng_timeout,
> - .irq_handler = ata_interrupt,
> + .irq_handler = k2_interrupt,
> .irq_clear = ata_bmdma_irq_clear,
> .scr_read = k2_sata_scr_read,
> .scr_write = k2_sata_scr_write,
> .port_start = ata_port_start,
> .port_stop = ata_port_stop,
> - .host_stop = ata_pci_host_stop,
> + .host_stop = k2_host_stop,
> };
>
> static void k2_sata_setup_port(struct ata_ioports *port, unsigned long base)
> @@ -354,6 +476,7 @@ static void k2_sata_setup_port(struct at
> static int k2_sata_init_one (struct pci_dev *pdev, const struct pci_device_id *ent)
> {
> static int printed_version;
> + struct svw_host *host;
> struct ata_probe_ent *probe_ent = NULL;
> unsigned long base;
> void __iomem *mmio_base;
> @@ -408,17 +531,13 @@ static int k2_sata_init_one (struct pci_
> goto err_out_free_ent;
> }
> base = (unsigned long) mmio_base;
> -
> - /* Clear a magic bit in SCR1 according to Darwin, those help
> - * some funky seagate drives (though so far, those were already
> - * set by the firmware on the machines I had access to)
> - */
> - writel(readl(mmio_base + K2_SATA_SICR1_OFFSET) & ~0x00040000,
> - mmio_base + K2_SATA_SICR1_OFFSET);
> -
> - /* Clear SATA error & interrupts we don't use */
> - writel(0xffffffff, mmio_base + K2_SATA_SCR_ERROR_OFFSET);
> - writel(0x0, mmio_base + K2_SATA_SIM_OFFSET);
> +
> + host = kmalloc(sizeof(struct svw_host), GFP_KERNEL);
> + if (!host)
> + goto err_out_free_ent;
> +
> + memset(host, 0, sizeof(struct svw_host));
> + probe_ent->private_data = host;
>
> probe_ent->sht = &k2_sata_sht;
> probe_ent->host_flags = ATA_FLAG_SATA | ATA_FLAG_SATA_RESET |

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/