diff options
Diffstat (limited to 'drivers')
143 files changed, 20356 insertions, 7874 deletions
diff --git a/drivers/Makefile b/drivers/Makefile index 35d5181c8ef2..0ee98d50f975 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -92,7 +92,6 @@ obj-$(CONFIG_BT) += bluetooth/ obj-$(CONFIG_ACCESSIBILITY) += accessibility/ obj-$(CONFIG_ISDN) += isdn/ obj-$(CONFIG_EDAC) += edac/ -obj-$(CONFIG_MCA) += mca/ obj-$(CONFIG_EISA) += eisa/ obj-y += lguest/ obj-$(CONFIG_CPU_FREQ) += cpufreq/ diff --git a/drivers/ata/Kconfig b/drivers/ata/Kconfig index 6bdedd7cca2c..2be8ef1d3093 100644 --- a/drivers/ata/Kconfig +++ b/drivers/ata/Kconfig @@ -416,6 +416,15 @@ config PATA_EFAR If unsure, say N. +config PATA_EP93XX + tristate "Cirrus Logic EP93xx PATA support" + depends on ARCH_EP93XX + help + This option enables support for the PATA controller in + the Cirrus Logic EP9312 and EP9315 ARM CPU. + + If unsure, say N. + config PATA_HPT366 tristate "HPT 366/368 PATA support" depends on PCI diff --git a/drivers/ata/Makefile b/drivers/ata/Makefile index 6ece5b7231a3..a454a139b1d2 100644 --- a/drivers/ata/Makefile +++ b/drivers/ata/Makefile @@ -43,6 +43,7 @@ obj-$(CONFIG_PATA_CS5535) += pata_cs5535.o obj-$(CONFIG_PATA_CS5536) += pata_cs5536.o obj-$(CONFIG_PATA_CYPRESS) += pata_cypress.o obj-$(CONFIG_PATA_EFAR) += pata_efar.o +obj-$(CONFIG_PATA_EP93XX) += pata_ep93xx.o obj-$(CONFIG_PATA_HPT366) += pata_hpt366.o obj-$(CONFIG_PATA_HPT37X) += pata_hpt37x.o obj-$(CONFIG_PATA_HPT3X2N) += pata_hpt3x2n.o diff --git a/drivers/ata/ata_generic.c b/drivers/ata/ata_generic.c index 7df56ec31819..aae115600b74 100644 --- a/drivers/ata/ata_generic.c +++ b/drivers/ata/ata_generic.c @@ -177,7 +177,7 @@ static int ata_generic_init_one(struct pci_dev *dev, const struct pci_device_id if ((id->driver_data & ATA_GEN_CLASS_MATCH) && all_generic_ide == 0) return -ENODEV; - if (id->driver_data & ATA_GEN_INTEL_IDER) + if ((id->driver_data & ATA_GEN_INTEL_IDER) && !all_generic_ide) if (!is_intel_ider(dev)) return -ENODEV; diff --git a/drivers/ata/ata_piix.c b/drivers/ata/ata_piix.c index 7857e8fd0a3e..3c809bfbccf5 100644 --- a/drivers/ata/ata_piix.c +++ b/drivers/ata/ata_piix.c @@ -1554,6 +1554,39 @@ static bool piix_broken_system_poweroff(struct pci_dev *pdev) return false; } +static int prefer_ms_hyperv = 1; +module_param(prefer_ms_hyperv, int, 0); + +static void piix_ignore_devices_quirk(struct ata_host *host) +{ +#if IS_ENABLED(CONFIG_HYPERV_STORAGE) + static const struct dmi_system_id ignore_hyperv[] = { + { + /* On Hyper-V hypervisors the disks are exposed on + * both the emulated SATA controller and on the + * paravirtualised drivers. The CD/DVD devices + * are only exposed on the emulated controller. + * Request we ignore ATA devices on this host. + */ + .ident = "Hyper-V Virtual Machine", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, + "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "Virtual Machine"), + }, + }, + { } /* terminate list */ + }; + const struct dmi_system_id *dmi = dmi_first_match(ignore_hyperv); + + if (dmi && prefer_ms_hyperv) { + host->flags |= ATA_HOST_IGNORE_ATA; + dev_info(host->dev, "%s detected, ATA device ignore set\n", + dmi->ident); + } +#endif +} + /** * piix_init_one - Register PIIX ATA PCI device with kernel services * @pdev: PCI device to register @@ -1669,6 +1702,9 @@ static int __devinit piix_init_one(struct pci_dev *pdev, } host->flags |= ATA_HOST_PARALLEL_SCAN; + /* Allow hosts to specify device types to ignore when scanning. */ + piix_ignore_devices_quirk(host); + pci_set_master(pdev); return ata_pci_sff_activate_host(host, ata_bmdma_interrupt, sht); } diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c index 23763a1ec570..cece3a4d11ea 100644 --- a/drivers/ata/libata-core.c +++ b/drivers/ata/libata-core.c @@ -1973,6 +1973,12 @@ retry: if (class == ATA_DEV_ATA) { if (!ata_id_is_ata(id) && !ata_id_is_cfa(id)) goto err_out; + if (ap->host->flags & ATA_HOST_IGNORE_ATA && + ata_id_is_ata(id)) { + ata_dev_dbg(dev, + "host indicates ignore ATA devices, ignored\n"); + return -ENOENT; + } } else { if (ata_id_is_ata(id)) goto err_out; @@ -4051,6 +4057,7 @@ static const struct ata_blacklist_entry ata_device_blacklist [] = { { "_NEC DV5800A", NULL, ATA_HORKAGE_NODMA }, { "SAMSUNG CD-ROM SN-124", "N001", ATA_HORKAGE_NODMA }, { "Seagate STT20000A", NULL, ATA_HORKAGE_NODMA }, + { "2GB ATA Flash Disk", "ADMA428M", ATA_HORKAGE_NODMA }, /* Odd clown on sil3726/4726 PMPs */ { "Config Disk", NULL, ATA_HORKAGE_DISABLE }, diff --git a/drivers/ata/libata-eh.c b/drivers/ata/libata-eh.c index d1fbd59ead16..6d53cf9b3b6e 100644 --- a/drivers/ata/libata-eh.c +++ b/drivers/ata/libata-eh.c @@ -2047,6 +2047,26 @@ static unsigned int ata_eh_speed_down(struct ata_device *dev, } /** + * ata_eh_worth_retry - analyze error and decide whether to retry + * @qc: qc to possibly retry + * + * Look at the cause of the error and decide if a retry + * might be useful or not. We don't want to retry media errors + * because the drive itself has probably already taken 10-30 seconds + * doing its own internal retries before reporting the failure. + */ +static inline int ata_eh_worth_retry(struct ata_queued_cmd *qc) +{ + if (qc->flags & AC_ERR_MEDIA) + return 0; /* don't retry media errors */ + if (qc->flags & ATA_QCFLAG_IO) + return 1; /* otherwise retry anything from fs stack */ + if (qc->err_mask & AC_ERR_INVALID) + return 0; /* don't retry these */ + return qc->err_mask != AC_ERR_DEV; /* retry if not dev error */ +} + +/** * ata_eh_link_autopsy - analyze error and determine recovery action * @link: host link to perform autopsy on * @@ -2120,9 +2140,7 @@ static void ata_eh_link_autopsy(struct ata_link *link) qc->err_mask &= ~(AC_ERR_DEV | AC_ERR_OTHER); /* determine whether the command is worth retrying */ - if (qc->flags & ATA_QCFLAG_IO || - (!(qc->err_mask & AC_ERR_INVALID) && - qc->err_mask != AC_ERR_DEV)) + if (ata_eh_worth_retry(qc)) qc->flags |= ATA_QCFLAG_RETRY; /* accumulate error info */ diff --git a/drivers/ata/pata_ep93xx.c b/drivers/ata/pata_ep93xx.c new file mode 100644 index 000000000000..6ef2e3741f76 --- /dev/null +++ b/drivers/ata/pata_ep93xx.c @@ -0,0 +1,1044 @@ +/* + * EP93XX PATA controller driver. + * + * Copyright (c) 2012, Metasoft s.c. + * Rafal Prylowski <[email protected]> + * + * Based on pata_scc.c, pata_icside.c and on earlier version of EP93XX + * PATA driver by Lennert Buytenhek and Alessandro Zummo. + * Read/Write timings, resource management and other improvements + * from driver by Joao Ramos and Bartlomiej Zolnierkiewicz. + * DMA engine support based on spi-ep93xx.c by Mika Westerberg. + * + * Original copyrights: + * + * Support for Cirrus Logic's EP93xx (EP9312, EP9315) CPUs + * PATA host controller driver. + * + * Copyright (c) 2009, Bartlomiej Zolnierkiewicz + * + * Heavily based on the ep93xx-ide.c driver: + * + * Copyright (c) 2009, Joao Ramos <[email protected]> + * INESC Inovacao (INOV) + * + * EP93XX PATA controller driver. + * Copyright (C) 2007 Lennert Buytenhek <[email protected]> + * + * An ATA driver for the Cirrus Logic EP93xx PATA controller. + * + * Based on an earlier version by Alessandro Zummo, which is: + * Copyright (C) 2006 Tower Technologies + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/blkdev.h> +#include <scsi/scsi_host.h> +#include <linux/ata.h> +#include <linux/libata.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/dmaengine.h> +#include <linux/ktime.h> + +#include <mach/dma.h> +#include <mach/platform.h> + +#define DRV_NAME "ep93xx-ide" +#define DRV_VERSION "1.0" + +enum { + /* IDE Control Register */ + IDECTRL = 0x00, + IDECTRL_CS0N = (1 << 0), + IDECTRL_CS1N = (1 << 1), + IDECTRL_DIORN = (1 << 5), + IDECTRL_DIOWN = (1 << 6), + IDECTRL_INTRQ = (1 << 9), + IDECTRL_IORDY = (1 << 10), + /* + * the device IDE register to be accessed is selected through + * IDECTRL register's specific bitfields 'DA', 'CS1N' and 'CS0N': + * b4 b3 b2 b1 b0 + * A2 A1 A0 CS1N CS0N + * the values filled in this structure allows the value to be directly + * ORed to the IDECTRL register, hence giving directly the A[2:0] and + * CS1N/CS0N values for each IDE register. + * The values correspond to the transformation: + * ((real IDE address) << 2) | CS1N value << 1 | CS0N value + */ + IDECTRL_ADDR_CMD = 0 + 2, /* CS1 */ + IDECTRL_ADDR_DATA = (ATA_REG_DATA << 2) + 2, + IDECTRL_ADDR_ERROR = (ATA_REG_ERR << 2) + 2, + IDECTRL_ADDR_FEATURE = (ATA_REG_FEATURE << 2) + 2, + IDECTRL_ADDR_NSECT = (ATA_REG_NSECT << 2) + 2, + IDECTRL_ADDR_LBAL = (ATA_REG_LBAL << 2) + 2, + IDECTRL_ADDR_LBAM = (ATA_REG_LBAM << 2) + 2, + IDECTRL_ADDR_LBAH = (ATA_REG_LBAH << 2) + 2, + IDECTRL_ADDR_DEVICE = (ATA_REG_DEVICE << 2) + 2, + IDECTRL_ADDR_STATUS = (ATA_REG_STATUS << 2) + 2, + IDECTRL_ADDR_COMMAND = (ATA_REG_CMD << 2) + 2, + IDECTRL_ADDR_ALTSTATUS = (0x06 << 2) + 1, /* CS0 */ + IDECTRL_ADDR_CTL = (0x06 << 2) + 1, /* CS0 */ + + /* IDE Configuration Register */ + IDECFG = 0x04, + IDECFG_IDEEN = (1 << 0), + IDECFG_PIO = (1 << 1), + IDECFG_MDMA = (1 << 2), + IDECFG_UDMA = (1 << 3), + IDECFG_MODE_SHIFT = 4, + IDECFG_MODE_MASK = (0xf << 4), + IDECFG_WST_SHIFT = 8, + IDECFG_WST_MASK = (0x3 << 8), + + /* MDMA Operation Register */ + IDEMDMAOP = 0x08, + + /* UDMA Operation Register */ + IDEUDMAOP = 0x0c, + IDEUDMAOP_UEN = (1 << 0), + IDEUDMAOP_RWOP = (1 << 1), + + /* PIO/MDMA/UDMA Data Registers */ + IDEDATAOUT = 0x10, + IDEDATAIN = 0x14, + IDEMDMADATAOUT = 0x18, + IDEMDMADATAIN = 0x1c, + IDEUDMADATAOUT = 0x20, + IDEUDMADATAIN = 0x24, + + /* UDMA Status Register */ + IDEUDMASTS = 0x28, + IDEUDMASTS_DMAIDE = (1 << 16), + IDEUDMASTS_INTIDE = (1 << 17), + IDEUDMASTS_SBUSY = (1 << 18), + IDEUDMASTS_NDO = (1 << 24), + IDEUDMASTS_NDI = (1 << 25), + IDEUDMASTS_N4X = (1 << 26), + + /* UDMA Debug Status Register */ + IDEUDMADEBUG = 0x2c, +}; + +struct ep93xx_pata_data { + const struct platform_device *pdev; + void __iomem *ide_base; + struct ata_timing t; + bool iordy; + + unsigned long udma_in_phys; + unsigned long udma_out_phys; + + struct dma_chan *dma_rx_channel; + struct ep93xx_dma_data dma_rx_data; + struct dma_chan *dma_tx_channel; + struct ep93xx_dma_data dma_tx_data; +}; + +static void ep93xx_pata_clear_regs(void __iomem *base) +{ + writel(IDECTRL_CS0N | IDECTRL_CS1N | IDECTRL_DIORN | + IDECTRL_DIOWN, base + IDECTRL); + + writel(0, base + IDECFG); + writel(0, base + IDEMDMAOP); + writel(0, base + IDEUDMAOP); + writel(0, base + IDEDATAOUT); + writel(0, base + IDEDATAIN); + writel(0, base + IDEMDMADATAOUT); + writel(0, base + IDEMDMADATAIN); + writel(0, base + IDEUDMADATAOUT); + writel(0, base + IDEUDMADATAIN); + writel(0, base + IDEUDMADEBUG); +} + +static bool ep93xx_pata_check_iordy(void __iomem *base) +{ + return !!(readl(base + IDECTRL) & IDECTRL_IORDY); +} + +/* + * According to EP93xx User's Guide, WST field of IDECFG specifies number + * of HCLK cycles to hold the data bus after a PIO write operation. + * It should be programmed to guarantee following delays: + * + * PIO Mode [ns] + * 0 30 + * 1 20 + * 2 15 + * 3 10 + * 4 5 + * + * Maximum possible value for HCLK is 100MHz. + */ +static int ep93xx_pata_get_wst(int pio_mode) +{ + int val; + + if (pio_mode == 0) + val = 3; + else if (pio_mode < 3) + val = 2; + else + val = 1; + + return val << IDECFG_WST_SHIFT; +} + +static void ep93xx_pata_enable_pio(void __iomem *base, int pio_mode) +{ + writel(IDECFG_IDEEN | IDECFG_PIO | + ep93xx_pata_get_wst(pio_mode) | + (pio_mode << IDECFG_MODE_SHIFT), base + IDECFG); +} + +/* + * Based on delay loop found in mach-pxa/mp900.c. + * + * Single iteration should take 5 cpu cycles. This is 25ns assuming the + * fastest ep93xx cpu speed (200MHz) and is better optimized for PIO4 timings + * than eg. 20ns. + */ +static void ep93xx_pata_delay(unsigned long count) +{ + __asm__ volatile ( + "0:\n" + "mov r0, r0\n" + "subs %0, %1, #1\n" + "bge 0b\n" + : "=r" (count) + : "0" (count) + ); +} + +static unsigned long ep93xx_pata_wait_for_iordy(void __iomem *base, + unsigned long t2) +{ + /* + * According to ATA specification, IORDY pin can be first sampled + * tA = 35ns after activation of DIOR-/DIOW-. Maximum IORDY pulse + * width is tB = 1250ns. + * + * We are already t2 delay loop iterations after activation of + * DIOR-/DIOW-, so we set timeout to (1250 + 35) / 25 - t2 additional + * delay loop iterations. + */ + unsigned long start = (1250 + 35) / 25 - t2; + unsigned long counter = start; + + while (!ep93xx_pata_check_iordy(base) && counter--) + ep93xx_pata_delay(1); + return start - counter; +} + +/* common part at start of ep93xx_pata_read/write() */ +static void ep93xx_pata_rw_begin(void __iomem *base, unsigned long addr, + unsigned long t1) +{ + writel(IDECTRL_DIOWN | IDECTRL_DIORN | addr, base + IDECTRL); + ep93xx_pata_delay(t1); +} + +/* common part at end of ep93xx_pata_read/write() */ +static void ep93xx_pata_rw_end(void __iomem *base, unsigned long addr, + bool iordy, unsigned long t0, unsigned long t2, + unsigned long t2i) +{ + ep93xx_pata_delay(t2); + /* lengthen t2 if needed */ + if (iordy) + t2 += ep93xx_pata_wait_for_iordy(base, t2); + writel(IDECTRL_DIOWN | IDECTRL_DIORN | addr, base + IDECTRL); + if (t0 > t2 && t0 - t2 > t2i) + ep93xx_pata_delay(t0 - t2); + else + ep93xx_pata_delay(t2i); +} + +static u16 ep93xx_pata_read(struct ep93xx_pata_data *drv_data, + unsigned long addr, + bool reg) +{ + void __iomem *base = drv_data->ide_base; + const struct ata_timing *t = &drv_data->t; + unsigned long t0 = reg ? t->cyc8b : t->cycle; + unsigned long t2 = reg ? t->act8b : t->active; + unsigned long t2i = reg ? t->rec8b : t->recover; + + ep93xx_pata_rw_begin(base, addr, t->setup); + writel(IDECTRL_DIOWN | addr, base + IDECTRL); + /* + * The IDEDATAIN register is loaded from the DD pins at the positive + * edge of the DIORN signal. (EP93xx UG p27-14) + */ + ep93xx_pata_rw_end(base, addr, drv_data->iordy, t0, t2, t2i); + return readl(base + IDEDATAIN); +} + +/* IDE register read */ +static u16 ep93xx_pata_read_reg(struct ep93xx_pata_data *drv_data, + unsigned long addr) +{ + return ep93xx_pata_read(drv_data, addr, true); +} + +/* PIO data read */ +static u16 ep93xx_pata_read_data(struct ep93xx_pata_data *drv_data, + unsigned long addr) +{ + return ep93xx_pata_read(drv_data, addr, false); +} + +static void ep93xx_pata_write(struct ep93xx_pata_data *drv_data, + u16 value, unsigned long addr, + bool reg) +{ + void __iomem *base = drv_data->ide_base; + const struct ata_timing *t = &drv_data->t; + unsigned long t0 = reg ? t->cyc8b : t->cycle; + unsigned long t2 = reg ? t->act8b : t->active; + unsigned long t2i = reg ? t->rec8b : t->recover; + + ep93xx_pata_rw_begin(base, addr, t->setup); + /* + * Value from IDEDATAOUT register is driven onto the DD pins when + * DIOWN is low. (EP93xx UG p27-13) + */ + writel(value, base + IDEDATAOUT); + writel(IDECTRL_DIORN | addr, base + IDECTRL); + ep93xx_pata_rw_end(base, addr, drv_data->iordy, t0, t2, t2i); +} + +/* IDE register write */ +static void ep93xx_pata_write_reg(struct ep93xx_pata_data *drv_data, + u16 value, unsigned long addr) +{ + ep93xx_pata_write(drv_data, value, addr, true); +} + +/* PIO data write */ +static void ep93xx_pata_write_data(struct ep93xx_pata_data *drv_data, + u16 value, unsigned long addr) +{ + ep93xx_pata_write(drv_data, value, addr, false); +} + +static void ep93xx_pata_set_piomode(struct ata_port *ap, + struct ata_device *adev) +{ + struct ep93xx_pata_data *drv_data = ap->host->private_data; + struct ata_device *pair = ata_dev_pair(adev); + /* + * Calculate timings for the delay loop, assuming ep93xx cpu speed + * is 200MHz (maximum possible for ep93xx). If actual cpu speed is + * slower, we will wait a bit longer in each delay. + * Additional division of cpu speed by 5, because single iteration + * of our delay loop takes 5 cpu cycles (25ns). + */ + unsigned long T = 1000000 / (200 / 5); + + ata_timing_compute(adev, adev->pio_mode, &drv_data->t, T, 0); + if (pair && pair->pio_mode) { + struct ata_timing t; + ata_timing_compute(pair, pair->pio_mode, &t, T, 0); + ata_timing_merge(&t, &drv_data->t, &drv_data->t, + ATA_TIMING_SETUP | ATA_TIMING_8BIT); + } + drv_data->iordy = ata_pio_need_iordy(adev); + + ep93xx_pata_enable_pio(drv_data->ide_base, + adev->pio_mode - XFER_PIO_0); +} + +/* Note: original code is ata_sff_check_status */ +static u8 ep93xx_pata_check_status(struct ata_port *ap) +{ + struct ep93xx_pata_data *drv_data = ap->host->private_data; + + return ep93xx_pata_read_reg(drv_data, IDECTRL_ADDR_STATUS); +} + +static u8 ep93xx_pata_check_altstatus(struct ata_port *ap) +{ + struct ep93xx_pata_data *drv_data = ap->host->private_data; + + return ep93xx_pata_read_reg(drv_data, IDECTRL_ADDR_ALTSTATUS); +} + +/* Note: original code is ata_sff_tf_load */ +static void ep93xx_pata_tf_load(struct ata_port *ap, + const struct ata_taskfile *tf) +{ + struct ep93xx_pata_data *drv_data = ap->host->private_data; + unsigned int is_addr = tf->flags & ATA_TFLAG_ISADDR; + + if (tf->ctl != ap->last_ctl) { + ep93xx_pata_write_reg(drv_data, tf->ctl, IDECTRL_ADDR_CTL); + ap->last_ctl = tf->ctl; + ata_wait_idle(ap); + } + + if (is_addr && (tf->flags & ATA_TFLAG_LBA48)) { + ep93xx_pata_write_reg(drv_data, tf->hob_feature, + IDECTRL_ADDR_FEATURE); + ep93xx_pata_write_reg(drv_data, tf->hob_nsect, + IDECTRL_ADDR_NSECT); + ep93xx_pata_write_reg(drv_data, tf->hob_lbal, + IDECTRL_ADDR_LBAL); + ep93xx_pata_write_reg(drv_data, tf->hob_lbam, + IDECTRL_ADDR_LBAM); + ep93xx_pata_write_reg(drv_data, tf->hob_lbah, + IDECTRL_ADDR_LBAH); + } + + if (is_addr) { + ep93xx_pata_write_reg(drv_data, tf->feature, + IDECTRL_ADDR_FEATURE); + ep93xx_pata_write_reg(drv_data, tf->nsect, IDECTRL_ADDR_NSECT); + ep93xx_pata_write_reg(drv_data, tf->lbal, IDECTRL_ADDR_LBAL); + ep93xx_pata_write_reg(drv_data, tf->lbam, IDECTRL_ADDR_LBAM); + ep93xx_pata_write_reg(drv_data, tf->lbah, IDECTRL_ADDR_LBAH); + } + + if (tf->flags & ATA_TFLAG_DEVICE) + ep93xx_pata_write_reg(drv_data, tf->device, + IDECTRL_ADDR_DEVICE); + + ata_wait_idle(ap); +} + +/* Note: original code is ata_sff_tf_read */ +static void ep93xx_pata_tf_read(struct ata_port *ap, struct ata_taskfile *tf) +{ + struct ep93xx_pata_data *drv_data = ap->host->private_data; + + tf->command = ep93xx_pata_check_status(ap); + tf->feature = ep93xx_pata_read_reg(drv_data, IDECTRL_ADDR_FEATURE); + tf->nsect = ep93xx_pata_read_reg(drv_data, IDECTRL_ADDR_NSECT); + tf->lbal = ep93xx_pata_read_reg(drv_data, IDECTRL_ADDR_LBAL); + tf->lbam = ep93xx_pata_read_reg(drv_data, IDECTRL_ADDR_LBAM); + tf->lbah = ep93xx_pata_read_reg(drv_data, IDECTRL_ADDR_LBAH); + tf->device = ep93xx_pata_read_reg(drv_data, IDECTRL_ADDR_DEVICE); + + if (tf->flags & ATA_TFLAG_LBA48) { + ep93xx_pata_write_reg(drv_data, tf->ctl | ATA_HOB, + IDECTRL_ADDR_CTL); + tf->hob_feature = ep93xx_pata_read_reg(drv_data, + IDECTRL_ADDR_FEATURE); + tf->hob_nsect = ep93xx_pata_read_reg(drv_data, + IDECTRL_ADDR_NSECT); + tf->hob_lbal = ep93xx_pata_read_reg(drv_data, + IDECTRL_ADDR_LBAL); + tf->hob_lbam = ep93xx_pata_read_reg(drv_data, + IDECTRL_ADDR_LBAM); + tf->hob_lbah = ep93xx_pata_read_reg(drv_data, + IDECTRL_ADDR_LBAH); + ep93xx_pata_write_reg(drv_data, tf->ctl, IDECTRL_ADDR_CTL); + ap->last_ctl = tf->ctl; + } +} + +/* Note: original code is ata_sff_exec_command */ +static void ep93xx_pata_exec_command(struct ata_port *ap, + const struct ata_taskfile *tf) +{ + struct ep93xx_pata_data *drv_data = ap->host->private_data; + + ep93xx_pata_write_reg(drv_data, tf->command, + IDECTRL_ADDR_COMMAND); + ata_sff_pause(ap); +} + +/* Note: original code is ata_sff_dev_select */ +static void ep93xx_pata_dev_select(struct ata_port *ap, unsigned int device) +{ + struct ep93xx_pata_data *drv_data = ap->host->private_data; + u8 tmp = ATA_DEVICE_OBS; + + if (device != 0) + tmp |= ATA_DEV1; + + ep93xx_pata_write_reg(drv_data, tmp, IDECTRL_ADDR_DEVICE); + ata_sff_pause(ap); /* needed; also flushes, for mmio */ +} + +/* Note: original code is ata_sff_set_devctl */ +static void ep93xx_pata_set_devctl(struct ata_port *ap, u8 ctl) +{ + struct ep93xx_pata_data *drv_data = ap->host->private_data; + + ep93xx_pata_write_reg(drv_data, ctl, IDECTRL_ADDR_CTL); +} + +/* Note: original code is ata_sff_data_xfer */ +static unsigned int ep93xx_pata_data_xfer(struct ata_device *adev, + unsigned char *buf, + unsigned int buflen, int rw) +{ + struct ata_port *ap = adev->link->ap; + struct ep93xx_pata_data *drv_data = ap->host->private_data; + u16 *data = (u16 *)buf; + unsigned int words = buflen >> 1; + + /* Transfer multiple of 2 bytes */ + while (words--) + if (rw == READ) + *data++ = cpu_to_le16( + ep93xx_pata_read_data( + drv_data, IDECTRL_ADDR_DATA)); + else + ep93xx_pata_write_data(drv_data, le16_to_cpu(*data++), + IDECTRL_ADDR_DATA); + + /* Transfer trailing 1 byte, if any. */ + if (unlikely(buflen & 0x01)) { + unsigned char pad[2] = { }; + + buf += buflen - 1; + + if (rw == READ) { + *pad = cpu_to_le16( + ep93xx_pata_read_data( + drv_data, IDECTRL_ADDR_DATA)); + *buf = pad[0]; + } else { + pad[0] = *buf; + ep93xx_pata_write_data(drv_data, le16_to_cpu(*pad), + IDECTRL_ADDR_DATA); + } + words++; + } + + return words << 1; +} + +/* Note: original code is ata_devchk */ +static bool ep93xx_pata_device_is_present(struct ata_port *ap, + unsigned int device) +{ + struct ep93xx_pata_data *drv_data = ap->host->private_data; + u8 nsect, lbal; + + ap->ops->sff_dev_select(ap, device); + + ep93xx_pata_write_reg(drv_data, 0x55, IDECTRL_ADDR_NSECT); + ep93xx_pata_write_reg(drv_data, 0xaa, IDECTRL_ADDR_LBAL); + + ep93xx_pata_write_reg(drv_data, 0xaa, IDECTRL_ADDR_NSECT); + ep93xx_pata_write_reg(drv_data, 0x55, IDECTRL_ADDR_LBAL); + + ep93xx_pata_write_reg(drv_data, 0x55, IDECTRL_ADDR_NSECT); + ep93xx_pata_write_reg(drv_data, 0xaa, IDECTRL_ADDR_LBAL); + + nsect = ep93xx_pata_read_reg(drv_data, IDECTRL_ADDR_NSECT); + lbal = ep93xx_pata_read_reg(drv_data, IDECTRL_ADDR_LBAL); + + if ((nsect == 0x55) && (lbal == 0xaa)) + return true; + + return false; +} + +/* Note: original code is ata_sff_wait_after_reset */ +static int ep93xx_pata_wait_after_reset(struct ata_link *link, + unsigned int devmask, + unsigned long deadline) +{ + struct ata_port *ap = link->ap; + struct ep93xx_pata_data *drv_data = ap->host->private_data; + unsigned int dev0 = devmask & (1 << 0); + unsigned int dev1 = devmask & (1 << 1); + int rc, ret = 0; + + ata_msleep(ap, ATA_WAIT_AFTER_RESET); + + /* always check readiness of the master device */ + rc = ata_sff_wait_ready(link, deadline); + /* + * -ENODEV means the odd clown forgot the D7 pulldown resistor + * and TF status is 0xff, bail out on it too. + */ + if (rc) + return rc; + + /* + * if device 1 was found in ata_devchk, wait for register + * access briefly, then wait for BSY to clear. + */ + if (dev1) { + int i; + + ap->ops->sff_dev_select(ap, 1); + + /* + * Wait for register access. Some ATAPI devices fail + * to set nsect/lbal after reset, so don't waste too + * much time on it. We're gonna wait for !BSY anyway. + */ + for (i = 0; i < 2; i++) { + u8 nsect, lbal; + + nsect = ep93xx_pata_read_reg(drv_data, + IDECTRL_ADDR_NSECT); + lbal = ep93xx_pata_read_reg(drv_data, + IDECTRL_ADDR_LBAL); + if (nsect == 1 && lbal == 1) + break; + msleep(50); /* give drive a breather */ + } + + rc = ata_sff_wait_ready(link, deadline); + if (rc) { + if (rc != -ENODEV) + return rc; + ret = rc; + } + } + /* is all this really necessary? */ + ap->ops->sff_dev_select(ap, 0); + if (dev1) + ap->ops->sff_dev_select(ap, 1); + if (dev0) + ap->ops->sff_dev_select(ap, 0); + + return ret; +} + +/* Note: original code is ata_bus_softreset */ +static int ep93xx_pata_bus_softreset(struct ata_port *ap, unsigned int devmask, + unsigned long deadline) +{ + struct ep93xx_pata_data *drv_data = ap->host->private_data; + + ep93xx_pata_write_reg(drv_data, ap->ctl, IDECTRL_ADDR_CTL); + udelay(20); /* FIXME: flush */ + ep93xx_pata_write_reg(drv_data, ap->ctl | ATA_SRST, IDECTRL_ADDR_CTL); + udelay(20); /* FIXME: flush */ + ep93xx_pata_write_reg(drv_data, ap->ctl, IDECTRL_ADDR_CTL); + ap->last_ctl = ap->ctl; + + return ep93xx_pata_wait_after_reset(&ap->link, devmask, deadline); +} + +static void ep93xx_pata_release_dma(struct ep93xx_pata_data *drv_data) +{ + if (drv_data->dma_rx_channel) { + dma_release_channel(drv_data->dma_rx_channel); + drv_data->dma_rx_channel = NULL; + } + if (drv_data->dma_tx_channel) { + dma_release_channel(drv_data->dma_tx_channel); + drv_data->dma_tx_channel = NULL; + } +} + +static bool ep93xx_pata_dma_filter(struct dma_chan *chan, void *filter_param) +{ + if (ep93xx_dma_chan_is_m2p(chan)) + return false; + + chan->private = filter_param; + return true; +} + +static void ep93xx_pata_dma_init(struct ep93xx_pata_data *drv_data) +{ + const struct platform_device *pdev = drv_data->pdev; + dma_cap_mask_t mask; + struct dma_slave_config conf; + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + /* + * Request two channels for IDE. Another possibility would be + * to request only one channel, and reprogram it's direction at + * start of new transfer. + */ + drv_data->dma_rx_data.port = EP93XX_DMA_IDE; + drv_data->dma_rx_data.direction = DMA_FROM_DEVICE; + drv_data->dma_rx_data.name = "ep93xx-pata-rx"; + drv_data->dma_rx_channel = dma_request_channel(mask, + ep93xx_pata_dma_filter, &drv_data->dma_rx_data); + if (!drv_data->dma_rx_channel) + return; + + drv_data->dma_tx_data.port = EP93XX_DMA_IDE; + drv_data->dma_tx_data.direction = DMA_TO_DEVICE; + drv_data->dma_tx_data.name = "ep93xx-pata-tx"; + drv_data->dma_tx_channel = dma_request_channel(mask, + ep93xx_pata_dma_filter, &drv_data->dma_tx_data); + if (!drv_data->dma_tx_channel) { + dma_release_channel(drv_data->dma_rx_channel); + return; + } + + /* Configure receive channel direction and source address */ + memset(&conf, 0, sizeof(conf)); + conf.direction = DMA_FROM_DEVICE; + conf.src_addr = drv_data->udma_in_phys; + conf.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + if (dmaengine_slave_config(drv_data->dma_rx_channel, &conf)) { + dev_err(&pdev->dev, "failed to configure rx dma channel\n"); + ep93xx_pata_release_dma(drv_data); + return; + } + + /* Configure transmit channel direction and destination address */ + memset(&conf, 0, sizeof(conf)); + conf.direction = DMA_TO_DEVICE; + conf.dst_addr = drv_data->udma_out_phys; + conf.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + if (dmaengine_slave_config(drv_data->dma_tx_channel, &conf)) { + dev_err(&pdev->dev, "failed to configure tx dma channel\n"); + ep93xx_pata_release_dma(drv_data); + } +} + +static void ep93xx_pata_dma_start(struct ata_queued_cmd *qc) +{ + struct dma_async_tx_descriptor *txd; + struct ep93xx_pata_data *drv_data = qc->ap->host->private_data; + void __iomem *base = drv_data->ide_base; + struct ata_device *adev = qc->dev; + u32 v = qc->dma_dir == DMA_TO_DEVICE ? IDEUDMAOP_RWOP : 0; + struct dma_chan *channel = qc->dma_dir == DMA_TO_DEVICE + ? drv_data->dma_tx_channel : drv_data->dma_rx_channel; + + txd = channel->device->device_prep_slave_sg(channel, qc->sg, + qc->n_elem, qc->dma_dir, DMA_CTRL_ACK, NULL); + if (!txd) { + dev_err(qc->ap->dev, "failed to prepare slave for sg dma\n"); + return; + } + txd->callback = NULL; + txd->callback_param = NULL; + + if (dmaengine_submit(txd) < 0) { + dev_err(qc->ap->dev, "failed to submit dma transfer\n"); + return; + } + dma_async_issue_pending(channel); + + /* + * When enabling UDMA operation, IDEUDMAOP register needs to be + * programmed in three step sequence: + * 1) set or clear the RWOP bit, + * 2) perform dummy read of the register, + * 3) set the UEN bit. + */ + writel(v, base + IDEUDMAOP); + readl(base + IDEUDMAOP); + writel(v | IDEUDMAOP_UEN, base + IDEUDMAOP); + + writel(IDECFG_IDEEN | IDECFG_UDMA | + ((adev->xfer_mode - XFER_UDMA_0) << IDECFG_MODE_SHIFT), + base + IDECFG); +} + +static void ep93xx_pata_dma_stop(struct ata_queued_cmd *qc) +{ + struct ep93xx_pata_data *drv_data = qc->ap->host->private_data; + void __iomem *base = drv_data->ide_base; + + /* terminate all dma transfers, if not yet finished */ + dmaengine_terminate_all(drv_data->dma_rx_channel); + dmaengine_terminate_all(drv_data->dma_tx_channel); + + /* + * To properly stop IDE-DMA, IDEUDMAOP register must to be cleared + * and IDECTRL register must be set to default value. + */ + writel(0, base + IDEUDMAOP); + writel(readl(base + IDECTRL) | IDECTRL_DIOWN | IDECTRL_DIORN | + IDECTRL_CS0N | IDECTRL_CS1N, base + IDECTRL); + + ep93xx_pata_enable_pio(drv_data->ide_base, + qc->dev->pio_mode - XFER_PIO_0); + + ata_sff_dma_pause(qc->ap); +} + +static void ep93xx_pata_dma_setup(struct ata_queued_cmd *qc) +{ + qc->ap->ops->sff_exec_command(qc->ap, &qc->tf); +} + +static u8 ep93xx_pata_dma_status(struct ata_port *ap) +{ + struct ep93xx_pata_data *drv_data = ap->host->private_data; + u32 val = readl(drv_data->ide_base + IDEUDMASTS); + + /* + * UDMA Status Register bits: + * + * DMAIDE - DMA request signal from UDMA state machine, + * INTIDE - INT line generated by UDMA because of errors in the + * state machine, + * SBUSY - UDMA state machine busy, not in idle state, + * NDO - error for data-out not completed, + * NDI - error for data-in not completed, + * N4X - error for data transferred not multiplies of four + * 32-bit words. + * (EP93xx UG p27-17) + */ + if (val & IDEUDMASTS_NDO || val & IDEUDMASTS_NDI || + val & IDEUDMASTS_N4X || val & IDEUDMASTS_INTIDE) + return ATA_DMA_ERR; + + /* read INTRQ (INT[3]) pin input state */ + if (readl(drv_data->ide_base + IDECTRL) & IDECTRL_INTRQ) + return ATA_DMA_INTR; + + if (val & IDEUDMASTS_SBUSY || val & IDEUDMASTS_DMAIDE) + return ATA_DMA_ACTIVE; + + return 0; +} + +/* Note: original code is ata_sff_softreset */ +static int ep93xx_pata_softreset(struct ata_link *al, unsigned int *classes, + unsigned long deadline) +{ + struct ata_port *ap = al->ap; + unsigned int slave_possible = ap->flags & ATA_FLAG_SLAVE_POSS; + unsigned int devmask = 0; + int rc; + u8 err; + + /* determine if device 0/1 are present */ + if (ep93xx_pata_device_is_present(ap, 0)) + devmask |= (1 << 0); + if (slave_possible && ep93xx_pata_device_is_present(ap, 1)) + devmask |= (1 << 1); + + /* select device 0 again */ + ap->ops->sff_dev_select(al->ap, 0); + + /* issue bus reset */ + rc = ep93xx_pata_bus_softreset(ap, devmask, deadline); + /* if link is ocuppied, -ENODEV too is an error */ + if (rc && (rc != -ENODEV || sata_scr_valid(al))) { + ata_link_printk(al, KERN_ERR, "SRST failed (errno=%d)\n", + rc); + return rc; + } + + /* determine by signature whether we have ATA or ATAPI devices */ + classes[0] = ata_sff_dev_classify(&al->device[0], devmask & (1 << 0), + &err); + if (slave_possible && err != 0x81) + classes[1] = ata_sff_dev_classify(&al->device[1], + devmask & (1 << 1), &err); + + return 0; +} + +/* Note: original code is ata_sff_drain_fifo */ +static void ep93xx_pata_drain_fifo(struct ata_queued_cmd *qc) +{ + int count; + struct ata_port *ap; + struct ep93xx_pata_data *drv_data; + + /* We only need to flush incoming data when a command was running */ + if (qc == NULL || qc->dma_dir == DMA_TO_DEVICE) + return; + + ap = qc->ap; + drv_data = ap->host->private_data; + /* Drain up to 64K of data before we give up this recovery method */ + for (count = 0; (ap->ops->sff_check_status(ap) & ATA_DRQ) + && count < 65536; count += 2) + ep93xx_pata_read_reg(drv_data, IDECTRL_ADDR_DATA); + + /* Can become DEBUG later */ + if (count) + ata_port_printk(ap, KERN_DEBUG, + "drained %d bytes to clear DRQ.\n", count); + +} + +static int ep93xx_pata_port_start(struct ata_port *ap) +{ + struct ep93xx_pata_data *drv_data = ap->host->private_data; + + /* + * Set timings to safe values at startup (= number of ns from ATA + * specification), we'll switch to properly calculated values later. + */ + drv_data->t = *ata_timing_find_mode(XFER_PIO_0); + return 0; +} + +static struct scsi_host_template ep93xx_pata_sht = { + ATA_BASE_SHT(DRV_NAME), + /* ep93xx dma implementation limit */ + .sg_tablesize = 32, + /* ep93xx dma can't transfer 65536 bytes at once */ + .dma_boundary = 0x7fff, +}; + +static struct ata_port_operations ep93xx_pata_port_ops = { + .inherits = &ata_bmdma_port_ops, + + .qc_prep = ata_noop_qc_prep, + + .softreset = ep93xx_pata_softreset, + .hardreset = ATA_OP_NULL, + + .sff_dev_select = ep93xx_pata_dev_select, + .sff_set_devctl = ep93xx_pata_set_devctl, + .sff_check_status = ep93xx_pata_check_status, + .sff_check_altstatus = ep93xx_pata_check_altstatus, + .sff_tf_load = ep93xx_pata_tf_load, + .sff_tf_read = ep93xx_pata_tf_read, + .sff_exec_command = ep93xx_pata_exec_command, + .sff_data_xfer = ep93xx_pata_data_xfer, + .sff_drain_fifo = ep93xx_pata_drain_fifo, + .sff_irq_clear = ATA_OP_NULL, + + .set_piomode = ep93xx_pata_set_piomode, + + .bmdma_setup = ep93xx_pata_dma_setup, + .bmdma_start = ep93xx_pata_dma_start, + .bmdma_stop = ep93xx_pata_dma_stop, + .bmdma_status = ep93xx_pata_dma_status, + + .cable_detect = ata_cable_unknown, + .port_start = ep93xx_pata_port_start, +}; + +static int __devinit ep93xx_pata_probe(struct platform_device *pdev) +{ + struct ep93xx_pata_data *drv_data; + struct ata_host *host; + struct ata_port *ap; + unsigned int irq; + struct resource *mem_res; + void __iomem *ide_base; + int err; + + err = ep93xx_ide_acquire_gpio(pdev); + if (err) + return err; + + /* INT[3] (IRQ_EP93XX_EXT3) line connected as pull down */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + err = -ENXIO; + goto err_rel_gpio; + } + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem_res) { + err = -ENXIO; + goto err_rel_gpio; + } + + ide_base = devm_request_and_ioremap(&pdev->dev, mem_res); + if (!ide_base) { + err = -ENXIO; + goto err_rel_gpio; + } + + drv_data = devm_kzalloc(&pdev->dev, sizeof(*drv_data), GFP_KERNEL); + if (!drv_data) { + err = -ENXIO; + goto err_rel_gpio; + } + + platform_set_drvdata(pdev, drv_data); + drv_data->pdev = pdev; + drv_data->ide_base = ide_base; + drv_data->udma_in_phys = mem_res->start + IDEUDMADATAIN; + drv_data->udma_out_phys = mem_res->start + IDEUDMADATAOUT; + ep93xx_pata_dma_init(drv_data); + + /* allocate host */ + host = ata_host_alloc(&pdev->dev, 1); + if (!host) { + err = -ENXIO; + goto err_rel_dma; + } + + ep93xx_pata_clear_regs(ide_base); + + host->private_data = drv_data; + + ap = host->ports[0]; + ap->dev = &pdev->dev; + ap->ops = &ep93xx_pata_port_ops; + ap->flags |= ATA_FLAG_SLAVE_POSS; + ap->pio_mask = ATA_PIO4; + + /* + * Maximum UDMA modes: + * EP931x rev.E0 - UDMA2 + * EP931x rev.E1 - UDMA3 + * EP931x rev.E2 - UDMA4 + * + * MWDMA support was removed from EP931x rev.E2, + * so this driver supports only UDMA modes. + */ + if (drv_data->dma_rx_channel && drv_data->dma_tx_channel) { + int chip_rev = ep93xx_chip_revision(); + + if (chip_rev == EP93XX_CHIP_REV_E1) + ap->udma_mask = ATA_UDMA3; + else if (chip_rev == EP93XX_CHIP_REV_E2) + ap->udma_mask = ATA_UDMA4; + else + ap->udma_mask = ATA_UDMA2; + } + + /* defaults, pio 0 */ + ep93xx_pata_enable_pio(ide_base, 0); + + dev_info(&pdev->dev, "version " DRV_VERSION "\n"); + + /* activate host */ + err = ata_host_activate(host, irq, ata_bmdma_interrupt, 0, + &ep93xx_pata_sht); + if (err == 0) + return 0; + +err_rel_dma: + ep93xx_pata_release_dma(drv_data); +err_rel_gpio: + ep93xx_ide_release_gpio(pdev); + return err; +} + +static int __devexit ep93xx_pata_remove(struct platform_device *pdev) +{ + struct ata_host *host = platform_get_drvdata(pdev); + struct ep93xx_pata_data *drv_data = host->private_data; + + ata_host_detach(host); + ep93xx_pata_release_dma(drv_data); + ep93xx_pata_clear_regs(drv_data->ide_base); + ep93xx_ide_release_gpio(pdev); + return 0; +} + +static struct platform_driver ep93xx_pata_platform_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, + .probe = ep93xx_pata_probe, + .remove = __devexit_p(ep93xx_pata_remove), +}; + +module_platform_driver(ep93xx_pata_platform_driver); + +MODULE_AUTHOR("Alessandro Zummo, Lennert Buytenhek, Joao Ramos, " + "Bartlomiej Zolnierkiewicz, Rafal Prylowski"); +MODULE_DESCRIPTION("low-level driver for cirrus ep93xx IDE controller"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRV_VERSION); +MODULE_ALIAS("platform:pata_ep93xx"); diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 73ce9fbe9839..83aa694a8efe 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -11,6 +11,7 @@ #include <linux/io.h> #include <linux/pm_runtime.h> #include <linux/pm_domain.h> +#include <linux/pm_qos.h> #include <linux/slab.h> #include <linux/err.h> #include <linux/sched.h> @@ -38,11 +39,13 @@ ktime_t __start = ktime_get(); \ type __retval = GENPD_DEV_CALLBACK(genpd, type, callback, dev); \ s64 __elapsed = ktime_to_ns(ktime_sub(ktime_get(), __start)); \ - struct generic_pm_domain_data *__gpd_data = dev_gpd_data(dev); \ - if (__elapsed > __gpd_data->td.field) { \ - __gpd_data->td.field = __elapsed; \ + struct gpd_timing_data *__td = &dev_gpd_data(dev)->td; \ + if (!__retval && __elapsed > __td->field) { \ + __td->field = __elapsed; \ dev_warn(dev, name " latency exceeded, new value %lld ns\n", \ __elapsed); \ + genpd->max_off_time_changed = true; \ + __td->constraint_changed = true; \ } \ __retval; \ }) @@ -211,6 +214,7 @@ int __pm_genpd_poweron(struct generic_pm_domain *genpd) elapsed_ns = ktime_to_ns(ktime_sub(ktime_get(), time_start)); if (elapsed_ns > genpd->power_on_latency_ns) { genpd->power_on_latency_ns = elapsed_ns; + genpd->max_off_time_changed = true; if (genpd->name) pr_warning("%s: Power-on latency exceeded, " "new value %lld ns\n", genpd->name, @@ -247,6 +251,53 @@ int pm_genpd_poweron(struct generic_pm_domain *genpd) #ifdef CONFIG_PM_RUNTIME +static int genpd_dev_pm_qos_notifier(struct notifier_block *nb, + unsigned long val, void *ptr) +{ + struct generic_pm_domain_data *gpd_data; + struct device *dev; + + gpd_data = container_of(nb, struct generic_pm_domain_data, nb); + + mutex_lock(&gpd_data->lock); + dev = gpd_data->base.dev; + if (!dev) { + mutex_unlock(&gpd_data->lock); + return NOTIFY_DONE; + } + mutex_unlock(&gpd_data->lock); + + for (;;) { + struct generic_pm_domain *genpd; + struct pm_domain_data *pdd; + + spin_lock_irq(&dev->power.lock); + + pdd = dev->power.subsys_data ? + dev->power.subsys_data->domain_data : NULL; + if (pdd) { + to_gpd_data(pdd)->td.constraint_changed = true; + genpd = dev_to_genpd(dev); + } else { + genpd = ERR_PTR(-ENODATA); + } + + spin_unlock_irq(&dev->power.lock); + + if (!IS_ERR(genpd)) { + mutex_lock(&genpd->lock); + genpd->max_off_time_changed = true; + mutex_unlock(&genpd->lock); + } + + dev = dev->parent; + if (!dev || dev->power.ignore_children) + break; + } + + return NOTIFY_DONE; +} + /** * __pm_genpd_save_device - Save the pre-suspend state of a device. * @pdd: Domain data of the device to save the state of. @@ -435,6 +486,7 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) elapsed_ns = ktime_to_ns(ktime_sub(ktime_get(), time_start)); if (elapsed_ns > genpd->power_off_latency_ns) { genpd->power_off_latency_ns = elapsed_ns; + genpd->max_off_time_changed = true; if (genpd->name) pr_warning("%s: Power-off latency exceeded, " "new value %lld ns\n", genpd->name, @@ -443,17 +495,6 @@ static int pm_genpd_poweroff(struct generic_pm_domain *genpd) } genpd->status = GPD_STATE_POWER_OFF; - genpd->power_off_time = ktime_get(); - - /* Update PM QoS information for devices in the domain. */ - list_for_each_entry_reverse(pdd, &genpd->dev_list, list_node) { - struct gpd_timing_data *td = &to_gpd_data(pdd)->td; - - pm_runtime_update_max_time_suspended(pdd->dev, - td->start_latency_ns + - td->restore_state_latency_ns + - genpd->power_on_latency_ns); - } list_for_each_entry(link, &genpd->slave_links, slave_node) { genpd_sd_counter_dec(link->master); @@ -514,9 +555,6 @@ static int pm_genpd_runtime_suspend(struct device *dev) if (ret) return ret; - pm_runtime_update_max_time_suspended(dev, - dev_gpd_data(dev)->td.start_latency_ns); - /* * If power.irq_safe is set, this routine will be run with interrupts * off, so it can't use mutexes. @@ -613,6 +651,12 @@ void pm_genpd_poweroff_unused(void) #else +static inline int genpd_dev_pm_qos_notifier(struct notifier_block *nb, + unsigned long val, void *ptr) +{ + return NOTIFY_DONE; +} + static inline void genpd_power_off_work_fn(struct work_struct *work) {} #define pm_genpd_runtime_suspend NULL @@ -1209,12 +1253,15 @@ int __pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev, if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(dev)) return -EINVAL; - genpd_acquire_lock(genpd); + gpd_data = kzalloc(sizeof(*gpd_data), GFP_KERNEL); + if (!gpd_data) + return -ENOMEM; - if (genpd->status == GPD_STATE_POWER_OFF) { - ret = -EINVAL; - goto out; - } + mutex_init(&gpd_data->lock); + gpd_data->nb.notifier_call = genpd_dev_pm_qos_notifier; + dev_pm_qos_add_notifier(dev, &gpd_data->nb); + + genpd_acquire_lock(genpd); if (genpd->prepared_count > 0) { ret = -EAGAIN; @@ -1227,26 +1274,35 @@ int __pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev, goto out; } - gpd_data = kzalloc(sizeof(*gpd_data), GFP_KERNEL); - if (!gpd_data) { - ret = -ENOMEM; - goto out; - } - genpd->device_count++; + genpd->max_off_time_changed = true; - dev->pm_domain = &genpd->domain; dev_pm_get_subsys_data(dev); + + mutex_lock(&gpd_data->lock); + spin_lock_irq(&dev->power.lock); + dev->pm_domain = &genpd->domain; dev->power.subsys_data->domain_data = &gpd_data->base; gpd_data->base.dev = dev; - gpd_data->need_restore = false; list_add_tail(&gpd_data->base.list_node, &genpd->dev_list); + gpd_data->need_restore = genpd->status == GPD_STATE_POWER_OFF; if (td) gpd_data->td = *td; + gpd_data->td.constraint_changed = true; + gpd_data->td.effective_constraint_ns = -1; + spin_unlock_irq(&dev->power.lock); + mutex_unlock(&gpd_data->lock); + + genpd_release_lock(genpd); + + return 0; + out: genpd_release_lock(genpd); + dev_pm_qos_remove_notifier(dev, &gpd_data->nb); + kfree(gpd_data); return ret; } @@ -1290,12 +1346,15 @@ int __pm_genpd_of_add_device(struct device_node *genpd_node, struct device *dev, int pm_genpd_remove_device(struct generic_pm_domain *genpd, struct device *dev) { + struct generic_pm_domain_data *gpd_data; struct pm_domain_data *pdd; - int ret = -EINVAL; + int ret = 0; dev_dbg(dev, "%s()\n", __func__); - if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(dev)) + if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(dev) + || IS_ERR_OR_NULL(dev->pm_domain) + || pd_to_genpd(dev->pm_domain) != genpd) return -EINVAL; genpd_acquire_lock(genpd); @@ -1305,21 +1364,27 @@ int pm_genpd_remove_device(struct generic_pm_domain *genpd, goto out; } - list_for_each_entry(pdd, &genpd->dev_list, list_node) { - if (pdd->dev != dev) - continue; + genpd->device_count--; + genpd->max_off_time_changed = true; - list_del_init(&pdd->list_node); - pdd->dev = NULL; - dev_pm_put_subsys_data(dev); - dev->pm_domain = NULL; - kfree(to_gpd_data(pdd)); + spin_lock_irq(&dev->power.lock); + dev->pm_domain = NULL; + pdd = dev->power.subsys_data->domain_data; + list_del_init(&pdd->list_node); + dev->power.subsys_data->domain_data = NULL; + spin_unlock_irq(&dev->power.lock); - genpd->device_count--; + gpd_data = to_gpd_data(pdd); + mutex_lock(&gpd_data->lock); + pdd->dev = NULL; + mutex_unlock(&gpd_data->lock); - ret = 0; - break; - } + genpd_release_lock(genpd); + + dev_pm_qos_remove_notifier(dev, &gpd_data->nb); + kfree(gpd_data); + dev_pm_put_subsys_data(dev); + return 0; out: genpd_release_lock(genpd); @@ -1348,6 +1413,26 @@ void pm_genpd_dev_always_on(struct device *dev, bool val) EXPORT_SYMBOL_GPL(pm_genpd_dev_always_on); /** + * pm_genpd_dev_need_restore - Set/unset the device's "need restore" flag. + * @dev: Device to set/unset the flag for. + * @val: The new value of the device's "need restore" flag. + */ +void pm_genpd_dev_need_restore(struct device *dev, bool val) +{ + struct pm_subsys_data *psd; + unsigned long flags; + + spin_lock_irqsave(&dev->power.lock, flags); + + psd = dev_to_psd(dev); + if (psd && psd->domain_data) + to_gpd_data(psd->domain_data)->need_restore = val; + + spin_unlock_irqrestore(&dev->power.lock, flags); +} +EXPORT_SYMBOL_GPL(pm_genpd_dev_need_restore); + +/** * pm_genpd_add_subdomain - Add a subdomain to an I/O PM domain. * @genpd: Master PM domain to add the subdomain to. * @subdomain: Subdomain to be added. @@ -1378,7 +1463,7 @@ int pm_genpd_add_subdomain(struct generic_pm_domain *genpd, goto out; } - list_for_each_entry(link, &genpd->slave_links, slave_node) { + list_for_each_entry(link, &genpd->master_links, master_node) { if (link->slave == subdomain && link->master == genpd) { ret = -EINVAL; goto out; @@ -1690,6 +1775,7 @@ void pm_genpd_init(struct generic_pm_domain *genpd, genpd->resume_count = 0; genpd->device_count = 0; genpd->max_off_time_ns = -1; + genpd->max_off_time_changed = true; genpd->domain.ops.runtime_suspend = pm_genpd_runtime_suspend; genpd->domain.ops.runtime_resume = pm_genpd_runtime_resume; genpd->domain.ops.runtime_idle = pm_generic_runtime_idle; diff --git a/drivers/base/power/domain_governor.c b/drivers/base/power/domain_governor.c index 66a265bf5867..28dee3053f1f 100644 --- a/drivers/base/power/domain_governor.c +++ b/drivers/base/power/domain_governor.c @@ -14,6 +14,31 @@ #ifdef CONFIG_PM_RUNTIME +static int dev_update_qos_constraint(struct device *dev, void *data) +{ + s64 *constraint_ns_p = data; + s32 constraint_ns = -1; + + if (dev->power.subsys_data && dev->power.subsys_data->domain_data) + constraint_ns = dev_gpd_data(dev)->td.effective_constraint_ns; + + if (constraint_ns < 0) { + constraint_ns = dev_pm_qos_read_value(dev); + constraint_ns *= NSEC_PER_USEC; + } + if (constraint_ns == 0) + return 0; + + /* + * constraint_ns cannot be negative here, because the device has been + * suspended. + */ + if (constraint_ns < *constraint_ns_p || *constraint_ns_p == 0) + *constraint_ns_p = constraint_ns; + + return 0; +} + /** * default_stop_ok - Default PM domain governor routine for stopping devices. * @dev: Device to check. @@ -21,14 +46,52 @@ bool default_stop_ok(struct device *dev) { struct gpd_timing_data *td = &dev_gpd_data(dev)->td; + unsigned long flags; + s64 constraint_ns; dev_dbg(dev, "%s()\n", __func__); - if (dev->power.max_time_suspended_ns < 0 || td->break_even_ns == 0) - return true; + spin_lock_irqsave(&dev->power.lock, flags); + + if (!td->constraint_changed) { + bool ret = td->cached_stop_ok; - return td->stop_latency_ns + td->start_latency_ns < td->break_even_ns - && td->break_even_ns < dev->power.max_time_suspended_ns; + spin_unlock_irqrestore(&dev->power.lock, flags); + return ret; + } + td->constraint_changed = false; + td->cached_stop_ok = false; + td->effective_constraint_ns = -1; + constraint_ns = __dev_pm_qos_read_value(dev); + + spin_unlock_irqrestore(&dev->power.lock, flags); + + if (constraint_ns < 0) + return false; + + constraint_ns *= NSEC_PER_USEC; + /* + * We can walk the children without any additional locking, because + * they all have been suspended at this point and their + * effective_constraint_ns fields won't be modified in parallel with us. + */ + if (!dev->power.ignore_children) + device_for_each_child(dev, &constraint_ns, + dev_update_qos_constraint); + + if (constraint_ns > 0) { + constraint_ns -= td->start_latency_ns; + if (constraint_ns == 0) + return false; + } + td->effective_constraint_ns = constraint_ns; + td->cached_stop_ok = constraint_ns > td->stop_latency_ns || + constraint_ns == 0; + /* + * The children have been suspended already, so we don't need to take + * their stop latencies into account here. + */ + return td->cached_stop_ok; } /** @@ -42,9 +105,27 @@ static bool default_power_down_ok(struct dev_pm_domain *pd) struct generic_pm_domain *genpd = pd_to_genpd(pd); struct gpd_link *link; struct pm_domain_data *pdd; - s64 min_dev_off_time_ns; + s64 min_off_time_ns; s64 off_on_time_ns; - ktime_t time_now = ktime_get(); + + if (genpd->max_off_time_changed) { + struct gpd_link *link; + + /* + * We have to invalidate the cached results for the masters, so + * use the observation that default_power_down_ok() is not + * going to be called for any master until this instance + * returns. + */ + list_for_each_entry(link, &genpd->slave_links, slave_node) + link->master->max_off_time_changed = true; + + genpd->max_off_time_changed = false; + genpd->cached_power_down_ok = false; + genpd->max_off_time_ns = -1; + } else { + return genpd->cached_power_down_ok; + } off_on_time_ns = genpd->power_off_latency_ns + genpd->power_on_latency_ns; @@ -61,6 +142,7 @@ static bool default_power_down_ok(struct dev_pm_domain *pd) to_gpd_data(pdd)->td.save_state_latency_ns; } + min_off_time_ns = -1; /* * Check if subdomains can be off for enough time. * @@ -73,8 +155,6 @@ static bool default_power_down_ok(struct dev_pm_domain *pd) if (sd_max_off_ns < 0) continue; - sd_max_off_ns -= ktime_to_ns(ktime_sub(time_now, - sd->power_off_time)); /* * Check if the subdomain is allowed to be off long enough for * the current domain to turn off and on (that's how much time @@ -82,60 +162,64 @@ static bool default_power_down_ok(struct dev_pm_domain *pd) */ if (sd_max_off_ns <= off_on_time_ns) return false; + + if (min_off_time_ns > sd_max_off_ns || min_off_time_ns < 0) + min_off_time_ns = sd_max_off_ns; } /* * Check if the devices in the domain can be off enough time. */ - min_dev_off_time_ns = -1; list_for_each_entry(pdd, &genpd->dev_list, list_node) { struct gpd_timing_data *td; - struct device *dev = pdd->dev; - s64 dev_off_time_ns; + s64 constraint_ns; - if (!dev->driver || dev->power.max_time_suspended_ns < 0) + if (!pdd->dev->driver) continue; + /* + * Check if the device is allowed to be off long enough for the + * domain to turn off and on (that's how much time it will + * have to wait worst case). + */ td = &to_gpd_data(pdd)->td; - dev_off_time_ns = dev->power.max_time_suspended_ns - - (td->start_latency_ns + td->restore_state_latency_ns + - ktime_to_ns(ktime_sub(time_now, - dev->power.suspend_time))); - if (dev_off_time_ns <= off_on_time_ns) - return false; - - if (min_dev_off_time_ns > dev_off_time_ns - || min_dev_off_time_ns < 0) - min_dev_off_time_ns = dev_off_time_ns; - } + constraint_ns = td->effective_constraint_ns; + /* default_stop_ok() need not be called before us. */ + if (constraint_ns < 0) { + constraint_ns = dev_pm_qos_read_value(pdd->dev); + constraint_ns *= NSEC_PER_USEC; + } + if (constraint_ns == 0) + continue; - if (min_dev_off_time_ns < 0) { /* - * There are no latency constraints, so the domain can spend - * arbitrary time in the "off" state. + * constraint_ns cannot be negative here, because the device has + * been suspended. */ - genpd->max_off_time_ns = -1; - return true; + constraint_ns -= td->restore_state_latency_ns; + if (constraint_ns <= off_on_time_ns) + return false; + + if (min_off_time_ns > constraint_ns || min_off_time_ns < 0) + min_off_time_ns = constraint_ns; } + genpd->cached_power_down_ok = true; + /* - * The difference between the computed minimum delta and the time needed - * to turn the domain on is the maximum theoretical time this domain can - * spend in the "off" state. + * If the computed minimum device off time is negative, there are no + * latency constraints, so the domain can spend arbitrary time in the + * "off" state. */ - min_dev_off_time_ns -= genpd->power_on_latency_ns; + if (min_off_time_ns < 0) + return true; /* - * If the difference between the computed minimum delta and the time - * needed to turn the domain off and back on on is smaller than the - * domain's power break even time, removing power from the domain is not - * worth it. + * The difference between the computed minimum subdomain or device off + * time and the time needed to turn the domain on is the maximum + * theoretical time this domain can spend in the "off" state. */ - if (genpd->break_even_ns > - min_dev_off_time_ns - genpd->power_off_latency_ns) - return false; - - genpd->max_off_time_ns = min_dev_off_time_ns; + genpd->max_off_time_ns = min_off_time_ns - genpd->power_on_latency_ns; return true; } diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index b462c0e341cb..e0fb5b0435a3 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -889,6 +889,11 @@ static int dpm_suspend_noirq(pm_message_t state) if (!list_empty(&dev->power.entry)) list_move(&dev->power.entry, &dpm_noirq_list); put_device(dev); + + if (pm_wakeup_pending()) { + error = -EBUSY; + break; + } } mutex_unlock(&dpm_list_mtx); if (error) @@ -962,6 +967,11 @@ static int dpm_suspend_late(pm_message_t state) if (!list_empty(&dev->power.entry)) list_move(&dev->power.entry, &dpm_late_early_list); put_device(dev); + + if (pm_wakeup_pending()) { + error = -EBUSY; + break; + } } mutex_unlock(&dpm_list_mtx); if (error) diff --git a/drivers/base/power/qos.c b/drivers/base/power/qos.c index 71855570922d..fd849a2c4fa8 100644 --- a/drivers/base/power/qos.c +++ b/drivers/base/power/qos.c @@ -352,21 +352,26 @@ EXPORT_SYMBOL_GPL(dev_pm_qos_remove_request); * * Will register the notifier into a notification chain that gets called * upon changes to the target value for the device. + * + * If the device's constraints object doesn't exist when this routine is called, + * it will be created (or error code will be returned if that fails). */ int dev_pm_qos_add_notifier(struct device *dev, struct notifier_block *notifier) { - int retval = 0; + int ret = 0; mutex_lock(&dev_pm_qos_mtx); - /* Silently return if the constraints object is not present. */ - if (dev->power.constraints) - retval = blocking_notifier_chain_register( - dev->power.constraints->notifiers, - notifier); + if (!dev->power.constraints) + ret = dev->power.power_state.event != PM_EVENT_INVALID ? + dev_pm_qos_constraints_allocate(dev) : -ENODEV; + + if (!ret) + ret = blocking_notifier_chain_register( + dev->power.constraints->notifiers, notifier); mutex_unlock(&dev_pm_qos_mtx); - return retval; + return ret; } EXPORT_SYMBOL_GPL(dev_pm_qos_add_notifier); diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index bd0f3949bcf9..59894873a3b3 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c @@ -282,47 +282,6 @@ static int rpm_callback(int (*cb)(struct device *), struct device *dev) return retval != -EACCES ? retval : -EIO; } -struct rpm_qos_data { - ktime_t time_now; - s64 constraint_ns; -}; - -/** - * rpm_update_qos_constraint - Update a given PM QoS constraint data. - * @dev: Device whose timing data to use. - * @data: PM QoS constraint data to update. - * - * Use the suspend timing data of @dev to update PM QoS constraint data pointed - * to by @data. - */ -static int rpm_update_qos_constraint(struct device *dev, void *data) -{ - struct rpm_qos_data *qos = data; - unsigned long flags; - s64 delta_ns; - int ret = 0; - - spin_lock_irqsave(&dev->power.lock, flags); - - if (dev->power.max_time_suspended_ns < 0) - goto out; - - delta_ns = dev->power.max_time_suspended_ns - - ktime_to_ns(ktime_sub(qos->time_now, dev->power.suspend_time)); - if (delta_ns <= 0) { - ret = -EBUSY; - goto out; - } - - if (qos->constraint_ns > delta_ns || qos->constraint_ns == 0) - qos->constraint_ns = delta_ns; - - out: - spin_unlock_irqrestore(&dev->power.lock, flags); - - return ret; -} - /** * rpm_suspend - Carry out runtime suspend of given device. * @dev: Device to suspend. @@ -349,7 +308,6 @@ static int rpm_suspend(struct device *dev, int rpmflags) { int (*callback)(struct device *); struct device *parent = NULL; - struct rpm_qos_data qos; int retval; trace_rpm_suspend(dev, rpmflags); @@ -445,38 +403,14 @@ static int rpm_suspend(struct device *dev, int rpmflags) goto out; } - qos.constraint_ns = __dev_pm_qos_read_value(dev); - if (qos.constraint_ns < 0) { - /* Negative constraint means "never suspend". */ + if (__dev_pm_qos_read_value(dev) < 0) { + /* Negative PM QoS constraint means "never suspend". */ retval = -EPERM; goto out; } - qos.constraint_ns *= NSEC_PER_USEC; - qos.time_now = ktime_get(); __update_runtime_status(dev, RPM_SUSPENDING); - if (!dev->power.ignore_children) { - if (dev->power.irq_safe) - spin_unlock(&dev->power.lock); - else - spin_unlock_irq(&dev->power.lock); - - retval = device_for_each_child(dev, &qos, - rpm_update_qos_constraint); - - if (dev->power.irq_safe) - spin_lock(&dev->power.lock); - else - spin_lock_irq(&dev->power.lock); - - if (retval) - goto fail; - } - - dev->power.suspend_time = qos.time_now; - dev->power.max_time_suspended_ns = qos.constraint_ns ? : -1; - if (dev->pm_domain) callback = dev->pm_domain->ops.runtime_suspend; else if (dev->type && dev->type->pm) @@ -529,8 +463,6 @@ static int rpm_suspend(struct device *dev, int rpmflags) fail: __update_runtime_status(dev, RPM_ACTIVE); - dev->power.suspend_time = ktime_set(0, 0); - dev->power.max_time_suspended_ns = -1; dev->power.deferred_resume = false; wake_up_all(&dev->power.wait_queue); @@ -704,9 +636,6 @@ static int rpm_resume(struct device *dev, int rpmflags) if (dev->power.no_callbacks) goto no_callback; /* Assume success. */ - dev->power.suspend_time = ktime_set(0, 0); - dev->power.max_time_suspended_ns = -1; - __update_runtime_status(dev, RPM_RESUMING); if (dev->pm_domain) @@ -1369,9 +1298,6 @@ void pm_runtime_init(struct device *dev) setup_timer(&dev->power.suspend_timer, pm_suspend_timer_fn, (unsigned long)dev); - dev->power.suspend_time = ktime_set(0, 0); - dev->power.max_time_suspended_ns = -1; - init_waitqueue_head(&dev->power.wait_queue); } @@ -1389,28 +1315,3 @@ void pm_runtime_remove(struct device *dev) if (dev->power.irq_safe && dev->parent) pm_runtime_put_sync(dev->parent); } - -/** - * pm_runtime_update_max_time_suspended - Update device's suspend time data. - * @dev: Device to handle. - * @delta_ns: Value to subtract from the device's max_time_suspended_ns field. - * - * Update the device's power.max_time_suspended_ns field by subtracting - * @delta_ns from it. The resulting value of power.max_time_suspended_ns is - * never negative. - */ -void pm_runtime_update_max_time_suspended(struct device *dev, s64 delta_ns) -{ - unsigned long flags; - - spin_lock_irqsave(&dev->power.lock, flags); - - if (delta_ns > 0 && dev->power.max_time_suspended_ns > 0) { - if (dev->power.max_time_suspended_ns > delta_ns) - dev->power.max_time_suspended_ns -= delta_ns; - else - dev->power.max_time_suspended_ns = 0; - } - - spin_unlock_irqrestore(&dev->power.lock, flags); -} diff --git a/drivers/base/power/sysfs.c b/drivers/base/power/sysfs.c index 95c12f6cb5b9..48be2ad4dd2c 100644 --- a/drivers/base/power/sysfs.c +++ b/drivers/base/power/sysfs.c @@ -314,22 +314,41 @@ static ssize_t wakeup_active_count_show(struct device *dev, static DEVICE_ATTR(wakeup_active_count, 0444, wakeup_active_count_show, NULL); -static ssize_t wakeup_hit_count_show(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t wakeup_abort_count_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + unsigned long count = 0; + bool enabled = false; + + spin_lock_irq(&dev->power.lock); + if (dev->power.wakeup) { + count = dev->power.wakeup->wakeup_count; + enabled = true; + } + spin_unlock_irq(&dev->power.lock); + return enabled ? sprintf(buf, "%lu\n", count) : sprintf(buf, "\n"); +} + +static DEVICE_ATTR(wakeup_abort_count, 0444, wakeup_abort_count_show, NULL); + +static ssize_t wakeup_expire_count_show(struct device *dev, + struct device_attribute *attr, + char *buf) { unsigned long count = 0; bool enabled = false; spin_lock_irq(&dev->power.lock); if (dev->power.wakeup) { - count = dev->power.wakeup->hit_count; + count = dev->power.wakeup->expire_count; enabled = true; } spin_unlock_irq(&dev->power.lock); return enabled ? sprintf(buf, "%lu\n", count) : sprintf(buf, "\n"); } -static DEVICE_ATTR(wakeup_hit_count, 0444, wakeup_hit_count_show, NULL); +static DEVICE_ATTR(wakeup_expire_count, 0444, wakeup_expire_count_show, NULL); static ssize_t wakeup_active_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -398,6 +417,27 @@ static ssize_t wakeup_last_time_show(struct device *dev, } static DEVICE_ATTR(wakeup_last_time_ms, 0444, wakeup_last_time_show, NULL); + +#ifdef CONFIG_PM_AUTOSLEEP +static ssize_t wakeup_prevent_sleep_time_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + s64 msec = 0; + bool enabled = false; + + spin_lock_irq(&dev->power.lock); + if (dev->power.wakeup) { + msec = ktime_to_ms(dev->power.wakeup->prevent_sleep_time); + enabled = true; + } + spin_unlock_irq(&dev->power.lock); + return enabled ? sprintf(buf, "%lld\n", msec) : sprintf(buf, "\n"); +} + +static DEVICE_ATTR(wakeup_prevent_sleep_time_ms, 0444, + wakeup_prevent_sleep_time_show, NULL); +#endif /* CONFIG_PM_AUTOSLEEP */ #endif /* CONFIG_PM_SLEEP */ #ifdef CONFIG_PM_ADVANCED_DEBUG @@ -486,11 +526,15 @@ static struct attribute *wakeup_attrs[] = { &dev_attr_wakeup.attr, &dev_attr_wakeup_count.attr, &dev_attr_wakeup_active_count.attr, - &dev_attr_wakeup_hit_count.attr, + &dev_attr_wakeup_abort_count.attr, + &dev_attr_wakeup_expire_count.attr, &dev_attr_wakeup_active.attr, &dev_attr_wakeup_total_time_ms.attr, &dev_attr_wakeup_max_time_ms.attr, &dev_attr_wakeup_last_time_ms.attr, +#ifdef CONFIG_PM_AUTOSLEEP + &dev_attr_wakeup_prevent_sleep_time_ms.attr, +#endif #endif NULL, }; diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c index 2a3e581b8dcd..cbb463b3a750 100644 --- a/drivers/base/power/wakeup.c +++ b/drivers/base/power/wakeup.c @@ -14,16 +14,15 @@ #include <linux/suspend.h> #include <linux/seq_file.h> #include <linux/debugfs.h> +#include <trace/events/power.h> #include "power.h" -#define TIMEOUT 100 - /* * If set, the suspend/hibernate code will abort transitions to a sleep state * if wakeup events are registered during or immediately before the transition. */ -bool events_check_enabled; +bool events_check_enabled __read_mostly; /* * Combined counters of registered wakeup events and wakeup events in progress. @@ -52,6 +51,8 @@ static void pm_wakeup_timer_fn(unsigned long data); static LIST_HEAD(wakeup_sources); +static DECLARE_WAIT_QUEUE_HEAD(wakeup_count_wait_queue); + /** * wakeup_source_prepare - Prepare a new wakeup source for initialization. * @ws: Wakeup source to prepare. @@ -132,6 +133,7 @@ void wakeup_source_add(struct wakeup_source *ws) spin_lock_init(&ws->lock); setup_timer(&ws->timer, pm_wakeup_timer_fn, (unsigned long)ws); ws->active = false; + ws->last_time = ktime_get(); spin_lock_irq(&events_lock); list_add_rcu(&ws->entry, &wakeup_sources); @@ -374,12 +376,33 @@ EXPORT_SYMBOL_GPL(device_set_wakeup_enable); */ static void wakeup_source_activate(struct wakeup_source *ws) { + unsigned int cec; + ws->active = true; ws->active_count++; ws->last_time = ktime_get(); + if (ws->autosleep_enabled) + ws->start_prevent_time = ws->last_time; /* Increment the counter of events in progress. */ - atomic_inc(&combined_event_count); + cec = atomic_inc_return(&combined_event_count); + + trace_wakeup_source_activate(ws->name, cec); +} + +/** + * wakeup_source_report_event - Report wakeup event using the given source. + * @ws: Wakeup source to report the event for. + */ +static void wakeup_source_report_event(struct wakeup_source *ws) +{ + ws->event_count++; + /* This is racy, but the counter is approximate anyway. */ + if (events_check_enabled) + ws->wakeup_count++; + + if (!ws->active) + wakeup_source_activate(ws); } /** @@ -397,10 +420,7 @@ void __pm_stay_awake(struct wakeup_source *ws) spin_lock_irqsave(&ws->lock, flags); - ws->event_count++; - if (!ws->active) - wakeup_source_activate(ws); - + wakeup_source_report_event(ws); del_timer(&ws->timer); ws->timer_expires = 0; @@ -432,6 +452,17 @@ void pm_stay_awake(struct device *dev) } EXPORT_SYMBOL_GPL(pm_stay_awake); +#ifdef CONFIG_PM_AUTOSLEEP +static void update_prevent_sleep_time(struct wakeup_source *ws, ktime_t now) +{ + ktime_t delta = ktime_sub(now, ws->start_prevent_time); + ws->prevent_sleep_time = ktime_add(ws->prevent_sleep_time, delta); +} +#else +static inline void update_prevent_sleep_time(struct wakeup_source *ws, + ktime_t now) {} +#endif + /** * wakup_source_deactivate - Mark given wakeup source as inactive. * @ws: Wakeup source to handle. @@ -442,6 +473,7 @@ EXPORT_SYMBOL_GPL(pm_stay_awake); */ static void wakeup_source_deactivate(struct wakeup_source *ws) { + unsigned int cnt, inpr, cec; ktime_t duration; ktime_t now; @@ -468,14 +500,23 @@ static void wakeup_source_deactivate(struct wakeup_source *ws) if (ktime_to_ns(duration) > ktime_to_ns(ws->max_time)) ws->max_time = duration; + ws->last_time = now; del_timer(&ws->timer); ws->timer_expires = 0; + if (ws->autosleep_enabled) + update_prevent_sleep_time(ws, now); + /* * Increment the counter of registered wakeup events and decrement the * couter of wakeup events in progress simultaneously. */ - atomic_add(MAX_IN_PROGRESS, &combined_event_count); + cec = atomic_add_return(MAX_IN_PROGRESS, &combined_event_count); + trace_wakeup_source_deactivate(ws->name, cec); + + split_counters(&cnt, &inpr); + if (!inpr && waitqueue_active(&wakeup_count_wait_queue)) + wake_up(&wakeup_count_wait_queue); } /** @@ -536,8 +577,10 @@ static void pm_wakeup_timer_fn(unsigned long data) spin_lock_irqsave(&ws->lock, flags); if (ws->active && ws->timer_expires - && time_after_eq(jiffies, ws->timer_expires)) + && time_after_eq(jiffies, ws->timer_expires)) { wakeup_source_deactivate(ws); + ws->expire_count++; + } spin_unlock_irqrestore(&ws->lock, flags); } @@ -564,9 +607,7 @@ void __pm_wakeup_event(struct wakeup_source *ws, unsigned int msec) spin_lock_irqsave(&ws->lock, flags); - ws->event_count++; - if (!ws->active) - wakeup_source_activate(ws); + wakeup_source_report_event(ws); if (!msec) { wakeup_source_deactivate(ws); @@ -609,24 +650,6 @@ void pm_wakeup_event(struct device *dev, unsigned int msec) EXPORT_SYMBOL_GPL(pm_wakeup_event); /** - * pm_wakeup_update_hit_counts - Update hit counts of all active wakeup sources. - */ -static void pm_wakeup_update_hit_counts(void) -{ - unsigned long flags; - struct wakeup_source *ws; - - rcu_read_lock(); - list_for_each_entry_rcu(ws, &wakeup_sources, entry) { - spin_lock_irqsave(&ws->lock, flags); - if (ws->active) - ws->hit_count++; - spin_unlock_irqrestore(&ws->lock, flags); - } - rcu_read_unlock(); -} - -/** * pm_wakeup_pending - Check if power transition in progress should be aborted. * * Compare the current number of registered wakeup events with its preserved @@ -648,32 +671,38 @@ bool pm_wakeup_pending(void) events_check_enabled = !ret; } spin_unlock_irqrestore(&events_lock, flags); - if (ret) - pm_wakeup_update_hit_counts(); return ret; } /** * pm_get_wakeup_count - Read the number of registered wakeup events. * @count: Address to store the value at. + * @block: Whether or not to block. * - * Store the number of registered wakeup events at the address in @count. Block - * if the current number of wakeup events being processed is nonzero. + * Store the number of registered wakeup events at the address in @count. If + * @block is set, block until the current number of wakeup events being + * processed is zero. * - * Return 'false' if the wait for the number of wakeup events being processed to - * drop down to zero has been interrupted by a signal (and the current number - * of wakeup events being processed is still nonzero). Otherwise return 'true'. + * Return 'false' if the current number of wakeup events being processed is + * nonzero. Otherwise return 'true'. */ -bool pm_get_wakeup_count(unsigned int *count) +bool pm_get_wakeup_count(unsigned int *count, bool block) { unsigned int cnt, inpr; - for (;;) { - split_counters(&cnt, &inpr); - if (inpr == 0 || signal_pending(current)) - break; - pm_wakeup_update_hit_counts(); - schedule_timeout_interruptible(msecs_to_jiffies(TIMEOUT)); + if (block) { + DEFINE_WAIT(wait); + + for (;;) { + prepare_to_wait(&wakeup_count_wait_queue, &wait, + TASK_INTERRUPTIBLE); + split_counters(&cnt, &inpr); + if (inpr == 0 || signal_pending(current)) + break; + + schedule(); + } + finish_wait(&wakeup_count_wait_queue, &wait); } split_counters(&cnt, &inpr); @@ -703,11 +732,37 @@ bool pm_save_wakeup_count(unsigned int count) events_check_enabled = true; } spin_unlock_irq(&events_lock); - if (!events_check_enabled) - pm_wakeup_update_hit_counts(); return events_check_enabled; } +#ifdef CONFIG_PM_AUTOSLEEP +/** + * pm_wakep_autosleep_enabled - Modify autosleep_enabled for all wakeup sources. + * @enabled: Whether to set or to clear the autosleep_enabled flags. + */ +void pm_wakep_autosleep_enabled(bool set) +{ + struct wakeup_source *ws; + ktime_t now = ktime_get(); + + rcu_read_lock(); + list_for_each_entry_rcu(ws, &wakeup_sources, entry) { + spin_lock_irq(&ws->lock); + if (ws->autosleep_enabled != set) { + ws->autosleep_enabled = set; + if (ws->active) { + if (set) + ws->start_prevent_time = now; + else + update_prevent_sleep_time(ws, now); + } + } + spin_unlock_irq(&ws->lock); + } + rcu_read_unlock(); +} +#endif /* CONFIG_PM_AUTOSLEEP */ + static struct dentry *wakeup_sources_stats_dentry; /** @@ -723,27 +778,37 @@ static int print_wakeup_source_stats(struct seq_file *m, ktime_t max_time; unsigned long active_count; ktime_t active_time; + ktime_t prevent_sleep_time; int ret; spin_lock_irqsave(&ws->lock, flags); total_time = ws->total_time; max_time = ws->max_time; + prevent_sleep_time = ws->prevent_sleep_time; active_count = ws->active_count; if (ws->active) { - active_time = ktime_sub(ktime_get(), ws->last_time); + ktime_t now = ktime_get(); + + active_time = ktime_sub(now, ws->last_time); total_time = ktime_add(total_time, active_time); if (active_time.tv64 > max_time.tv64) max_time = active_time; + + if (ws->autosleep_enabled) + prevent_sleep_time = ktime_add(prevent_sleep_time, + ktime_sub(now, ws->start_prevent_time)); } else { active_time = ktime_set(0, 0); } - ret = seq_printf(m, "%-12s\t%lu\t\t%lu\t\t%lu\t\t" - "%lld\t\t%lld\t\t%lld\t\t%lld\n", - ws->name, active_count, ws->event_count, ws->hit_count, + ret = seq_printf(m, "%-12s\t%lu\t\t%lu\t\t%lu\t\t%lu\t\t" + "%lld\t\t%lld\t\t%lld\t\t%lld\t\t%lld\n", + ws->name, active_count, ws->event_count, + ws->wakeup_count, ws->expire_count, ktime_to_ms(active_time), ktime_to_ms(total_time), - ktime_to_ms(max_time), ktime_to_ms(ws->last_time)); + ktime_to_ms(max_time), ktime_to_ms(ws->last_time), + ktime_to_ms(prevent_sleep_time)); spin_unlock_irqrestore(&ws->lock, flags); @@ -758,8 +823,9 @@ static int wakeup_sources_stats_show(struct seq_file *m, void *unused) { struct wakeup_source *ws; - seq_puts(m, "name\t\tactive_count\tevent_count\thit_count\t" - "active_since\ttotal_time\tmax_time\tlast_change\n"); + seq_puts(m, "name\t\tactive_count\tevent_count\twakeup_count\t" + "expire_count\tactive_since\ttotal_time\tmax_time\t" + "last_change\tprevent_suspend_time\n"); rcu_read_lock(); list_for_each_entry_rcu(ws, &wakeup_sources, entry) diff --git a/drivers/base/regmap/Kconfig b/drivers/base/regmap/Kconfig index 9ef0a5326f17..6be390bd8bd1 100644 --- a/drivers/base/regmap/Kconfig +++ b/drivers/base/regmap/Kconfig @@ -6,6 +6,7 @@ config REGMAP default y if (REGMAP_I2C || REGMAP_SPI) select LZO_COMPRESS select LZO_DECOMPRESS + select IRQ_DOMAIN if REGMAP_IRQ bool config REGMAP_I2C diff --git a/drivers/char/hw_random/Kconfig b/drivers/char/hw_random/Kconfig index b2402eb076c7..f45dad39a18b 100644 --- a/drivers/char/hw_random/Kconfig +++ b/drivers/char/hw_random/Kconfig @@ -63,7 +63,7 @@ config HW_RANDOM_AMD config HW_RANDOM_ATMEL tristate "Atmel Random Number Generator support" depends on HW_RANDOM && HAVE_CLK - default HW_RANDOM + default (HW_RANDOM && ARCH_AT91) ---help--- This driver provides kernel-side support for the Random Number Generator hardware found on Atmel AT91 devices. @@ -250,3 +250,16 @@ config UML_RANDOM (check your distro, or download from http://sourceforge.net/projects/gkernel/). rngd periodically reads /dev/hwrng and injects the entropy into /dev/random. + +config HW_RANDOM_PSERIES + tristate "pSeries HW Random Number Generator support" + depends on HW_RANDOM && PPC64 && IBMVIO + default HW_RANDOM + ---help--- + This driver provides kernel-side support for the Random Number + Generator hardware found on POWER7+ machines and above + + To compile this driver as a module, choose M here: the + module will be called pseries-rng. + + If unsure, say Y. diff --git a/drivers/char/hw_random/Makefile b/drivers/char/hw_random/Makefile index b2ff5265a996..d901dfa30321 100644 --- a/drivers/char/hw_random/Makefile +++ b/drivers/char/hw_random/Makefile @@ -22,3 +22,4 @@ obj-$(CONFIG_HW_RANDOM_OCTEON) += octeon-rng.o obj-$(CONFIG_HW_RANDOM_NOMADIK) += nomadik-rng.o obj-$(CONFIG_HW_RANDOM_PICOXCELL) += picoxcell-rng.o obj-$(CONFIG_HW_RANDOM_PPC4XX) += ppc4xx-rng.o +obj-$(CONFIG_HW_RANDOM_PSERIES) += pseries-rng.o diff --git a/drivers/char/hw_random/omap-rng.c b/drivers/char/hw_random/omap-rng.c index a07a5caa599c..1412565c01af 100644 --- a/drivers/char/hw_random/omap-rng.c +++ b/drivers/char/hw_random/omap-rng.c @@ -115,22 +115,12 @@ static int __devinit omap_rng_probe(struct platform_device *pdev) res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - ret = -ENOENT; - goto err_region; - } - - if (!request_mem_region(res->start, resource_size(res), pdev->name)) { - ret = -EBUSY; - goto err_region; - } - - dev_set_drvdata(&pdev->dev, res); - rng_base = ioremap(res->start, resource_size(res)); + rng_base = devm_request_and_ioremap(&pdev->dev, res); if (!rng_base) { ret = -ENOMEM; goto err_ioremap; } + dev_set_drvdata(&pdev->dev, res); ret = hwrng_register(&omap_rng_ops); if (ret) @@ -145,11 +135,8 @@ static int __devinit omap_rng_probe(struct platform_device *pdev) return 0; err_register: - iounmap(rng_base); rng_base = NULL; err_ioremap: - release_mem_region(res->start, resource_size(res)); -err_region: if (cpu_is_omap24xx()) { clk_disable(rng_ick); clk_put(rng_ick); @@ -159,20 +146,15 @@ err_region: static int __exit omap_rng_remove(struct platform_device *pdev) { - struct resource *res = dev_get_drvdata(&pdev->dev); - hwrng_unregister(&omap_rng_ops); omap_rng_write_reg(RNG_MASK_REG, 0x0); - iounmap(rng_base); - if (cpu_is_omap24xx()) { clk_disable(rng_ick); clk_put(rng_ick); } - release_mem_region(res->start, resource_size(res)); rng_base = NULL; return 0; diff --git a/drivers/char/hw_random/pseries-rng.c b/drivers/char/hw_random/pseries-rng.c new file mode 100644 index 000000000000..5f1197929f0c --- /dev/null +++ b/drivers/char/hw_random/pseries-rng.c @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2010 Michael Neuling IBM Corporation + * + * Driver for the pseries hardware RNG for POWER7+ and above + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/module.h> +#include <linux/hw_random.h> +#include <asm/vio.h> + +#define MODULE_NAME "pseries-rng" + +static int pseries_rng_data_read(struct hwrng *rng, u32 *data) +{ + if (plpar_hcall(H_RANDOM, (unsigned long *)data) != H_SUCCESS) { + printk(KERN_ERR "pseries rng hcall error\n"); + return 0; + } + return 8; +} + +/** + * pseries_rng_get_desired_dma - Return desired DMA allocate for CMO operations + * + * This is a required function for a driver to operate in a CMO environment + * but this device does not make use of DMA allocations, return 0. + * + * Return value: + * Number of bytes of IO data the driver will need to perform well -> 0 + */ +static unsigned long pseries_rng_get_desired_dma(struct vio_dev *vdev) +{ + return 0; +}; + +static struct hwrng pseries_rng = { + .name = MODULE_NAME, + .data_read = pseries_rng_data_read, +}; + +static int __init pseries_rng_probe(struct vio_dev *dev, + const struct vio_device_id *id) +{ + return hwrng_register(&pseries_rng); +} + +static int __exit pseries_rng_remove(struct vio_dev *dev) +{ + hwrng_unregister(&pseries_rng); + return 0; +} + +static struct vio_device_id pseries_rng_driver_ids[] = { + { "ibm,random-v1", "ibm,random"}, + { "", "" } +}; +MODULE_DEVICE_TABLE(vio, pseries_rng_driver_ids); + +static struct vio_driver pseries_rng_driver = { + .name = MODULE_NAME, + .probe = pseries_rng_probe, + .remove = pseries_rng_remove, + .get_desired_dma = pseries_rng_get_desired_dma, + .id_table = pseries_rng_driver_ids +}; + +static int __init rng_init(void) +{ + printk(KERN_INFO "Registering IBM pSeries RNG driver\n"); + return vio_register_driver(&pseries_rng_driver); +} + +module_init(rng_init); + +static void __exit rng_exit(void) +{ + vio_unregister_driver(&pseries_rng_driver); +} +module_exit(rng_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Michael Neuling <[email protected]>"); +MODULE_DESCRIPTION("H/W RNG driver for IBM pSeries processors"); diff --git a/drivers/crypto/Kconfig b/drivers/crypto/Kconfig index 371f13cc38eb..1092a770482e 100644 --- a/drivers/crypto/Kconfig +++ b/drivers/crypto/Kconfig @@ -297,4 +297,32 @@ config CRYPTO_DEV_TEGRA_AES To compile this driver as a module, choose M here: the module will be called tegra-aes. +config CRYPTO_DEV_NX + tristate "Support for Power7+ in-Nest cryptographic accleration" + depends on PPC64 && IBMVIO + select CRYPTO_AES + select CRYPTO_CBC + select CRYPTO_ECB + select CRYPTO_CCM + select CRYPTO_GCM + select CRYPTO_AUTHENC + select CRYPTO_XCBC + select CRYPTO_SHA256 + select CRYPTO_SHA512 + help + Support for Power7+ in-Nest cryptographic acceleration. This + module supports acceleration for AES and SHA2 algorithms. If you + choose 'M' here, this module will be called nx_crypto. + +config CRYPTO_DEV_UX500 + tristate "Driver for ST-Ericsson UX500 crypto hardware acceleration" + depends on ARCH_U8500 + select CRYPTO_ALGAPI + help + Driver for ST-Ericsson UX500 crypto engine. + +if CRYPTO_DEV_UX500 + source "drivers/crypto/ux500/Kconfig" +endif # if CRYPTO_DEV_UX500 + endif # CRYPTO_HW diff --git a/drivers/crypto/Makefile b/drivers/crypto/Makefile index f3e64eadd7af..01390325d72d 100644 --- a/drivers/crypto/Makefile +++ b/drivers/crypto/Makefile @@ -14,3 +14,4 @@ obj-$(CONFIG_CRYPTO_DEV_OMAP_AES) += omap-aes.o obj-$(CONFIG_CRYPTO_DEV_PICOXCELL) += picoxcell_crypto.o obj-$(CONFIG_CRYPTO_DEV_S5P) += s5p-sss.o obj-$(CONFIG_CRYPTO_DEV_TEGRA_AES) += tegra-aes.o +obj-$(CONFIG_CRYPTO_DEV_UX500) += ux500/
\ No newline at end of file diff --git a/drivers/crypto/amcc/crypto4xx_core.c b/drivers/crypto/amcc/crypto4xx_core.c index 13f8e1a14988..802e85102c32 100644 --- a/drivers/crypto/amcc/crypto4xx_core.c +++ b/drivers/crypto/amcc/crypto4xx_core.c @@ -1244,9 +1244,9 @@ err_start_dev: iounmap(core_dev->dev->ce_base); err_iomap: free_irq(core_dev->irq, dev); +err_request_irq: irq_dispose_mapping(core_dev->irq); tasklet_kill(&core_dev->tasklet); -err_request_irq: crypto4xx_destroy_sdr(core_dev->dev); err_build_sdr: crypto4xx_destroy_gdr(core_dev->dev); diff --git a/drivers/crypto/caam/caamalg.c b/drivers/crypto/caam/caamalg.c index 534a36469d57..4eec389184d3 100644 --- a/drivers/crypto/caam/caamalg.c +++ b/drivers/crypto/caam/caamalg.c @@ -2267,8 +2267,11 @@ static void __exit caam_algapi_exit(void) int i, err; dev_node = of_find_compatible_node(NULL, NULL, "fsl,sec-v4.0"); - if (!dev_node) - return; + if (!dev_node) { + dev_node = of_find_compatible_node(NULL, NULL, "fsl,sec4.0"); + if (!dev_node) + return; + } pdev = of_find_device_by_node(dev_node); if (!pdev) @@ -2350,8 +2353,11 @@ static int __init caam_algapi_init(void) int i = 0, err = 0; dev_node = of_find_compatible_node(NULL, NULL, "fsl,sec-v4.0"); - if (!dev_node) - return -ENODEV; + if (!dev_node) { + dev_node = of_find_compatible_node(NULL, NULL, "fsl,sec4.0"); + if (!dev_node) + return -ENODEV; + } pdev = of_find_device_by_node(dev_node); if (!pdev) diff --git a/drivers/crypto/caam/ctrl.c b/drivers/crypto/caam/ctrl.c index c5f61c55d923..77557ebcd337 100644 --- a/drivers/crypto/caam/ctrl.c +++ b/drivers/crypto/caam/ctrl.c @@ -98,6 +98,12 @@ static int caam_probe(struct platform_device *pdev) rspec = 0; for_each_compatible_node(np, NULL, "fsl,sec-v4.0-job-ring") rspec++; + if (!rspec) { + /* for backward compatible with device trees */ + for_each_compatible_node(np, NULL, "fsl,sec4.0-job-ring") + rspec++; + } + ctrlpriv->jrdev = kzalloc(sizeof(struct device *) * rspec, GFP_KERNEL); if (ctrlpriv->jrdev == NULL) { iounmap(&topregs->ctrl); @@ -111,6 +117,13 @@ static int caam_probe(struct platform_device *pdev) ctrlpriv->total_jobrs++; ring++; } + if (!ring) { + for_each_compatible_node(np, NULL, "fsl,sec4.0-job-ring") { + caam_jr_probe(pdev, np, ring); + ctrlpriv->total_jobrs++; + ring++; + } + } /* Check to see if QI present. If so, enable */ ctrlpriv->qi_present = !!(rd_reg64(&topregs->ctrl.perfmon.comp_parms) & @@ -226,6 +239,9 @@ static struct of_device_id caam_match[] = { { .compatible = "fsl,sec-v4.0", }, + { + .compatible = "fsl,sec4.0", + }, {}, }; MODULE_DEVICE_TABLE(of, caam_match); diff --git a/drivers/crypto/nx/Makefile b/drivers/crypto/nx/Makefile new file mode 100644 index 000000000000..411ce59c80d1 --- /dev/null +++ b/drivers/crypto/nx/Makefile @@ -0,0 +1,11 @@ +obj-$(CONFIG_CRYPTO_DEV_NX) += nx-crypto.o +nx-crypto-objs := nx.o \ + nx_debugfs.o \ + nx-aes-cbc.o \ + nx-aes-ecb.o \ + nx-aes-gcm.o \ + nx-aes-ccm.o \ + nx-aes-ctr.o \ + nx-aes-xcbc.o \ + nx-sha256.o \ + nx-sha512.o diff --git a/drivers/crypto/nx/nx-aes-cbc.c b/drivers/crypto/nx/nx-aes-cbc.c new file mode 100644 index 000000000000..69ed796ee327 --- /dev/null +++ b/drivers/crypto/nx/nx-aes-cbc.c @@ -0,0 +1,141 @@ +/** + * AES CBC routines supporting the Power 7+ Nest Accelerators driver + * + * Copyright (C) 2011-2012 International Business Machines Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Author: Kent Yoder <[email protected]> + */ + +#include <crypto/aes.h> +#include <crypto/algapi.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/crypto.h> +#include <asm/vio.h> + +#include "nx_csbcpb.h" +#include "nx.h" + + +static int cbc_aes_nx_set_key(struct crypto_tfm *tfm, + const u8 *in_key, + unsigned int key_len) +{ + struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(tfm); + struct nx_csbcpb *csbcpb = nx_ctx->csbcpb; + + nx_ctx_init(nx_ctx, HCOP_FC_AES); + + switch (key_len) { + case AES_KEYSIZE_128: + NX_CPB_SET_KEY_SIZE(csbcpb, NX_KS_AES_128); + nx_ctx->ap = &nx_ctx->props[NX_PROPS_AES_128]; + break; + case AES_KEYSIZE_192: + NX_CPB_SET_KEY_SIZE(csbcpb, NX_KS_AES_192); + nx_ctx->ap = &nx_ctx->props[NX_PROPS_AES_192]; + break; + case AES_KEYSIZE_256: + NX_CPB_SET_KEY_SIZE(csbcpb, NX_KS_AES_256); + nx_ctx->ap = &nx_ctx->props[NX_PROPS_AES_256]; + break; + default: + return -EINVAL; + } + + csbcpb->cpb.hdr.mode = NX_MODE_AES_CBC; + memcpy(csbcpb->cpb.aes_cbc.key, in_key, key_len); + + return 0; +} + +static int cbc_aes_nx_crypt(struct blkcipher_desc *desc, + struct scatterlist *dst, + struct scatterlist *src, + unsigned int nbytes, + int enc) +{ + struct nx_crypto_ctx *nx_ctx = crypto_blkcipher_ctx(desc->tfm); + struct nx_csbcpb *csbcpb = nx_ctx->csbcpb; + int rc; + + if (nbytes > nx_ctx->ap->databytelen) + return -EINVAL; + + if (enc) + NX_CPB_FDM(csbcpb) |= NX_FDM_ENDE_ENCRYPT; + else + NX_CPB_FDM(csbcpb) &= ~NX_FDM_ENDE_ENCRYPT; + + rc = nx_build_sg_lists(nx_ctx, desc, dst, src, nbytes, + csbcpb->cpb.aes_cbc.iv); + if (rc) + goto out; + + if (!nx_ctx->op.inlen || !nx_ctx->op.outlen) { + rc = -EINVAL; + goto out; + } + + rc = nx_hcall_sync(nx_ctx, &nx_ctx->op, + desc->flags & CRYPTO_TFM_REQ_MAY_SLEEP); + if (rc) + goto out; + + atomic_inc(&(nx_ctx->stats->aes_ops)); + atomic64_add(csbcpb->csb.processed_byte_count, + &(nx_ctx->stats->aes_bytes)); +out: + return rc; +} + +static int cbc_aes_nx_encrypt(struct blkcipher_desc *desc, + struct scatterlist *dst, + struct scatterlist *src, + unsigned int nbytes) +{ + return cbc_aes_nx_crypt(desc, dst, src, nbytes, 1); +} + +static int cbc_aes_nx_decrypt(struct blkcipher_desc *desc, + struct scatterlist *dst, + struct scatterlist *src, + unsigned int nbytes) +{ + return cbc_aes_nx_crypt(desc, dst, src, nbytes, 0); +} + +struct crypto_alg nx_cbc_aes_alg = { + .cra_name = "cbc(aes)", + .cra_driver_name = "cbc-aes-nx", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_BLKCIPHER, + .cra_blocksize = AES_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct nx_crypto_ctx), + .cra_type = &crypto_blkcipher_type, + .cra_module = THIS_MODULE, + .cra_list = LIST_HEAD_INIT(nx_cbc_aes_alg.cra_list), + .cra_init = nx_crypto_ctx_aes_cbc_init, + .cra_exit = nx_crypto_ctx_exit, + .cra_blkcipher = { + .min_keysize = AES_MIN_KEY_SIZE, + .max_keysize = AES_MAX_KEY_SIZE, + .ivsize = AES_BLOCK_SIZE, + .setkey = cbc_aes_nx_set_key, + .encrypt = cbc_aes_nx_encrypt, + .decrypt = cbc_aes_nx_decrypt, + } +}; diff --git a/drivers/crypto/nx/nx-aes-ccm.c b/drivers/crypto/nx/nx-aes-ccm.c new file mode 100644 index 000000000000..7aeac678b9c0 --- /dev/null +++ b/drivers/crypto/nx/nx-aes-ccm.c @@ -0,0 +1,468 @@ +/** + * AES CCM routines supporting the Power 7+ Nest Accelerators driver + * + * Copyright (C) 2012 International Business Machines Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Author: Kent Yoder <[email protected]> + */ + +#include <crypto/internal/aead.h> +#include <crypto/aes.h> +#include <crypto/algapi.h> +#include <crypto/scatterwalk.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/crypto.h> +#include <asm/vio.h> + +#include "nx_csbcpb.h" +#include "nx.h" + + +static int ccm_aes_nx_set_key(struct crypto_aead *tfm, + const u8 *in_key, + unsigned int key_len) +{ + struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&tfm->base); + struct nx_csbcpb *csbcpb = nx_ctx->csbcpb; + struct nx_csbcpb *csbcpb_aead = nx_ctx->csbcpb_aead; + + nx_ctx_init(nx_ctx, HCOP_FC_AES); + + switch (key_len) { + case AES_KEYSIZE_128: + NX_CPB_SET_KEY_SIZE(csbcpb, NX_KS_AES_128); + NX_CPB_SET_KEY_SIZE(csbcpb_aead, NX_KS_AES_128); + nx_ctx->ap = &nx_ctx->props[NX_PROPS_AES_128]; + break; + default: + return -EINVAL; + } + + csbcpb->cpb.hdr.mode = NX_MODE_AES_CCM; + memcpy(csbcpb->cpb.aes_ccm.key, in_key, key_len); + + csbcpb_aead->cpb.hdr.mode = NX_MODE_AES_CCA; + memcpy(csbcpb_aead->cpb.aes_cca.key, in_key, key_len); + + return 0; + +} + +static int ccm4309_aes_nx_set_key(struct crypto_aead *tfm, + const u8 *in_key, + unsigned int key_len) +{ + struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&tfm->base); + + if (key_len < 3) + return -EINVAL; + + key_len -= 3; + + memcpy(nx_ctx->priv.ccm.nonce, in_key + key_len, 3); + + return ccm_aes_nx_set_key(tfm, in_key, key_len); +} + +static int ccm_aes_nx_setauthsize(struct crypto_aead *tfm, + unsigned int authsize) +{ + switch (authsize) { + case 4: + case 6: + case 8: + case 10: + case 12: + case 14: + case 16: + break; + default: + return -EINVAL; + } + + crypto_aead_crt(tfm)->authsize = authsize; + + return 0; +} + +static int ccm4309_aes_nx_setauthsize(struct crypto_aead *tfm, + unsigned int authsize) +{ + switch (authsize) { + case 8: + case 12: + case 16: + break; + default: + return -EINVAL; + } + + crypto_aead_crt(tfm)->authsize = authsize; + + return 0; +} + +/* taken from crypto/ccm.c */ +static int set_msg_len(u8 *block, unsigned int msglen, int csize) +{ + __be32 data; + + memset(block, 0, csize); + block += csize; + + if (csize >= 4) + csize = 4; + else if (msglen > (unsigned int)(1 << (8 * csize))) + return -EOVERFLOW; + + data = cpu_to_be32(msglen); + memcpy(block - csize, (u8 *)&data + 4 - csize, csize); + + return 0; +} + +/* taken from crypto/ccm.c */ +static inline int crypto_ccm_check_iv(const u8 *iv) +{ + /* 2 <= L <= 8, so 1 <= L' <= 7. */ + if (1 > iv[0] || iv[0] > 7) + return -EINVAL; + + return 0; +} + +/* based on code from crypto/ccm.c */ +static int generate_b0(u8 *iv, unsigned int assoclen, unsigned int authsize, + unsigned int cryptlen, u8 *b0) +{ + unsigned int l, lp, m = authsize; + int rc; + + memcpy(b0, iv, 16); + + lp = b0[0]; + l = lp + 1; + + /* set m, bits 3-5 */ + *b0 |= (8 * ((m - 2) / 2)); + + /* set adata, bit 6, if associated data is used */ + if (assoclen) + *b0 |= 64; + + rc = set_msg_len(b0 + 16 - l, cryptlen, l); + + return rc; +} + +static int generate_pat(u8 *iv, + struct aead_request *req, + struct nx_crypto_ctx *nx_ctx, + unsigned int authsize, + unsigned int nbytes, + u8 *out) +{ + struct nx_sg *nx_insg = nx_ctx->in_sg; + struct nx_sg *nx_outsg = nx_ctx->out_sg; + unsigned int iauth_len = 0; + struct vio_pfo_op *op = NULL; + u8 tmp[16], *b1 = NULL, *b0 = NULL, *result = NULL; + int rc; + + /* zero the ctr value */ + memset(iv + 15 - iv[0], 0, iv[0] + 1); + + if (!req->assoclen) { + b0 = nx_ctx->csbcpb->cpb.aes_ccm.in_pat_or_b0; + } else if (req->assoclen <= 14) { + /* if associated data is 14 bytes or less, we do 1 GCM + * operation on 2 AES blocks, B0 (stored in the csbcpb) and B1, + * which is fed in through the source buffers here */ + b0 = nx_ctx->csbcpb->cpb.aes_ccm.in_pat_or_b0; + b1 = nx_ctx->priv.ccm.iauth_tag; + iauth_len = req->assoclen; + + nx_insg = nx_build_sg_list(nx_insg, b1, 16, nx_ctx->ap->sglen); + nx_outsg = nx_build_sg_list(nx_outsg, tmp, 16, + nx_ctx->ap->sglen); + + /* inlen should be negative, indicating to phyp that its a + * pointer to an sg list */ + nx_ctx->op.inlen = (nx_ctx->in_sg - nx_insg) * + sizeof(struct nx_sg); + nx_ctx->op.outlen = (nx_ctx->out_sg - nx_outsg) * + sizeof(struct nx_sg); + + NX_CPB_FDM(nx_ctx->csbcpb) |= NX_FDM_ENDE_ENCRYPT; + NX_CPB_FDM(nx_ctx->csbcpb) |= NX_FDM_INTERMEDIATE; + + op = &nx_ctx->op; + result = nx_ctx->csbcpb->cpb.aes_ccm.out_pat_or_mac; + } else if (req->assoclen <= 65280) { + /* if associated data is less than (2^16 - 2^8), we construct + * B1 differently and feed in the associated data to a CCA + * operation */ + b0 = nx_ctx->csbcpb_aead->cpb.aes_cca.b0; + b1 = nx_ctx->csbcpb_aead->cpb.aes_cca.b1; + iauth_len = 14; + + /* remaining assoc data must have scatterlist built for it */ + nx_insg = nx_walk_and_build(nx_insg, nx_ctx->ap->sglen, + req->assoc, iauth_len, + req->assoclen - iauth_len); + nx_ctx->op_aead.inlen = (nx_ctx->in_sg - nx_insg) * + sizeof(struct nx_sg); + + op = &nx_ctx->op_aead; + result = nx_ctx->csbcpb_aead->cpb.aes_cca.out_pat_or_b0; + } else { + /* if associated data is less than (2^32), we construct B1 + * differently yet again and feed in the associated data to a + * CCA operation */ + pr_err("associated data len is %u bytes (returning -EINVAL)\n", + req->assoclen); + rc = -EINVAL; + } + + rc = generate_b0(iv, req->assoclen, authsize, nbytes, b0); + if (rc) + goto done; + + if (b1) { + memset(b1, 0, 16); + *(u16 *)b1 = (u16)req->assoclen; + + scatterwalk_map_and_copy(b1 + 2, req->assoc, 0, + iauth_len, SCATTERWALK_FROM_SG); + + rc = nx_hcall_sync(nx_ctx, op, + req->base.flags & CRYPTO_TFM_REQ_MAY_SLEEP); + if (rc) + goto done; + + atomic_inc(&(nx_ctx->stats->aes_ops)); + atomic64_add(req->assoclen, &(nx_ctx->stats->aes_bytes)); + + memcpy(out, result, AES_BLOCK_SIZE); + } +done: + return rc; +} + +static int ccm_nx_decrypt(struct aead_request *req, + struct blkcipher_desc *desc) +{ + struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(req->base.tfm); + struct nx_csbcpb *csbcpb = nx_ctx->csbcpb; + unsigned int nbytes = req->cryptlen; + unsigned int authsize = crypto_aead_authsize(crypto_aead_reqtfm(req)); + struct nx_ccm_priv *priv = &nx_ctx->priv.ccm; + int rc = -1; + + if (nbytes > nx_ctx->ap->databytelen) + return -EINVAL; + + nbytes -= authsize; + + /* copy out the auth tag to compare with later */ + scatterwalk_map_and_copy(priv->oauth_tag, + req->src, nbytes, authsize, + SCATTERWALK_FROM_SG); + + rc = generate_pat(desc->info, req, nx_ctx, authsize, nbytes, + csbcpb->cpb.aes_ccm.in_pat_or_b0); + if (rc) + goto out; + + rc = nx_build_sg_lists(nx_ctx, desc, req->dst, req->src, nbytes, + csbcpb->cpb.aes_ccm.iv_or_ctr); + if (rc) + goto out; + + NX_CPB_FDM(nx_ctx->csbcpb) &= ~NX_FDM_ENDE_ENCRYPT; + NX_CPB_FDM(nx_ctx->csbcpb) &= ~NX_FDM_INTERMEDIATE; + + rc = nx_hcall_sync(nx_ctx, &nx_ctx->op, + req->base.flags & CRYPTO_TFM_REQ_MAY_SLEEP); + if (rc) + goto out; + + atomic_inc(&(nx_ctx->stats->aes_ops)); + atomic64_add(csbcpb->csb.processed_byte_count, + &(nx_ctx->stats->aes_bytes)); + + rc = memcmp(csbcpb->cpb.aes_ccm.out_pat_or_mac, priv->oauth_tag, + authsize) ? -EBADMSG : 0; +out: + return rc; +} + +static int ccm_nx_encrypt(struct aead_request *req, + struct blkcipher_desc *desc) +{ + struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(req->base.tfm); + struct nx_csbcpb *csbcpb = nx_ctx->csbcpb; + unsigned int nbytes = req->cryptlen; + unsigned int authsize = crypto_aead_authsize(crypto_aead_reqtfm(req)); + int rc = -1; + + if (nbytes > nx_ctx->ap->databytelen) + return -EINVAL; + + rc = generate_pat(desc->info, req, nx_ctx, authsize, nbytes, + csbcpb->cpb.aes_ccm.in_pat_or_b0); + if (rc) + goto out; + + rc = nx_build_sg_lists(nx_ctx, desc, req->dst, req->src, nbytes, + csbcpb->cpb.aes_ccm.iv_or_ctr); + if (rc) + goto out; + + NX_CPB_FDM(csbcpb) |= NX_FDM_ENDE_ENCRYPT; + NX_CPB_FDM(csbcpb) &= ~NX_FDM_INTERMEDIATE; + + rc = nx_hcall_sync(nx_ctx, &nx_ctx->op, + req->base.flags & CRYPTO_TFM_REQ_MAY_SLEEP); + if (rc) + goto out; + + atomic_inc(&(nx_ctx->stats->aes_ops)); + atomic64_add(csbcpb->csb.processed_byte_count, + &(nx_ctx->stats->aes_bytes)); + + /* copy out the auth tag */ + scatterwalk_map_and_copy(csbcpb->cpb.aes_ccm.out_pat_or_mac, + req->dst, nbytes, authsize, + SCATTERWALK_TO_SG); +out: + return rc; +} + +static int ccm4309_aes_nx_encrypt(struct aead_request *req) +{ + struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(req->base.tfm); + struct blkcipher_desc desc; + u8 *iv = nx_ctx->priv.ccm.iv; + + iv[0] = 3; + memcpy(iv + 1, nx_ctx->priv.ccm.nonce, 3); + memcpy(iv + 4, req->iv, 8); + + desc.info = iv; + desc.tfm = (struct crypto_blkcipher *)req->base.tfm; + + return ccm_nx_encrypt(req, &desc); +} + +static int ccm_aes_nx_encrypt(struct aead_request *req) +{ + struct blkcipher_desc desc; + int rc; + + desc.info = req->iv; + desc.tfm = (struct crypto_blkcipher *)req->base.tfm; + + rc = crypto_ccm_check_iv(desc.info); + if (rc) + return rc; + + return ccm_nx_encrypt(req, &desc); +} + +static int ccm4309_aes_nx_decrypt(struct aead_request *req) +{ + struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(req->base.tfm); + struct blkcipher_desc desc; + u8 *iv = nx_ctx->priv.ccm.iv; + + iv[0] = 3; + memcpy(iv + 1, nx_ctx->priv.ccm.nonce, 3); + memcpy(iv + 4, req->iv, 8); + + desc.info = iv; + desc.tfm = (struct crypto_blkcipher *)req->base.tfm; + + return ccm_nx_decrypt(req, &desc); +} + +static int ccm_aes_nx_decrypt(struct aead_request *req) +{ + struct blkcipher_desc desc; + int rc; + + desc.info = req->iv; + desc.tfm = (struct crypto_blkcipher *)req->base.tfm; + + rc = crypto_ccm_check_iv(desc.info); + if (rc) + return rc; + + return ccm_nx_decrypt(req, &desc); +} + +/* tell the block cipher walk routines that this is a stream cipher by + * setting cra_blocksize to 1. Even using blkcipher_walk_virt_block + * during encrypt/decrypt doesn't solve this problem, because it calls + * blkcipher_walk_done under the covers, which doesn't use walk->blocksize, + * but instead uses this tfm->blocksize. */ +struct crypto_alg nx_ccm_aes_alg = { + .cra_name = "ccm(aes)", + .cra_driver_name = "ccm-aes-nx", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_AEAD | + CRYPTO_ALG_NEED_FALLBACK, + .cra_blocksize = 1, + .cra_ctxsize = sizeof(struct nx_crypto_ctx), + .cra_type = &crypto_aead_type, + .cra_module = THIS_MODULE, + .cra_list = LIST_HEAD_INIT(nx_ccm_aes_alg.cra_list), + .cra_init = nx_crypto_ctx_aes_ccm_init, + .cra_exit = nx_crypto_ctx_exit, + .cra_aead = { + .ivsize = AES_BLOCK_SIZE, + .maxauthsize = AES_BLOCK_SIZE, + .setkey = ccm_aes_nx_set_key, + .setauthsize = ccm_aes_nx_setauthsize, + .encrypt = ccm_aes_nx_encrypt, + .decrypt = ccm_aes_nx_decrypt, + } +}; + +struct crypto_alg nx_ccm4309_aes_alg = { + .cra_name = "rfc4309(ccm(aes))", + .cra_driver_name = "rfc4309-ccm-aes-nx", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_AEAD | + CRYPTO_ALG_NEED_FALLBACK, + .cra_blocksize = 1, + .cra_ctxsize = sizeof(struct nx_crypto_ctx), + .cra_type = &crypto_nivaead_type, + .cra_module = THIS_MODULE, + .cra_list = LIST_HEAD_INIT(nx_ccm4309_aes_alg.cra_list), + .cra_init = nx_crypto_ctx_aes_ccm_init, + .cra_exit = nx_crypto_ctx_exit, + .cra_aead = { + .ivsize = 8, + .maxauthsize = AES_BLOCK_SIZE, + .setkey = ccm4309_aes_nx_set_key, + .setauthsize = ccm4309_aes_nx_setauthsize, + .encrypt = ccm4309_aes_nx_encrypt, + .decrypt = ccm4309_aes_nx_decrypt, + .geniv = "seqiv", + } +}; diff --git a/drivers/crypto/nx/nx-aes-ctr.c b/drivers/crypto/nx/nx-aes-ctr.c new file mode 100644 index 000000000000..52d4eb05e8f7 --- /dev/null +++ b/drivers/crypto/nx/nx-aes-ctr.c @@ -0,0 +1,178 @@ +/** + * AES CTR routines supporting the Power 7+ Nest Accelerators driver + * + * Copyright (C) 2011-2012 International Business Machines Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Author: Kent Yoder <[email protected]> + */ + +#include <crypto/aes.h> +#include <crypto/ctr.h> +#include <crypto/algapi.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/crypto.h> +#include <asm/vio.h> + +#include "nx_csbcpb.h" +#include "nx.h" + + +static int ctr_aes_nx_set_key(struct crypto_tfm *tfm, + const u8 *in_key, + unsigned int key_len) +{ + struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(tfm); + struct nx_csbcpb *csbcpb = nx_ctx->csbcpb; + + nx_ctx_init(nx_ctx, HCOP_FC_AES); + + switch (key_len) { + case AES_KEYSIZE_128: + NX_CPB_SET_KEY_SIZE(csbcpb, NX_KS_AES_128); + nx_ctx->ap = &nx_ctx->props[NX_PROPS_AES_128]; + break; + case AES_KEYSIZE_192: + NX_CPB_SET_KEY_SIZE(csbcpb, NX_KS_AES_192); + nx_ctx->ap = &nx_ctx->props[NX_PROPS_AES_192]; + break; + case AES_KEYSIZE_256: + NX_CPB_SET_KEY_SIZE(csbcpb, NX_KS_AES_256); + nx_ctx->ap = &nx_ctx->props[NX_PROPS_AES_256]; + break; + default: + return -EINVAL; + } + + csbcpb->cpb.hdr.mode = NX_MODE_AES_CTR; + memcpy(csbcpb->cpb.aes_ctr.key, in_key, key_len); + + return 0; +} + +static int ctr3686_aes_nx_set_key(struct crypto_tfm *tfm, + const u8 *in_key, + unsigned int key_len) +{ + struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(tfm); + + if (key_len < CTR_RFC3686_NONCE_SIZE) + return -EINVAL; + + memcpy(nx_ctx->priv.ctr.iv, + in_key + key_len - CTR_RFC3686_NONCE_SIZE, + CTR_RFC3686_NONCE_SIZE); + + key_len -= CTR_RFC3686_NONCE_SIZE; + + return ctr_aes_nx_set_key(tfm, in_key, key_len); +} + +static int ctr_aes_nx_crypt(struct blkcipher_desc *desc, + struct scatterlist *dst, + struct scatterlist *src, + unsigned int nbytes) +{ + struct nx_crypto_ctx *nx_ctx = crypto_blkcipher_ctx(desc->tfm); + struct nx_csbcpb *csbcpb = nx_ctx->csbcpb; + int rc; + + if (nbytes > nx_ctx->ap->databytelen) + return -EINVAL; + + rc = nx_build_sg_lists(nx_ctx, desc, dst, src, nbytes, + csbcpb->cpb.aes_ctr.iv); + if (rc) + goto out; + + if (!nx_ctx->op.inlen || !nx_ctx->op.outlen) { + rc = -EINVAL; + goto out; + } + + rc = nx_hcall_sync(nx_ctx, &nx_ctx->op, + desc->flags & CRYPTO_TFM_REQ_MAY_SLEEP); + if (rc) + goto out; + + atomic_inc(&(nx_ctx->stats->aes_ops)); + atomic64_add(csbcpb->csb.processed_byte_count, + &(nx_ctx->stats->aes_bytes)); +out: + return rc; +} + +static int ctr3686_aes_nx_crypt(struct blkcipher_desc *desc, + struct scatterlist *dst, + struct scatterlist *src, + unsigned int nbytes) +{ + struct nx_crypto_ctx *nx_ctx = crypto_blkcipher_ctx(desc->tfm); + u8 *iv = nx_ctx->priv.ctr.iv; + + memcpy(iv + CTR_RFC3686_NONCE_SIZE, + desc->info, CTR_RFC3686_IV_SIZE); + iv[15] = 1; + + desc->info = nx_ctx->priv.ctr.iv; + + return ctr_aes_nx_crypt(desc, dst, src, nbytes); +} + +struct crypto_alg nx_ctr_aes_alg = { + .cra_name = "ctr(aes)", + .cra_driver_name = "ctr-aes-nx", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_BLKCIPHER, + .cra_blocksize = 1, + .cra_ctxsize = sizeof(struct nx_crypto_ctx), + .cra_type = &crypto_blkcipher_type, + .cra_module = THIS_MODULE, + .cra_list = LIST_HEAD_INIT(nx_ctr_aes_alg.cra_list), + .cra_init = nx_crypto_ctx_aes_ctr_init, + .cra_exit = nx_crypto_ctx_exit, + .cra_blkcipher = { + .min_keysize = AES_MIN_KEY_SIZE, + .max_keysize = AES_MAX_KEY_SIZE, + .ivsize = AES_BLOCK_SIZE, + .setkey = ctr_aes_nx_set_key, + .encrypt = ctr_aes_nx_crypt, + .decrypt = ctr_aes_nx_crypt, + } +}; + +struct crypto_alg nx_ctr3686_aes_alg = { + .cra_name = "rfc3686(ctr(aes))", + .cra_driver_name = "rfc3686-ctr-aes-nx", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_BLKCIPHER, + .cra_blocksize = 1, + .cra_ctxsize = sizeof(struct nx_crypto_ctx), + .cra_type = &crypto_blkcipher_type, + .cra_module = THIS_MODULE, + .cra_list = LIST_HEAD_INIT(nx_ctr3686_aes_alg.cra_list), + .cra_init = nx_crypto_ctx_aes_ctr_init, + .cra_exit = nx_crypto_ctx_exit, + .cra_blkcipher = { + .min_keysize = AES_MIN_KEY_SIZE + CTR_RFC3686_NONCE_SIZE, + .max_keysize = AES_MAX_KEY_SIZE + CTR_RFC3686_NONCE_SIZE, + .ivsize = CTR_RFC3686_IV_SIZE, + .geniv = "seqiv", + .setkey = ctr3686_aes_nx_set_key, + .encrypt = ctr3686_aes_nx_crypt, + .decrypt = ctr3686_aes_nx_crypt, + } +}; diff --git a/drivers/crypto/nx/nx-aes-ecb.c b/drivers/crypto/nx/nx-aes-ecb.c new file mode 100644 index 000000000000..7b77bc2d1df4 --- /dev/null +++ b/drivers/crypto/nx/nx-aes-ecb.c @@ -0,0 +1,139 @@ +/** + * AES ECB routines supporting the Power 7+ Nest Accelerators driver + * + * Copyright (C) 2011-2012 International Business Machines Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Author: Kent Yoder <[email protected]> + */ + +#include <crypto/aes.h> +#include <crypto/algapi.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/crypto.h> +#include <asm/vio.h> + +#include "nx_csbcpb.h" +#include "nx.h" + + +static int ecb_aes_nx_set_key(struct crypto_tfm *tfm, + const u8 *in_key, + unsigned int key_len) +{ + struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(tfm); + struct nx_csbcpb *csbcpb = (struct nx_csbcpb *)nx_ctx->csbcpb; + + nx_ctx_init(nx_ctx, HCOP_FC_AES); + + switch (key_len) { + case AES_KEYSIZE_128: + NX_CPB_SET_KEY_SIZE(csbcpb, NX_KS_AES_128); + nx_ctx->ap = &nx_ctx->props[NX_PROPS_AES_128]; + break; + case AES_KEYSIZE_192: + NX_CPB_SET_KEY_SIZE(csbcpb, NX_KS_AES_192); + nx_ctx->ap = &nx_ctx->props[NX_PROPS_AES_192]; + break; + case AES_KEYSIZE_256: + NX_CPB_SET_KEY_SIZE(csbcpb, NX_KS_AES_256); + nx_ctx->ap = &nx_ctx->props[NX_PROPS_AES_256]; + break; + default: + return -EINVAL; + } + + csbcpb->cpb.hdr.mode = NX_MODE_AES_ECB; + memcpy(csbcpb->cpb.aes_ecb.key, in_key, key_len); + + return 0; +} + +static int ecb_aes_nx_crypt(struct blkcipher_desc *desc, + struct scatterlist *dst, + struct scatterlist *src, + unsigned int nbytes, + int enc) +{ + struct nx_crypto_ctx *nx_ctx = crypto_blkcipher_ctx(desc->tfm); + struct nx_csbcpb *csbcpb = nx_ctx->csbcpb; + int rc; + + if (nbytes > nx_ctx->ap->databytelen) + return -EINVAL; + + if (enc) + NX_CPB_FDM(csbcpb) |= NX_FDM_ENDE_ENCRYPT; + else + NX_CPB_FDM(csbcpb) &= ~NX_FDM_ENDE_ENCRYPT; + + rc = nx_build_sg_lists(nx_ctx, desc, dst, src, nbytes, NULL); + if (rc) + goto out; + + if (!nx_ctx->op.inlen || !nx_ctx->op.outlen) { + rc = -EINVAL; + goto out; + } + + rc = nx_hcall_sync(nx_ctx, &nx_ctx->op, + desc->flags & CRYPTO_TFM_REQ_MAY_SLEEP); + if (rc) + goto out; + + atomic_inc(&(nx_ctx->stats->aes_ops)); + atomic64_add(csbcpb->csb.processed_byte_count, + &(nx_ctx->stats->aes_bytes)); +out: + return rc; +} + +static int ecb_aes_nx_encrypt(struct blkcipher_desc *desc, + struct scatterlist *dst, + struct scatterlist *src, + unsigned int nbytes) +{ + return ecb_aes_nx_crypt(desc, dst, src, nbytes, 1); +} + +static int ecb_aes_nx_decrypt(struct blkcipher_desc *desc, + struct scatterlist *dst, + struct scatterlist *src, + unsigned int nbytes) +{ + return ecb_aes_nx_crypt(desc, dst, src, nbytes, 0); +} + +struct crypto_alg nx_ecb_aes_alg = { + .cra_name = "ecb(aes)", + .cra_driver_name = "ecb-aes-nx", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_BLKCIPHER, + .cra_blocksize = AES_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct nx_crypto_ctx), + .cra_type = &crypto_blkcipher_type, + .cra_module = THIS_MODULE, + .cra_list = LIST_HEAD_INIT(nx_ecb_aes_alg.cra_list), + .cra_init = nx_crypto_ctx_aes_ecb_init, + .cra_exit = nx_crypto_ctx_exit, + .cra_blkcipher = { + .min_keysize = AES_MIN_KEY_SIZE, + .max_keysize = AES_MAX_KEY_SIZE, + .setkey = ecb_aes_nx_set_key, + .encrypt = ecb_aes_nx_encrypt, + .decrypt = ecb_aes_nx_decrypt, + } +}; diff --git a/drivers/crypto/nx/nx-aes-gcm.c b/drivers/crypto/nx/nx-aes-gcm.c new file mode 100644 index 000000000000..9ab1c7341dac --- /dev/null +++ b/drivers/crypto/nx/nx-aes-gcm.c @@ -0,0 +1,353 @@ +/** + * AES GCM routines supporting the Power 7+ Nest Accelerators driver + * + * Copyright (C) 2012 International Business Machines Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Author: Kent Yoder <[email protected]> + */ + +#include <crypto/internal/aead.h> +#include <crypto/aes.h> +#include <crypto/algapi.h> +#include <crypto/scatterwalk.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/crypto.h> +#include <asm/vio.h> + +#include "nx_csbcpb.h" +#include "nx.h" + + +static int gcm_aes_nx_set_key(struct crypto_aead *tfm, + const u8 *in_key, + unsigned int key_len) +{ + struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&tfm->base); + struct nx_csbcpb *csbcpb = nx_ctx->csbcpb; + struct nx_csbcpb *csbcpb_aead = nx_ctx->csbcpb_aead; + + nx_ctx_init(nx_ctx, HCOP_FC_AES); + + switch (key_len) { + case AES_KEYSIZE_128: + NX_CPB_SET_KEY_SIZE(csbcpb, NX_KS_AES_128); + NX_CPB_SET_KEY_SIZE(csbcpb_aead, NX_KS_AES_128); + nx_ctx->ap = &nx_ctx->props[NX_PROPS_AES_128]; + break; + case AES_KEYSIZE_192: + NX_CPB_SET_KEY_SIZE(csbcpb, NX_KS_AES_192); + NX_CPB_SET_KEY_SIZE(csbcpb_aead, NX_KS_AES_192); + nx_ctx->ap = &nx_ctx->props[NX_PROPS_AES_192]; + break; + case AES_KEYSIZE_256: + NX_CPB_SET_KEY_SIZE(csbcpb, NX_KS_AES_256); + NX_CPB_SET_KEY_SIZE(csbcpb_aead, NX_KS_AES_256); + nx_ctx->ap = &nx_ctx->props[NX_PROPS_AES_256]; + break; + default: + return -EINVAL; + } + + csbcpb->cpb.hdr.mode = NX_MODE_AES_GCM; + memcpy(csbcpb->cpb.aes_gcm.key, in_key, key_len); + + csbcpb_aead->cpb.hdr.mode = NX_MODE_AES_GCA; + memcpy(csbcpb_aead->cpb.aes_gca.key, in_key, key_len); + + return 0; +} + +static int gcm4106_aes_nx_set_key(struct crypto_aead *tfm, + const u8 *in_key, + unsigned int key_len) +{ + struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&tfm->base); + char *nonce = nx_ctx->priv.gcm.nonce; + int rc; + + if (key_len < 4) + return -EINVAL; + + key_len -= 4; + + rc = gcm_aes_nx_set_key(tfm, in_key, key_len); + if (rc) + goto out; + + memcpy(nonce, in_key + key_len, 4); +out: + return rc; +} + +static int gcm_aes_nx_setauthsize(struct crypto_aead *tfm, + unsigned int authsize) +{ + if (authsize > crypto_aead_alg(tfm)->maxauthsize) + return -EINVAL; + + crypto_aead_crt(tfm)->authsize = authsize; + + return 0; +} + +static int gcm4106_aes_nx_setauthsize(struct crypto_aead *tfm, + unsigned int authsize) +{ + switch (authsize) { + case 8: + case 12: + case 16: + break; + default: + return -EINVAL; + } + + crypto_aead_crt(tfm)->authsize = authsize; + + return 0; +} + +static int nx_gca(struct nx_crypto_ctx *nx_ctx, + struct aead_request *req, + u8 *out) +{ + struct nx_csbcpb *csbcpb_aead = nx_ctx->csbcpb_aead; + int rc = -EINVAL; + struct scatter_walk walk; + struct nx_sg *nx_sg = nx_ctx->in_sg; + + if (req->assoclen > nx_ctx->ap->databytelen) + goto out; + + if (req->assoclen <= AES_BLOCK_SIZE) { + scatterwalk_start(&walk, req->assoc); + scatterwalk_copychunks(out, &walk, req->assoclen, + SCATTERWALK_FROM_SG); + scatterwalk_done(&walk, SCATTERWALK_FROM_SG, 0); + + rc = 0; + goto out; + } + + nx_sg = nx_walk_and_build(nx_sg, nx_ctx->ap->sglen, req->assoc, 0, + req->assoclen); + nx_ctx->op_aead.inlen = (nx_ctx->in_sg - nx_sg) * sizeof(struct nx_sg); + + rc = nx_hcall_sync(nx_ctx, &nx_ctx->op_aead, + req->base.flags & CRYPTO_TFM_REQ_MAY_SLEEP); + if (rc) + goto out; + + atomic_inc(&(nx_ctx->stats->aes_ops)); + atomic64_add(req->assoclen, &(nx_ctx->stats->aes_bytes)); + + memcpy(out, csbcpb_aead->cpb.aes_gca.out_pat, AES_BLOCK_SIZE); +out: + return rc; +} + +static int gcm_aes_nx_crypt(struct aead_request *req, int enc) +{ + struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(req->base.tfm); + struct nx_csbcpb *csbcpb = nx_ctx->csbcpb; + struct blkcipher_desc desc; + unsigned int nbytes = req->cryptlen; + int rc = -EINVAL; + + if (nbytes > nx_ctx->ap->databytelen) + goto out; + + desc.info = nx_ctx->priv.gcm.iv; + /* initialize the counter */ + *(u32 *)(desc.info + NX_GCM_CTR_OFFSET) = 1; + + /* For scenarios where the input message is zero length, AES CTR mode + * may be used. Set the source data to be a single block (16B) of all + * zeros, and set the input IV value to be the same as the GMAC IV + * value. - nx_wb 4.8.1.3 */ + if (nbytes == 0) { + char src[AES_BLOCK_SIZE] = {}; + struct scatterlist sg; + + desc.tfm = crypto_alloc_blkcipher("ctr(aes)", 0, 0); + if (IS_ERR(desc.tfm)) { + rc = -ENOMEM; + goto out; + } + + crypto_blkcipher_setkey(desc.tfm, csbcpb->cpb.aes_gcm.key, + NX_CPB_KEY_SIZE(csbcpb) == NX_KS_AES_128 ? 16 : + NX_CPB_KEY_SIZE(csbcpb) == NX_KS_AES_192 ? 24 : 32); + + sg_init_one(&sg, src, AES_BLOCK_SIZE); + if (enc) + crypto_blkcipher_encrypt_iv(&desc, req->dst, &sg, + AES_BLOCK_SIZE); + else + crypto_blkcipher_decrypt_iv(&desc, req->dst, &sg, + AES_BLOCK_SIZE); + crypto_free_blkcipher(desc.tfm); + + rc = 0; + goto out; + } + + desc.tfm = (struct crypto_blkcipher *)req->base.tfm; + + csbcpb->cpb.aes_gcm.bit_length_aad = req->assoclen * 8; + + if (req->assoclen) { + rc = nx_gca(nx_ctx, req, csbcpb->cpb.aes_gcm.in_pat_or_aad); + if (rc) + goto out; + } + + if (enc) + NX_CPB_FDM(csbcpb) |= NX_FDM_ENDE_ENCRYPT; + else + nbytes -= AES_BLOCK_SIZE; + + csbcpb->cpb.aes_gcm.bit_length_data = nbytes * 8; + + rc = nx_build_sg_lists(nx_ctx, &desc, req->dst, req->src, nbytes, + csbcpb->cpb.aes_gcm.iv_or_cnt); + if (rc) + goto out; + + rc = nx_hcall_sync(nx_ctx, &nx_ctx->op, + req->base.flags & CRYPTO_TFM_REQ_MAY_SLEEP); + if (rc) + goto out; + + atomic_inc(&(nx_ctx->stats->aes_ops)); + atomic64_add(csbcpb->csb.processed_byte_count, + &(nx_ctx->stats->aes_bytes)); + + if (enc) { + /* copy out the auth tag */ + scatterwalk_map_and_copy(csbcpb->cpb.aes_gcm.out_pat_or_mac, + req->dst, nbytes, + crypto_aead_authsize(crypto_aead_reqtfm(req)), + SCATTERWALK_TO_SG); + } else if (req->assoclen) { + u8 *itag = nx_ctx->priv.gcm.iauth_tag; + u8 *otag = csbcpb->cpb.aes_gcm.out_pat_or_mac; + + scatterwalk_map_and_copy(itag, req->dst, nbytes, + crypto_aead_authsize(crypto_aead_reqtfm(req)), + SCATTERWALK_FROM_SG); + rc = memcmp(itag, otag, + crypto_aead_authsize(crypto_aead_reqtfm(req))) ? + -EBADMSG : 0; + } +out: + return rc; +} + +static int gcm_aes_nx_encrypt(struct aead_request *req) +{ + struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(req->base.tfm); + char *iv = nx_ctx->priv.gcm.iv; + + memcpy(iv, req->iv, 12); + + return gcm_aes_nx_crypt(req, 1); +} + +static int gcm_aes_nx_decrypt(struct aead_request *req) +{ + struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(req->base.tfm); + char *iv = nx_ctx->priv.gcm.iv; + + memcpy(iv, req->iv, 12); + + return gcm_aes_nx_crypt(req, 0); +} + +static int gcm4106_aes_nx_encrypt(struct aead_request *req) +{ + struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(req->base.tfm); + char *iv = nx_ctx->priv.gcm.iv; + char *nonce = nx_ctx->priv.gcm.nonce; + + memcpy(iv, nonce, NX_GCM4106_NONCE_LEN); + memcpy(iv + NX_GCM4106_NONCE_LEN, req->iv, 8); + + return gcm_aes_nx_crypt(req, 1); +} + +static int gcm4106_aes_nx_decrypt(struct aead_request *req) +{ + struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(req->base.tfm); + char *iv = nx_ctx->priv.gcm.iv; + char *nonce = nx_ctx->priv.gcm.nonce; + + memcpy(iv, nonce, NX_GCM4106_NONCE_LEN); + memcpy(iv + NX_GCM4106_NONCE_LEN, req->iv, 8); + + return gcm_aes_nx_crypt(req, 0); +} + +/* tell the block cipher walk routines that this is a stream cipher by + * setting cra_blocksize to 1. Even using blkcipher_walk_virt_block + * during encrypt/decrypt doesn't solve this problem, because it calls + * blkcipher_walk_done under the covers, which doesn't use walk->blocksize, + * but instead uses this tfm->blocksize. */ +struct crypto_alg nx_gcm_aes_alg = { + .cra_name = "gcm(aes)", + .cra_driver_name = "gcm-aes-nx", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_AEAD, + .cra_blocksize = 1, + .cra_ctxsize = sizeof(struct nx_crypto_ctx), + .cra_type = &crypto_aead_type, + .cra_module = THIS_MODULE, + .cra_list = LIST_HEAD_INIT(nx_gcm_aes_alg.cra_list), + .cra_init = nx_crypto_ctx_aes_gcm_init, + .cra_exit = nx_crypto_ctx_exit, + .cra_aead = { + .ivsize = AES_BLOCK_SIZE, + .maxauthsize = AES_BLOCK_SIZE, + .setkey = gcm_aes_nx_set_key, + .setauthsize = gcm_aes_nx_setauthsize, + .encrypt = gcm_aes_nx_encrypt, + .decrypt = gcm_aes_nx_decrypt, + } +}; + +struct crypto_alg nx_gcm4106_aes_alg = { + .cra_name = "rfc4106(gcm(aes))", + .cra_driver_name = "rfc4106-gcm-aes-nx", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_AEAD, + .cra_blocksize = 1, + .cra_ctxsize = sizeof(struct nx_crypto_ctx), + .cra_type = &crypto_nivaead_type, + .cra_module = THIS_MODULE, + .cra_list = LIST_HEAD_INIT(nx_gcm4106_aes_alg.cra_list), + .cra_init = nx_crypto_ctx_aes_gcm_init, + .cra_exit = nx_crypto_ctx_exit, + .cra_aead = { + .ivsize = 8, + .maxauthsize = AES_BLOCK_SIZE, + .geniv = "seqiv", + .setkey = gcm4106_aes_nx_set_key, + .setauthsize = gcm4106_aes_nx_setauthsize, + .encrypt = gcm4106_aes_nx_encrypt, + .decrypt = gcm4106_aes_nx_decrypt, + } +}; diff --git a/drivers/crypto/nx/nx-aes-xcbc.c b/drivers/crypto/nx/nx-aes-xcbc.c new file mode 100644 index 000000000000..93923e4628c0 --- /dev/null +++ b/drivers/crypto/nx/nx-aes-xcbc.c @@ -0,0 +1,236 @@ +/** + * AES XCBC routines supporting the Power 7+ Nest Accelerators driver + * + * Copyright (C) 2011-2012 International Business Machines Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Author: Kent Yoder <[email protected]> + */ + +#include <crypto/internal/hash.h> +#include <crypto/aes.h> +#include <crypto/algapi.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/crypto.h> +#include <asm/vio.h> + +#include "nx_csbcpb.h" +#include "nx.h" + + +struct xcbc_state { + u8 state[AES_BLOCK_SIZE]; + unsigned int count; + u8 buffer[AES_BLOCK_SIZE]; +}; + +static int nx_xcbc_set_key(struct crypto_shash *desc, + const u8 *in_key, + unsigned int key_len) +{ + struct nx_crypto_ctx *nx_ctx = crypto_shash_ctx(desc); + + switch (key_len) { + case AES_KEYSIZE_128: + nx_ctx->ap = &nx_ctx->props[NX_PROPS_AES_128]; + break; + default: + return -EINVAL; + } + + memcpy(nx_ctx->priv.xcbc.key, in_key, key_len); + + return 0; +} + +static int nx_xcbc_init(struct shash_desc *desc) +{ + struct xcbc_state *sctx = shash_desc_ctx(desc); + struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&desc->tfm->base); + struct nx_csbcpb *csbcpb = nx_ctx->csbcpb; + struct nx_sg *out_sg; + + nx_ctx_init(nx_ctx, HCOP_FC_AES); + + memset(sctx, 0, sizeof *sctx); + + NX_CPB_SET_KEY_SIZE(csbcpb, NX_KS_AES_128); + csbcpb->cpb.hdr.mode = NX_MODE_AES_XCBC_MAC; + + memcpy(csbcpb->cpb.aes_xcbc.key, nx_ctx->priv.xcbc.key, AES_BLOCK_SIZE); + memset(nx_ctx->priv.xcbc.key, 0, sizeof *nx_ctx->priv.xcbc.key); + + out_sg = nx_build_sg_list(nx_ctx->out_sg, (u8 *)sctx->state, + AES_BLOCK_SIZE, nx_ctx->ap->sglen); + nx_ctx->op.outlen = (nx_ctx->out_sg - out_sg) * sizeof(struct nx_sg); + + return 0; +} + +static int nx_xcbc_update(struct shash_desc *desc, + const u8 *data, + unsigned int len) +{ + struct xcbc_state *sctx = shash_desc_ctx(desc); + struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&desc->tfm->base); + struct nx_csbcpb *csbcpb = nx_ctx->csbcpb; + struct nx_sg *in_sg; + u32 to_process, leftover; + int rc = 0; + + if (NX_CPB_FDM(csbcpb) & NX_FDM_CONTINUATION) { + /* we've hit the nx chip previously and we're updating again, + * so copy over the partial digest */ + memcpy(csbcpb->cpb.aes_xcbc.cv, + csbcpb->cpb.aes_xcbc.out_cv_mac, AES_BLOCK_SIZE); + } + + /* 2 cases for total data len: + * 1: <= AES_BLOCK_SIZE: copy into state, return 0 + * 2: > AES_BLOCK_SIZE: process X blocks, copy in leftover + */ + if (len + sctx->count <= AES_BLOCK_SIZE) { + memcpy(sctx->buffer + sctx->count, data, len); + sctx->count += len; + goto out; + } + + /* to_process: the AES_BLOCK_SIZE data chunk to process in this + * update */ + to_process = (sctx->count + len) & ~(AES_BLOCK_SIZE - 1); + leftover = (sctx->count + len) & (AES_BLOCK_SIZE - 1); + + /* the hardware will not accept a 0 byte operation for this algorithm + * and the operation MUST be finalized to be correct. So if we happen + * to get an update that falls on a block sized boundary, we must + * save off the last block to finalize with later. */ + if (!leftover) { + to_process -= AES_BLOCK_SIZE; + leftover = AES_BLOCK_SIZE; + } + + if (sctx->count) { + in_sg = nx_build_sg_list(nx_ctx->in_sg, sctx->buffer, + sctx->count, nx_ctx->ap->sglen); + in_sg = nx_build_sg_list(in_sg, (u8 *)data, + to_process - sctx->count, + nx_ctx->ap->sglen); + nx_ctx->op.inlen = (nx_ctx->in_sg - in_sg) * + sizeof(struct nx_sg); + } else { + in_sg = nx_build_sg_list(nx_ctx->in_sg, (u8 *)data, to_process, + nx_ctx->ap->sglen); + nx_ctx->op.inlen = (nx_ctx->in_sg - in_sg) * + sizeof(struct nx_sg); + } + + NX_CPB_FDM(csbcpb) |= NX_FDM_INTERMEDIATE; + + if (!nx_ctx->op.inlen || !nx_ctx->op.outlen) { + rc = -EINVAL; + goto out; + } + + rc = nx_hcall_sync(nx_ctx, &nx_ctx->op, + desc->flags & CRYPTO_TFM_REQ_MAY_SLEEP); + if (rc) + goto out; + + atomic_inc(&(nx_ctx->stats->aes_ops)); + + /* copy the leftover back into the state struct */ + memcpy(sctx->buffer, data + len - leftover, leftover); + sctx->count = leftover; + + /* everything after the first update is continuation */ + NX_CPB_FDM(csbcpb) |= NX_FDM_CONTINUATION; +out: + return rc; +} + +static int nx_xcbc_final(struct shash_desc *desc, u8 *out) +{ + struct xcbc_state *sctx = shash_desc_ctx(desc); + struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&desc->tfm->base); + struct nx_csbcpb *csbcpb = nx_ctx->csbcpb; + struct nx_sg *in_sg, *out_sg; + int rc = 0; + + if (NX_CPB_FDM(csbcpb) & NX_FDM_CONTINUATION) { + /* we've hit the nx chip previously, now we're finalizing, + * so copy over the partial digest */ + memcpy(csbcpb->cpb.aes_xcbc.cv, + csbcpb->cpb.aes_xcbc.out_cv_mac, AES_BLOCK_SIZE); + } else if (sctx->count == 0) { + /* we've never seen an update, so this is a 0 byte op. The + * hardware cannot handle a 0 byte op, so just copy out the + * known 0 byte result. This is cheaper than allocating a + * software context to do a 0 byte op */ + u8 data[] = { 0x75, 0xf0, 0x25, 0x1d, 0x52, 0x8a, 0xc0, 0x1c, + 0x45, 0x73, 0xdf, 0xd5, 0x84, 0xd7, 0x9f, 0x29 }; + memcpy(out, data, sizeof(data)); + goto out; + } + + /* final is represented by continuing the operation and indicating that + * this is not an intermediate operation */ + NX_CPB_FDM(csbcpb) &= ~NX_FDM_INTERMEDIATE; + + in_sg = nx_build_sg_list(nx_ctx->in_sg, (u8 *)sctx->buffer, + sctx->count, nx_ctx->ap->sglen); + out_sg = nx_build_sg_list(nx_ctx->out_sg, out, AES_BLOCK_SIZE, + nx_ctx->ap->sglen); + + nx_ctx->op.inlen = (nx_ctx->in_sg - in_sg) * sizeof(struct nx_sg); + nx_ctx->op.outlen = (nx_ctx->out_sg - out_sg) * sizeof(struct nx_sg); + + if (!nx_ctx->op.outlen) { + rc = -EINVAL; + goto out; + } + + rc = nx_hcall_sync(nx_ctx, &nx_ctx->op, + desc->flags & CRYPTO_TFM_REQ_MAY_SLEEP); + if (rc) + goto out; + + atomic_inc(&(nx_ctx->stats->aes_ops)); + + memcpy(out, csbcpb->cpb.aes_xcbc.out_cv_mac, AES_BLOCK_SIZE); +out: + return rc; +} + +struct shash_alg nx_shash_aes_xcbc_alg = { + .digestsize = AES_BLOCK_SIZE, + .init = nx_xcbc_init, + .update = nx_xcbc_update, + .final = nx_xcbc_final, + .setkey = nx_xcbc_set_key, + .descsize = sizeof(struct xcbc_state), + .statesize = sizeof(struct xcbc_state), + .base = { + .cra_name = "xcbc(aes)", + .cra_driver_name = "xcbc-aes-nx", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_SHASH, + .cra_blocksize = AES_BLOCK_SIZE, + .cra_module = THIS_MODULE, + .cra_ctxsize = sizeof(struct nx_crypto_ctx), + .cra_init = nx_crypto_ctx_aes_xcbc_init, + .cra_exit = nx_crypto_ctx_exit, + } +}; diff --git a/drivers/crypto/nx/nx-sha256.c b/drivers/crypto/nx/nx-sha256.c new file mode 100644 index 000000000000..9767315f8c0b --- /dev/null +++ b/drivers/crypto/nx/nx-sha256.c @@ -0,0 +1,246 @@ +/** + * SHA-256 routines supporting the Power 7+ Nest Accelerators driver + * + * Copyright (C) 2011-2012 International Business Machines Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Author: Kent Yoder <[email protected]> + */ + +#include <crypto/internal/hash.h> +#include <crypto/sha.h> +#include <linux/module.h> +#include <asm/vio.h> + +#include "nx_csbcpb.h" +#include "nx.h" + + +static int nx_sha256_init(struct shash_desc *desc) +{ + struct sha256_state *sctx = shash_desc_ctx(desc); + struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&desc->tfm->base); + struct nx_sg *out_sg; + + nx_ctx_init(nx_ctx, HCOP_FC_SHA); + + memset(sctx, 0, sizeof *sctx); + + nx_ctx->ap = &nx_ctx->props[NX_PROPS_SHA256]; + + NX_CPB_SET_DIGEST_SIZE(nx_ctx->csbcpb, NX_DS_SHA256); + out_sg = nx_build_sg_list(nx_ctx->out_sg, (u8 *)sctx->state, + SHA256_DIGEST_SIZE, nx_ctx->ap->sglen); + nx_ctx->op.outlen = (nx_ctx->out_sg - out_sg) * sizeof(struct nx_sg); + + return 0; +} + +static int nx_sha256_update(struct shash_desc *desc, const u8 *data, + unsigned int len) +{ + struct sha256_state *sctx = shash_desc_ctx(desc); + struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&desc->tfm->base); + struct nx_csbcpb *csbcpb = (struct nx_csbcpb *)nx_ctx->csbcpb; + struct nx_sg *in_sg; + u64 to_process, leftover; + int rc = 0; + + if (NX_CPB_FDM(csbcpb) & NX_FDM_CONTINUATION) { + /* we've hit the nx chip previously and we're updating again, + * so copy over the partial digest */ + memcpy(csbcpb->cpb.sha256.input_partial_digest, + csbcpb->cpb.sha256.message_digest, SHA256_DIGEST_SIZE); + } + + /* 2 cases for total data len: + * 1: <= SHA256_BLOCK_SIZE: copy into state, return 0 + * 2: > SHA256_BLOCK_SIZE: process X blocks, copy in leftover + */ + if (len + sctx->count <= SHA256_BLOCK_SIZE) { + memcpy(sctx->buf + sctx->count, data, len); + sctx->count += len; + goto out; + } + + /* to_process: the SHA256_BLOCK_SIZE data chunk to process in this + * update */ + to_process = (sctx->count + len) & ~(SHA256_BLOCK_SIZE - 1); + leftover = (sctx->count + len) & (SHA256_BLOCK_SIZE - 1); + + if (sctx->count) { + in_sg = nx_build_sg_list(nx_ctx->in_sg, (u8 *)sctx->buf, + sctx->count, nx_ctx->ap->sglen); + in_sg = nx_build_sg_list(in_sg, (u8 *)data, + to_process - sctx->count, + nx_ctx->ap->sglen); + nx_ctx->op.inlen = (nx_ctx->in_sg - in_sg) * + sizeof(struct nx_sg); + } else { + in_sg = nx_build_sg_list(nx_ctx->in_sg, (u8 *)data, + to_process, nx_ctx->ap->sglen); + nx_ctx->op.inlen = (nx_ctx->in_sg - in_sg) * + sizeof(struct nx_sg); + } + + NX_CPB_FDM(csbcpb) |= NX_FDM_INTERMEDIATE; + + if (!nx_ctx->op.inlen || !nx_ctx->op.outlen) { + rc = -EINVAL; + goto out; + } + + rc = nx_hcall_sync(nx_ctx, &nx_ctx->op, + desc->flags & CRYPTO_TFM_REQ_MAY_SLEEP); + if (rc) + goto out; + + atomic_inc(&(nx_ctx->stats->sha256_ops)); + + /* copy the leftover back into the state struct */ + memcpy(sctx->buf, data + len - leftover, leftover); + sctx->count = leftover; + + csbcpb->cpb.sha256.message_bit_length += (u64) + (csbcpb->cpb.sha256.spbc * 8); + + /* everything after the first update is continuation */ + NX_CPB_FDM(csbcpb) |= NX_FDM_CONTINUATION; +out: + return rc; +} + +static int nx_sha256_final(struct shash_desc *desc, u8 *out) +{ + struct sha256_state *sctx = shash_desc_ctx(desc); + struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&desc->tfm->base); + struct nx_csbcpb *csbcpb = (struct nx_csbcpb *)nx_ctx->csbcpb; + struct nx_sg *in_sg, *out_sg; + int rc; + + if (NX_CPB_FDM(csbcpb) & NX_FDM_CONTINUATION) { + /* we've hit the nx chip previously, now we're finalizing, + * so copy over the partial digest */ + memcpy(csbcpb->cpb.sha256.input_partial_digest, + csbcpb->cpb.sha256.message_digest, SHA256_DIGEST_SIZE); + } + + /* final is represented by continuing the operation and indicating that + * this is not an intermediate operation */ + NX_CPB_FDM(csbcpb) &= ~NX_FDM_INTERMEDIATE; + + csbcpb->cpb.sha256.message_bit_length += (u64)(sctx->count * 8); + + in_sg = nx_build_sg_list(nx_ctx->in_sg, (u8 *)sctx->buf, + sctx->count, nx_ctx->ap->sglen); + out_sg = nx_build_sg_list(nx_ctx->out_sg, out, SHA256_DIGEST_SIZE, + nx_ctx->ap->sglen); + nx_ctx->op.inlen = (nx_ctx->in_sg - in_sg) * sizeof(struct nx_sg); + nx_ctx->op.outlen = (nx_ctx->out_sg - out_sg) * sizeof(struct nx_sg); + + if (!nx_ctx->op.outlen) { + rc = -EINVAL; + goto out; + } + + rc = nx_hcall_sync(nx_ctx, &nx_ctx->op, + desc->flags & CRYPTO_TFM_REQ_MAY_SLEEP); + if (rc) + goto out; + + atomic_inc(&(nx_ctx->stats->sha256_ops)); + + atomic64_add(csbcpb->cpb.sha256.message_bit_length, + &(nx_ctx->stats->sha256_bytes)); + memcpy(out, csbcpb->cpb.sha256.message_digest, SHA256_DIGEST_SIZE); +out: + return rc; +} + +static int nx_sha256_export(struct shash_desc *desc, void *out) +{ + struct sha256_state *sctx = shash_desc_ctx(desc); + struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&desc->tfm->base); + struct nx_csbcpb *csbcpb = (struct nx_csbcpb *)nx_ctx->csbcpb; + struct sha256_state *octx = out; + + octx->count = sctx->count + + (csbcpb->cpb.sha256.message_bit_length / 8); + memcpy(octx->buf, sctx->buf, sizeof(octx->buf)); + + /* if no data has been processed yet, we need to export SHA256's + * initial data, in case this context gets imported into a software + * context */ + if (csbcpb->cpb.sha256.message_bit_length) + memcpy(octx->state, csbcpb->cpb.sha256.message_digest, + SHA256_DIGEST_SIZE); + else { + octx->state[0] = SHA256_H0; + octx->state[1] = SHA256_H1; + octx->state[2] = SHA256_H2; + octx->state[3] = SHA256_H3; + octx->state[4] = SHA256_H4; + octx->state[5] = SHA256_H5; + octx->state[6] = SHA256_H6; + octx->state[7] = SHA256_H7; + } + + return 0; +} + +static int nx_sha256_import(struct shash_desc *desc, const void *in) +{ + struct sha256_state *sctx = shash_desc_ctx(desc); + struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&desc->tfm->base); + struct nx_csbcpb *csbcpb = (struct nx_csbcpb *)nx_ctx->csbcpb; + const struct sha256_state *ictx = in; + + memcpy(sctx->buf, ictx->buf, sizeof(ictx->buf)); + + sctx->count = ictx->count & 0x3f; + csbcpb->cpb.sha256.message_bit_length = (ictx->count & ~0x3f) * 8; + + if (csbcpb->cpb.sha256.message_bit_length) { + memcpy(csbcpb->cpb.sha256.message_digest, ictx->state, + SHA256_DIGEST_SIZE); + + NX_CPB_FDM(csbcpb) |= NX_FDM_CONTINUATION; + NX_CPB_FDM(csbcpb) |= NX_FDM_INTERMEDIATE; + } + + return 0; +} + +struct shash_alg nx_shash_sha256_alg = { + .digestsize = SHA256_DIGEST_SIZE, + .init = nx_sha256_init, + .update = nx_sha256_update, + .final = nx_sha256_final, + .export = nx_sha256_export, + .import = nx_sha256_import, + .descsize = sizeof(struct sha256_state), + .statesize = sizeof(struct sha256_state), + .base = { + .cra_name = "sha256", + .cra_driver_name = "sha256-nx", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_SHASH, + .cra_blocksize = SHA256_BLOCK_SIZE, + .cra_module = THIS_MODULE, + .cra_ctxsize = sizeof(struct nx_crypto_ctx), + .cra_init = nx_crypto_ctx_sha_init, + .cra_exit = nx_crypto_ctx_exit, + } +}; diff --git a/drivers/crypto/nx/nx-sha512.c b/drivers/crypto/nx/nx-sha512.c new file mode 100644 index 000000000000..3177b8c3d5f1 --- /dev/null +++ b/drivers/crypto/nx/nx-sha512.c @@ -0,0 +1,265 @@ +/** + * SHA-512 routines supporting the Power 7+ Nest Accelerators driver + * + * Copyright (C) 2011-2012 International Business Machines Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Author: Kent Yoder <[email protected]> + */ + +#include <crypto/internal/hash.h> +#include <crypto/sha.h> +#include <linux/module.h> +#include <asm/vio.h> + +#include "nx_csbcpb.h" +#include "nx.h" + + +static int nx_sha512_init(struct shash_desc *desc) +{ + struct sha512_state *sctx = shash_desc_ctx(desc); + struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&desc->tfm->base); + struct nx_sg *out_sg; + + nx_ctx_init(nx_ctx, HCOP_FC_SHA); + + memset(sctx, 0, sizeof *sctx); + + nx_ctx->ap = &nx_ctx->props[NX_PROPS_SHA512]; + + NX_CPB_SET_DIGEST_SIZE(nx_ctx->csbcpb, NX_DS_SHA512); + out_sg = nx_build_sg_list(nx_ctx->out_sg, (u8 *)sctx->state, + SHA512_DIGEST_SIZE, nx_ctx->ap->sglen); + nx_ctx->op.outlen = (nx_ctx->out_sg - out_sg) * sizeof(struct nx_sg); + + return 0; +} + +static int nx_sha512_update(struct shash_desc *desc, const u8 *data, + unsigned int len) +{ + struct sha512_state *sctx = shash_desc_ctx(desc); + struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&desc->tfm->base); + struct nx_csbcpb *csbcpb = (struct nx_csbcpb *)nx_ctx->csbcpb; + struct nx_sg *in_sg; + u64 to_process, leftover, spbc_bits; + int rc = 0; + + if (NX_CPB_FDM(csbcpb) & NX_FDM_CONTINUATION) { + /* we've hit the nx chip previously and we're updating again, + * so copy over the partial digest */ + memcpy(csbcpb->cpb.sha512.input_partial_digest, + csbcpb->cpb.sha512.message_digest, SHA512_DIGEST_SIZE); + } + + /* 2 cases for total data len: + * 1: <= SHA512_BLOCK_SIZE: copy into state, return 0 + * 2: > SHA512_BLOCK_SIZE: process X blocks, copy in leftover + */ + if ((u64)len + sctx->count[0] <= SHA512_BLOCK_SIZE) { + memcpy(sctx->buf + sctx->count[0], data, len); + sctx->count[0] += len; + goto out; + } + + /* to_process: the SHA512_BLOCK_SIZE data chunk to process in this + * update */ + to_process = (sctx->count[0] + len) & ~(SHA512_BLOCK_SIZE - 1); + leftover = (sctx->count[0] + len) & (SHA512_BLOCK_SIZE - 1); + + if (sctx->count[0]) { + in_sg = nx_build_sg_list(nx_ctx->in_sg, (u8 *)sctx->buf, + sctx->count[0], nx_ctx->ap->sglen); + in_sg = nx_build_sg_list(in_sg, (u8 *)data, + to_process - sctx->count[0], + nx_ctx->ap->sglen); + nx_ctx->op.inlen = (nx_ctx->in_sg - in_sg) * + sizeof(struct nx_sg); + } else { + in_sg = nx_build_sg_list(nx_ctx->in_sg, (u8 *)data, + to_process, nx_ctx->ap->sglen); + nx_ctx->op.inlen = (nx_ctx->in_sg - in_sg) * + sizeof(struct nx_sg); + } + + NX_CPB_FDM(csbcpb) |= NX_FDM_INTERMEDIATE; + + if (!nx_ctx->op.inlen || !nx_ctx->op.outlen) { + rc = -EINVAL; + goto out; + } + + rc = nx_hcall_sync(nx_ctx, &nx_ctx->op, + desc->flags & CRYPTO_TFM_REQ_MAY_SLEEP); + if (rc) + goto out; + + atomic_inc(&(nx_ctx->stats->sha512_ops)); + + /* copy the leftover back into the state struct */ + memcpy(sctx->buf, data + len - leftover, leftover); + sctx->count[0] = leftover; + + spbc_bits = csbcpb->cpb.sha512.spbc * 8; + csbcpb->cpb.sha512.message_bit_length_lo += spbc_bits; + if (csbcpb->cpb.sha512.message_bit_length_lo < spbc_bits) + csbcpb->cpb.sha512.message_bit_length_hi++; + + /* everything after the first update is continuation */ + NX_CPB_FDM(csbcpb) |= NX_FDM_CONTINUATION; +out: + return rc; +} + +static int nx_sha512_final(struct shash_desc *desc, u8 *out) +{ + struct sha512_state *sctx = shash_desc_ctx(desc); + struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&desc->tfm->base); + struct nx_csbcpb *csbcpb = (struct nx_csbcpb *)nx_ctx->csbcpb; + struct nx_sg *in_sg, *out_sg; + u64 count0; + int rc; + + if (NX_CPB_FDM(csbcpb) & NX_FDM_CONTINUATION) { + /* we've hit the nx chip previously, now we're finalizing, + * so copy over the partial digest */ + memcpy(csbcpb->cpb.sha512.input_partial_digest, + csbcpb->cpb.sha512.message_digest, SHA512_DIGEST_SIZE); + } + + /* final is represented by continuing the operation and indicating that + * this is not an intermediate operation */ + NX_CPB_FDM(csbcpb) &= ~NX_FDM_INTERMEDIATE; + + count0 = sctx->count[0] * 8; + + csbcpb->cpb.sha512.message_bit_length_lo += count0; + if (csbcpb->cpb.sha512.message_bit_length_lo < count0) + csbcpb->cpb.sha512.message_bit_length_hi++; + + in_sg = nx_build_sg_list(nx_ctx->in_sg, sctx->buf, sctx->count[0], + nx_ctx->ap->sglen); + out_sg = nx_build_sg_list(nx_ctx->out_sg, out, SHA512_DIGEST_SIZE, + nx_ctx->ap->sglen); + nx_ctx->op.inlen = (nx_ctx->in_sg - in_sg) * sizeof(struct nx_sg); + nx_ctx->op.outlen = (nx_ctx->out_sg - out_sg) * sizeof(struct nx_sg); + + if (!nx_ctx->op.outlen) { + rc = -EINVAL; + goto out; + } + + rc = nx_hcall_sync(nx_ctx, &nx_ctx->op, + desc->flags & CRYPTO_TFM_REQ_MAY_SLEEP); + if (rc) + goto out; + + atomic_inc(&(nx_ctx->stats->sha512_ops)); + atomic64_add(csbcpb->cpb.sha512.message_bit_length_lo, + &(nx_ctx->stats->sha512_bytes)); + + memcpy(out, csbcpb->cpb.sha512.message_digest, SHA512_DIGEST_SIZE); +out: + return rc; +} + +static int nx_sha512_export(struct shash_desc *desc, void *out) +{ + struct sha512_state *sctx = shash_desc_ctx(desc); + struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&desc->tfm->base); + struct nx_csbcpb *csbcpb = (struct nx_csbcpb *)nx_ctx->csbcpb; + struct sha512_state *octx = out; + + /* move message_bit_length (128 bits) into count and convert its value + * to bytes */ + octx->count[0] = csbcpb->cpb.sha512.message_bit_length_lo >> 3 | + ((csbcpb->cpb.sha512.message_bit_length_hi & 7) << 61); + octx->count[1] = csbcpb->cpb.sha512.message_bit_length_hi >> 3; + + octx->count[0] += sctx->count[0]; + if (octx->count[0] < sctx->count[0]) + octx->count[1]++; + + memcpy(octx->buf, sctx->buf, sizeof(octx->buf)); + + /* if no data has been processed yet, we need to export SHA512's + * initial data, in case this context gets imported into a software + * context */ + if (csbcpb->cpb.sha512.message_bit_length_hi || + csbcpb->cpb.sha512.message_bit_length_lo) + memcpy(octx->state, csbcpb->cpb.sha512.message_digest, + SHA512_DIGEST_SIZE); + else { + octx->state[0] = SHA512_H0; + octx->state[1] = SHA512_H1; + octx->state[2] = SHA512_H2; + octx->state[3] = SHA512_H3; + octx->state[4] = SHA512_H4; + octx->state[5] = SHA512_H5; + octx->state[6] = SHA512_H6; + octx->state[7] = SHA512_H7; + } + + return 0; +} + +static int nx_sha512_import(struct shash_desc *desc, const void *in) +{ + struct sha512_state *sctx = shash_desc_ctx(desc); + struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(&desc->tfm->base); + struct nx_csbcpb *csbcpb = (struct nx_csbcpb *)nx_ctx->csbcpb; + const struct sha512_state *ictx = in; + + memcpy(sctx->buf, ictx->buf, sizeof(ictx->buf)); + sctx->count[0] = ictx->count[0] & 0x3f; + csbcpb->cpb.sha512.message_bit_length_lo = (ictx->count[0] & ~0x3f) + << 3; + csbcpb->cpb.sha512.message_bit_length_hi = ictx->count[1] << 3 | + ictx->count[0] >> 61; + + if (csbcpb->cpb.sha512.message_bit_length_hi || + csbcpb->cpb.sha512.message_bit_length_lo) { + memcpy(csbcpb->cpb.sha512.message_digest, ictx->state, + SHA512_DIGEST_SIZE); + + NX_CPB_FDM(csbcpb) |= NX_FDM_CONTINUATION; + NX_CPB_FDM(csbcpb) |= NX_FDM_INTERMEDIATE; + } + + return 0; +} + +struct shash_alg nx_shash_sha512_alg = { + .digestsize = SHA512_DIGEST_SIZE, + .init = nx_sha512_init, + .update = nx_sha512_update, + .final = nx_sha512_final, + .export = nx_sha512_export, + .import = nx_sha512_import, + .descsize = sizeof(struct sha512_state), + .statesize = sizeof(struct sha512_state), + .base = { + .cra_name = "sha512", + .cra_driver_name = "sha512-nx", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_SHASH, + .cra_blocksize = SHA512_BLOCK_SIZE, + .cra_module = THIS_MODULE, + .cra_ctxsize = sizeof(struct nx_crypto_ctx), + .cra_init = nx_crypto_ctx_sha_init, + .cra_exit = nx_crypto_ctx_exit, + } +}; diff --git a/drivers/crypto/nx/nx.c b/drivers/crypto/nx/nx.c new file mode 100644 index 000000000000..d7f179cc2e98 --- /dev/null +++ b/drivers/crypto/nx/nx.c @@ -0,0 +1,716 @@ +/** + * Routines supporting the Power 7+ Nest Accelerators driver + * + * Copyright (C) 2011-2012 International Business Machines Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Author: Kent Yoder <[email protected]> + */ + +#include <crypto/internal/hash.h> +#include <crypto/hash.h> +#include <crypto/aes.h> +#include <crypto/sha.h> +#include <crypto/algapi.h> +#include <crypto/scatterwalk.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/mm.h> +#include <linux/crypto.h> +#include <linux/scatterlist.h> +#include <linux/device.h> +#include <linux/of.h> +#include <asm/pSeries_reconfig.h> +#include <asm/abs_addr.h> +#include <asm/hvcall.h> +#include <asm/vio.h> + +#include "nx_csbcpb.h" +#include "nx.h" + + +/** + * nx_hcall_sync - make an H_COP_OP hcall for the passed in op structure + * + * @nx_ctx: the crypto context handle + * @op: PFO operation struct to pass in + * @may_sleep: flag indicating the request can sleep + * + * Make the hcall, retrying while the hardware is busy. If we cannot yield + * the thread, limit the number of retries to 10 here. + */ +int nx_hcall_sync(struct nx_crypto_ctx *nx_ctx, + struct vio_pfo_op *op, + u32 may_sleep) +{ + int rc, retries = 10; + struct vio_dev *viodev = nx_driver.viodev; + + atomic_inc(&(nx_ctx->stats->sync_ops)); + + do { + rc = vio_h_cop_sync(viodev, op); + } while ((rc == -EBUSY && !may_sleep && retries--) || + (rc == -EBUSY && may_sleep && cond_resched())); + + if (rc) { + dev_dbg(&viodev->dev, "vio_h_cop_sync failed: rc: %d " + "hcall rc: %ld\n", rc, op->hcall_err); + atomic_inc(&(nx_ctx->stats->errors)); + atomic_set(&(nx_ctx->stats->last_error), op->hcall_err); + atomic_set(&(nx_ctx->stats->last_error_pid), current->pid); + } + + return rc; +} + +/** + * nx_build_sg_list - build an NX scatter list describing a single buffer + * + * @sg_head: pointer to the first scatter list element to build + * @start_addr: pointer to the linear buffer + * @len: length of the data at @start_addr + * @sgmax: the largest number of scatter list elements we're allowed to create + * + * This function will start writing nx_sg elements at @sg_head and keep + * writing them until all of the data from @start_addr is described or + * until sgmax elements have been written. Scatter list elements will be + * created such that none of the elements describes a buffer that crosses a 4K + * boundary. + */ +struct nx_sg *nx_build_sg_list(struct nx_sg *sg_head, + u8 *start_addr, + unsigned int len, + u32 sgmax) +{ + unsigned int sg_len = 0; + struct nx_sg *sg; + u64 sg_addr = (u64)start_addr; + u64 end_addr; + + /* determine the start and end for this address range - slightly + * different if this is in VMALLOC_REGION */ + if (is_vmalloc_addr(start_addr)) + sg_addr = phys_to_abs(page_to_phys(vmalloc_to_page(start_addr))) + + offset_in_page(sg_addr); + else + sg_addr = virt_to_abs(sg_addr); + + end_addr = sg_addr + len; + + /* each iteration will write one struct nx_sg element and add the + * length of data described by that element to sg_len. Once @len bytes + * have been described (or @sgmax elements have been written), the + * loop ends. min_t is used to ensure @end_addr falls on the same page + * as sg_addr, if not, we need to create another nx_sg element for the + * data on the next page */ + for (sg = sg_head; sg_len < len; sg++) { + sg->addr = sg_addr; + sg_addr = min_t(u64, NX_PAGE_NUM(sg_addr + NX_PAGE_SIZE), end_addr); + sg->len = sg_addr - sg->addr; + sg_len += sg->len; + + if ((sg - sg_head) == sgmax) { + pr_err("nx: scatter/gather list overflow, pid: %d\n", + current->pid); + return NULL; + } + } + + /* return the moved sg_head pointer */ + return sg; +} + +/** + * nx_walk_and_build - walk a linux scatterlist and build an nx scatterlist + * + * @nx_dst: pointer to the first nx_sg element to write + * @sglen: max number of nx_sg entries we're allowed to write + * @sg_src: pointer to the source linux scatterlist to walk + * @start: number of bytes to fast-forward past at the beginning of @sg_src + * @src_len: number of bytes to walk in @sg_src + */ +struct nx_sg *nx_walk_and_build(struct nx_sg *nx_dst, + unsigned int sglen, + struct scatterlist *sg_src, + unsigned int start, + unsigned int src_len) +{ + struct scatter_walk walk; + struct nx_sg *nx_sg = nx_dst; + unsigned int n, offset = 0, len = src_len; + char *dst; + + /* we need to fast forward through @start bytes first */ + for (;;) { + scatterwalk_start(&walk, sg_src); + + if (start < offset + sg_src->length) + break; + + offset += sg_src->length; + sg_src = scatterwalk_sg_next(sg_src); + } + + /* start - offset is the number of bytes to advance in the scatterlist + * element we're currently looking at */ + scatterwalk_advance(&walk, start - offset); + + while (len && nx_sg) { + n = scatterwalk_clamp(&walk, len); + if (!n) { + scatterwalk_start(&walk, sg_next(walk.sg)); + n = scatterwalk_clamp(&walk, len); + } + dst = scatterwalk_map(&walk); + + nx_sg = nx_build_sg_list(nx_sg, dst, n, sglen); + len -= n; + + scatterwalk_unmap(dst); + scatterwalk_advance(&walk, n); + scatterwalk_done(&walk, SCATTERWALK_FROM_SG, len); + } + + /* return the moved destination pointer */ + return nx_sg; +} + +/** + * nx_build_sg_lists - walk the input scatterlists and build arrays of NX + * scatterlists based on them. + * + * @nx_ctx: NX crypto context for the lists we're building + * @desc: the block cipher descriptor for the operation + * @dst: destination scatterlist + * @src: source scatterlist + * @nbytes: length of data described in the scatterlists + * @iv: destination for the iv data, if the algorithm requires it + * + * This is common code shared by all the AES algorithms. It uses the block + * cipher walk routines to traverse input and output scatterlists, building + * corresponding NX scatterlists + */ +int nx_build_sg_lists(struct nx_crypto_ctx *nx_ctx, + struct blkcipher_desc *desc, + struct scatterlist *dst, + struct scatterlist *src, + unsigned int nbytes, + u8 *iv) +{ + struct nx_sg *nx_insg = nx_ctx->in_sg; + struct nx_sg *nx_outsg = nx_ctx->out_sg; + struct blkcipher_walk walk; + int rc; + + blkcipher_walk_init(&walk, dst, src, nbytes); + rc = blkcipher_walk_virt_block(desc, &walk, AES_BLOCK_SIZE); + if (rc) + goto out; + + if (iv) + memcpy(iv, walk.iv, AES_BLOCK_SIZE); + + while (walk.nbytes) { + nx_insg = nx_build_sg_list(nx_insg, walk.src.virt.addr, + walk.nbytes, nx_ctx->ap->sglen); + nx_outsg = nx_build_sg_list(nx_outsg, walk.dst.virt.addr, + walk.nbytes, nx_ctx->ap->sglen); + + rc = blkcipher_walk_done(desc, &walk, 0); + if (rc) + break; + } + + if (walk.nbytes) { + nx_insg = nx_build_sg_list(nx_insg, walk.src.virt.addr, + walk.nbytes, nx_ctx->ap->sglen); + nx_outsg = nx_build_sg_list(nx_outsg, walk.dst.virt.addr, + walk.nbytes, nx_ctx->ap->sglen); + + rc = 0; + } + + /* these lengths should be negative, which will indicate to phyp that + * the input and output parameters are scatterlists, not linear + * buffers */ + nx_ctx->op.inlen = (nx_ctx->in_sg - nx_insg) * sizeof(struct nx_sg); + nx_ctx->op.outlen = (nx_ctx->out_sg - nx_outsg) * sizeof(struct nx_sg); +out: + return rc; +} + +/** + * nx_ctx_init - initialize an nx_ctx's vio_pfo_op struct + * + * @nx_ctx: the nx context to initialize + * @function: the function code for the op + */ +void nx_ctx_init(struct nx_crypto_ctx *nx_ctx, unsigned int function) +{ + memset(nx_ctx->kmem, 0, nx_ctx->kmem_len); + nx_ctx->csbcpb->csb.valid |= NX_CSB_VALID_BIT; + + nx_ctx->op.flags = function; + nx_ctx->op.csbcpb = virt_to_abs(nx_ctx->csbcpb); + nx_ctx->op.in = virt_to_abs(nx_ctx->in_sg); + nx_ctx->op.out = virt_to_abs(nx_ctx->out_sg); + + if (nx_ctx->csbcpb_aead) { + nx_ctx->csbcpb_aead->csb.valid |= NX_CSB_VALID_BIT; + + nx_ctx->op_aead.flags = function; + nx_ctx->op_aead.csbcpb = virt_to_abs(nx_ctx->csbcpb_aead); + nx_ctx->op_aead.in = virt_to_abs(nx_ctx->in_sg); + nx_ctx->op_aead.out = virt_to_abs(nx_ctx->out_sg); + } +} + +static void nx_of_update_status(struct device *dev, + struct property *p, + struct nx_of *props) +{ + if (!strncmp(p->value, "okay", p->length)) { + props->status = NX_WAITING; + props->flags |= NX_OF_FLAG_STATUS_SET; + } else { + dev_info(dev, "%s: status '%s' is not 'okay'\n", __func__, + (char *)p->value); + } +} + +static void nx_of_update_sglen(struct device *dev, + struct property *p, + struct nx_of *props) +{ + if (p->length != sizeof(props->max_sg_len)) { + dev_err(dev, "%s: unexpected format for " + "ibm,max-sg-len property\n", __func__); + dev_dbg(dev, "%s: ibm,max-sg-len is %d bytes " + "long, expected %zd bytes\n", __func__, + p->length, sizeof(props->max_sg_len)); + return; + } + + props->max_sg_len = *(u32 *)p->value; + props->flags |= NX_OF_FLAG_MAXSGLEN_SET; +} + +static void nx_of_update_msc(struct device *dev, + struct property *p, + struct nx_of *props) +{ + struct msc_triplet *trip; + struct max_sync_cop *msc; + unsigned int bytes_so_far, i, lenp; + + msc = (struct max_sync_cop *)p->value; + lenp = p->length; + + /* You can't tell if the data read in for this property is sane by its + * size alone. This is because there are sizes embedded in the data + * structure. The best we can do is check lengths as we parse and bail + * as soon as a length error is detected. */ + bytes_so_far = 0; + + while ((bytes_so_far + sizeof(struct max_sync_cop)) <= lenp) { + bytes_so_far += sizeof(struct max_sync_cop); + + trip = msc->trip; + + for (i = 0; + ((bytes_so_far + sizeof(struct msc_triplet)) <= lenp) && + i < msc->triplets; + i++) { + if (msc->fc > NX_MAX_FC || msc->mode > NX_MAX_MODE) { + dev_err(dev, "unknown function code/mode " + "combo: %d/%d (ignored)\n", msc->fc, + msc->mode); + goto next_loop; + } + + switch (trip->keybitlen) { + case 128: + case 160: + props->ap[msc->fc][msc->mode][0].databytelen = + trip->databytelen; + props->ap[msc->fc][msc->mode][0].sglen = + trip->sglen; + break; + case 192: + props->ap[msc->fc][msc->mode][1].databytelen = + trip->databytelen; + props->ap[msc->fc][msc->mode][1].sglen = + trip->sglen; + break; + case 256: + if (msc->fc == NX_FC_AES) { + props->ap[msc->fc][msc->mode][2]. + databytelen = trip->databytelen; + props->ap[msc->fc][msc->mode][2].sglen = + trip->sglen; + } else if (msc->fc == NX_FC_AES_HMAC || + msc->fc == NX_FC_SHA) { + props->ap[msc->fc][msc->mode][1]. + databytelen = trip->databytelen; + props->ap[msc->fc][msc->mode][1].sglen = + trip->sglen; + } else { + dev_warn(dev, "unknown function " + "code/key bit len combo" + ": (%u/256)\n", msc->fc); + } + break; + case 512: + props->ap[msc->fc][msc->mode][2].databytelen = + trip->databytelen; + props->ap[msc->fc][msc->mode][2].sglen = + trip->sglen; + break; + default: + dev_warn(dev, "unknown function code/key bit " + "len combo: (%u/%u)\n", msc->fc, + trip->keybitlen); + break; + } +next_loop: + bytes_so_far += sizeof(struct msc_triplet); + trip++; + } + + msc = (struct max_sync_cop *)trip; + } + + props->flags |= NX_OF_FLAG_MAXSYNCCOP_SET; +} + +/** + * nx_of_init - read openFirmware values from the device tree + * + * @dev: device handle + * @props: pointer to struct to hold the properties values + * + * Called once at driver probe time, this function will read out the + * openFirmware properties we use at runtime. If all the OF properties are + * acceptable, when we exit this function props->flags will indicate that + * we're ready to register our crypto algorithms. + */ +static void nx_of_init(struct device *dev, struct nx_of *props) +{ + struct device_node *base_node = dev->of_node; + struct property *p; + + p = of_find_property(base_node, "status", NULL); + if (!p) + dev_info(dev, "%s: property 'status' not found\n", __func__); + else + nx_of_update_status(dev, p, props); + + p = of_find_property(base_node, "ibm,max-sg-len", NULL); + if (!p) + dev_info(dev, "%s: property 'ibm,max-sg-len' not found\n", + __func__); + else + nx_of_update_sglen(dev, p, props); + + p = of_find_property(base_node, "ibm,max-sync-cop", NULL); + if (!p) + dev_info(dev, "%s: property 'ibm,max-sync-cop' not found\n", + __func__); + else + nx_of_update_msc(dev, p, props); +} + +/** + * nx_register_algs - register algorithms with the crypto API + * + * Called from nx_probe() + * + * If all OF properties are in an acceptable state, the driver flags will + * indicate that we're ready and we'll create our debugfs files and register + * out crypto algorithms. + */ +static int nx_register_algs(void) +{ + int rc = -1; + + if (nx_driver.of.flags != NX_OF_FLAG_MASK_READY) + goto out; + + memset(&nx_driver.stats, 0, sizeof(struct nx_stats)); + + rc = NX_DEBUGFS_INIT(&nx_driver); + if (rc) + goto out; + + rc = crypto_register_alg(&nx_ecb_aes_alg); + if (rc) + goto out; + + rc = crypto_register_alg(&nx_cbc_aes_alg); + if (rc) + goto out_unreg_ecb; + + rc = crypto_register_alg(&nx_ctr_aes_alg); + if (rc) + goto out_unreg_cbc; + + rc = crypto_register_alg(&nx_ctr3686_aes_alg); + if (rc) + goto out_unreg_ctr; + + rc = crypto_register_alg(&nx_gcm_aes_alg); + if (rc) + goto out_unreg_ctr3686; + + rc = crypto_register_alg(&nx_gcm4106_aes_alg); + if (rc) + goto out_unreg_gcm; + + rc = crypto_register_alg(&nx_ccm_aes_alg); + if (rc) + goto out_unreg_gcm4106; + + rc = crypto_register_alg(&nx_ccm4309_aes_alg); + if (rc) + goto out_unreg_ccm; + + rc = crypto_register_shash(&nx_shash_sha256_alg); + if (rc) + goto out_unreg_ccm4309; + + rc = crypto_register_shash(&nx_shash_sha512_alg); + if (rc) + goto out_unreg_s256; + + rc = crypto_register_shash(&nx_shash_aes_xcbc_alg); + if (rc) + goto out_unreg_s512; + + nx_driver.of.status = NX_OKAY; + + goto out; + +out_unreg_s512: + crypto_unregister_shash(&nx_shash_sha512_alg); +out_unreg_s256: + crypto_unregister_shash(&nx_shash_sha256_alg); +out_unreg_ccm4309: + crypto_unregister_alg(&nx_ccm4309_aes_alg); +out_unreg_ccm: + crypto_unregister_alg(&nx_ccm_aes_alg); +out_unreg_gcm4106: + crypto_unregister_alg(&nx_gcm4106_aes_alg); +out_unreg_gcm: + crypto_unregister_alg(&nx_gcm_aes_alg); +out_unreg_ctr3686: + crypto_unregister_alg(&nx_ctr3686_aes_alg); +out_unreg_ctr: + crypto_unregister_alg(&nx_ctr_aes_alg); +out_unreg_cbc: + crypto_unregister_alg(&nx_cbc_aes_alg); +out_unreg_ecb: + crypto_unregister_alg(&nx_ecb_aes_alg); +out: + return rc; +} + +/** + * nx_crypto_ctx_init - create and initialize a crypto api context + * + * @nx_ctx: the crypto api context + * @fc: function code for the context + * @mode: the function code specific mode for this context + */ +static int nx_crypto_ctx_init(struct nx_crypto_ctx *nx_ctx, u32 fc, u32 mode) +{ + if (nx_driver.of.status != NX_OKAY) { + pr_err("Attempt to initialize NX crypto context while device " + "is not available!\n"); + return -ENODEV; + } + + /* we need an extra page for csbcpb_aead for these modes */ + if (mode == NX_MODE_AES_GCM || mode == NX_MODE_AES_CCM) + nx_ctx->kmem_len = (4 * NX_PAGE_SIZE) + + sizeof(struct nx_csbcpb); + else + nx_ctx->kmem_len = (3 * NX_PAGE_SIZE) + + sizeof(struct nx_csbcpb); + + nx_ctx->kmem = kmalloc(nx_ctx->kmem_len, GFP_KERNEL); + if (!nx_ctx->kmem) + return -ENOMEM; + + /* the csbcpb and scatterlists must be 4K aligned pages */ + nx_ctx->csbcpb = (struct nx_csbcpb *)(round_up((u64)nx_ctx->kmem, + (u64)NX_PAGE_SIZE)); + nx_ctx->in_sg = (struct nx_sg *)((u8 *)nx_ctx->csbcpb + NX_PAGE_SIZE); + nx_ctx->out_sg = (struct nx_sg *)((u8 *)nx_ctx->in_sg + NX_PAGE_SIZE); + + if (mode == NX_MODE_AES_GCM || mode == NX_MODE_AES_CCM) + nx_ctx->csbcpb_aead = + (struct nx_csbcpb *)((u8 *)nx_ctx->out_sg + + NX_PAGE_SIZE); + + /* give each context a pointer to global stats and their OF + * properties */ + nx_ctx->stats = &nx_driver.stats; + memcpy(nx_ctx->props, nx_driver.of.ap[fc][mode], + sizeof(struct alg_props) * 3); + + return 0; +} + +/* entry points from the crypto tfm initializers */ +int nx_crypto_ctx_aes_ccm_init(struct crypto_tfm *tfm) +{ + return nx_crypto_ctx_init(crypto_tfm_ctx(tfm), NX_FC_AES, + NX_MODE_AES_CCM); +} + +int nx_crypto_ctx_aes_gcm_init(struct crypto_tfm *tfm) +{ + return nx_crypto_ctx_init(crypto_tfm_ctx(tfm), NX_FC_AES, + NX_MODE_AES_GCM); +} + +int nx_crypto_ctx_aes_ctr_init(struct crypto_tfm *tfm) +{ + return nx_crypto_ctx_init(crypto_tfm_ctx(tfm), NX_FC_AES, + NX_MODE_AES_CTR); +} + +int nx_crypto_ctx_aes_cbc_init(struct crypto_tfm *tfm) +{ + return nx_crypto_ctx_init(crypto_tfm_ctx(tfm), NX_FC_AES, + NX_MODE_AES_CBC); +} + +int nx_crypto_ctx_aes_ecb_init(struct crypto_tfm *tfm) +{ + return nx_crypto_ctx_init(crypto_tfm_ctx(tfm), NX_FC_AES, + NX_MODE_AES_ECB); +} + +int nx_crypto_ctx_sha_init(struct crypto_tfm *tfm) +{ + return nx_crypto_ctx_init(crypto_tfm_ctx(tfm), NX_FC_SHA, NX_MODE_SHA); +} + +int nx_crypto_ctx_aes_xcbc_init(struct crypto_tfm *tfm) +{ + return nx_crypto_ctx_init(crypto_tfm_ctx(tfm), NX_FC_AES, + NX_MODE_AES_XCBC_MAC); +} + +/** + * nx_crypto_ctx_exit - destroy a crypto api context + * + * @tfm: the crypto transform pointer for the context + * + * As crypto API contexts are destroyed, this exit hook is called to free the + * memory associated with it. + */ +void nx_crypto_ctx_exit(struct crypto_tfm *tfm) +{ + struct nx_crypto_ctx *nx_ctx = crypto_tfm_ctx(tfm); + + kzfree(nx_ctx->kmem); + nx_ctx->csbcpb = NULL; + nx_ctx->csbcpb_aead = NULL; + nx_ctx->in_sg = NULL; + nx_ctx->out_sg = NULL; +} + +static int __devinit nx_probe(struct vio_dev *viodev, + const struct vio_device_id *id) +{ + dev_dbg(&viodev->dev, "driver probed: %s resource id: 0x%x\n", + viodev->name, viodev->resource_id); + + if (nx_driver.viodev) { + dev_err(&viodev->dev, "%s: Attempt to register more than one " + "instance of the hardware\n", __func__); + return -EINVAL; + } + + nx_driver.viodev = viodev; + + nx_of_init(&viodev->dev, &nx_driver.of); + + return nx_register_algs(); +} + +static int __devexit nx_remove(struct vio_dev *viodev) +{ + dev_dbg(&viodev->dev, "entering nx_remove for UA 0x%x\n", + viodev->unit_address); + + if (nx_driver.of.status == NX_OKAY) { + NX_DEBUGFS_FINI(&nx_driver); + + crypto_unregister_alg(&nx_ccm_aes_alg); + crypto_unregister_alg(&nx_ccm4309_aes_alg); + crypto_unregister_alg(&nx_gcm_aes_alg); + crypto_unregister_alg(&nx_gcm4106_aes_alg); + crypto_unregister_alg(&nx_ctr_aes_alg); + crypto_unregister_alg(&nx_ctr3686_aes_alg); + crypto_unregister_alg(&nx_cbc_aes_alg); + crypto_unregister_alg(&nx_ecb_aes_alg); + crypto_unregister_shash(&nx_shash_sha256_alg); + crypto_unregister_shash(&nx_shash_sha512_alg); + crypto_unregister_shash(&nx_shash_aes_xcbc_alg); + } + + return 0; +} + + +/* module wide initialization/cleanup */ +static int __init nx_init(void) +{ + return vio_register_driver(&nx_driver.viodriver); +} + +static void __exit nx_fini(void) +{ + vio_unregister_driver(&nx_driver.viodriver); +} + +static struct vio_device_id nx_crypto_driver_ids[] __devinitdata = { + { "ibm,sym-encryption-v1", "ibm,sym-encryption" }, + { "", "" } +}; +MODULE_DEVICE_TABLE(vio, nx_crypto_driver_ids); + +/* driver state structure */ +struct nx_crypto_driver nx_driver = { + .viodriver = { + .id_table = nx_crypto_driver_ids, + .probe = nx_probe, + .remove = nx_remove, + .name = NX_NAME, + }, +}; + +module_init(nx_init); +module_exit(nx_fini); + +MODULE_AUTHOR("Kent Yoder <[email protected]>"); +MODULE_DESCRIPTION(NX_STRING); +MODULE_LICENSE("GPL"); +MODULE_VERSION(NX_VERSION); diff --git a/drivers/crypto/nx/nx.h b/drivers/crypto/nx/nx.h new file mode 100644 index 000000000000..3232b182dd28 --- /dev/null +++ b/drivers/crypto/nx/nx.h @@ -0,0 +1,193 @@ + +#ifndef __NX_H__ +#define __NX_H__ + +#define NX_NAME "nx-crypto" +#define NX_STRING "IBM Power7+ Nest Accelerator Crypto Driver" +#define NX_VERSION "1.0" + +static const char nx_driver_string[] = NX_STRING; +static const char nx_driver_version[] = NX_VERSION; + +/* a scatterlist in the format PHYP is expecting */ +struct nx_sg { + u64 addr; + u32 rsvd; + u32 len; +} __attribute((packed)); + +#define NX_PAGE_SIZE (4096) +#define NX_MAX_SG_ENTRIES (NX_PAGE_SIZE/(sizeof(struct nx_sg))) + +enum nx_status { + NX_DISABLED, + NX_WAITING, + NX_OKAY +}; + +/* msc_triplet and max_sync_cop are used only to assist in parsing the + * openFirmware property */ +struct msc_triplet { + u32 keybitlen; + u32 databytelen; + u32 sglen; +} __packed; + +struct max_sync_cop { + u32 fc; + u32 mode; + u32 triplets; + struct msc_triplet trip[0]; +} __packed; + +struct alg_props { + u32 databytelen; + u32 sglen; +}; + +#define NX_OF_FLAG_MAXSGLEN_SET (1) +#define NX_OF_FLAG_STATUS_SET (2) +#define NX_OF_FLAG_MAXSYNCCOP_SET (4) +#define NX_OF_FLAG_MASK_READY (NX_OF_FLAG_MAXSGLEN_SET | \ + NX_OF_FLAG_STATUS_SET | \ + NX_OF_FLAG_MAXSYNCCOP_SET) +struct nx_of { + u32 flags; + u32 max_sg_len; + enum nx_status status; + struct alg_props ap[NX_MAX_FC][NX_MAX_MODE][3]; +}; + +struct nx_stats { + atomic_t aes_ops; + atomic64_t aes_bytes; + atomic_t sha256_ops; + atomic64_t sha256_bytes; + atomic_t sha512_ops; + atomic64_t sha512_bytes; + + atomic_t sync_ops; + + atomic_t errors; + atomic_t last_error; + atomic_t last_error_pid; +}; + +struct nx_debugfs { + struct dentry *dfs_root; + struct dentry *dfs_aes_ops, *dfs_aes_bytes; + struct dentry *dfs_sha256_ops, *dfs_sha256_bytes; + struct dentry *dfs_sha512_ops, *dfs_sha512_bytes; + struct dentry *dfs_errors, *dfs_last_error, *dfs_last_error_pid; +}; + +struct nx_crypto_driver { + struct nx_stats stats; + struct nx_of of; + struct vio_dev *viodev; + struct vio_driver viodriver; + struct nx_debugfs dfs; +}; + +#define NX_GCM4106_NONCE_LEN (4) +#define NX_GCM_CTR_OFFSET (12) +struct nx_gcm_priv { + u8 iv[16]; + u8 iauth_tag[16]; + u8 nonce[NX_GCM4106_NONCE_LEN]; +}; + +#define NX_CCM_AES_KEY_LEN (16) +#define NX_CCM4309_AES_KEY_LEN (19) +#define NX_CCM4309_NONCE_LEN (3) +struct nx_ccm_priv { + u8 iv[16]; + u8 b0[16]; + u8 iauth_tag[16]; + u8 oauth_tag[16]; + u8 nonce[NX_CCM4309_NONCE_LEN]; +}; + +struct nx_xcbc_priv { + u8 key[16]; +}; + +struct nx_ctr_priv { + u8 iv[16]; +}; + +struct nx_crypto_ctx { + void *kmem; /* unaligned, kmalloc'd buffer */ + size_t kmem_len; /* length of kmem */ + struct nx_csbcpb *csbcpb; /* aligned page given to phyp @ hcall time */ + struct vio_pfo_op op; /* operation struct with hcall parameters */ + struct nx_csbcpb *csbcpb_aead; /* secondary csbcpb used by AEAD algs */ + struct vio_pfo_op op_aead;/* operation struct for csbcpb_aead */ + + struct nx_sg *in_sg; /* aligned pointer into kmem to an sg list */ + struct nx_sg *out_sg; /* aligned pointer into kmem to an sg list */ + + struct alg_props *ap; /* pointer into props based on our key size */ + struct alg_props props[3];/* openFirmware properties for requests */ + struct nx_stats *stats; /* pointer into an nx_crypto_driver for stats + reporting */ + + union { + struct nx_gcm_priv gcm; + struct nx_ccm_priv ccm; + struct nx_xcbc_priv xcbc; + struct nx_ctr_priv ctr; + } priv; +}; + +/* prototypes */ +int nx_crypto_ctx_aes_ccm_init(struct crypto_tfm *tfm); +int nx_crypto_ctx_aes_gcm_init(struct crypto_tfm *tfm); +int nx_crypto_ctx_aes_xcbc_init(struct crypto_tfm *tfm); +int nx_crypto_ctx_aes_ctr_init(struct crypto_tfm *tfm); +int nx_crypto_ctx_aes_cbc_init(struct crypto_tfm *tfm); +int nx_crypto_ctx_aes_ecb_init(struct crypto_tfm *tfm); +int nx_crypto_ctx_sha_init(struct crypto_tfm *tfm); +void nx_crypto_ctx_exit(struct crypto_tfm *tfm); +void nx_ctx_init(struct nx_crypto_ctx *nx_ctx, unsigned int function); +int nx_hcall_sync(struct nx_crypto_ctx *ctx, struct vio_pfo_op *op, + u32 may_sleep); +struct nx_sg *nx_build_sg_list(struct nx_sg *, u8 *, unsigned int, u32); +int nx_build_sg_lists(struct nx_crypto_ctx *, struct blkcipher_desc *, + struct scatterlist *, struct scatterlist *, unsigned int, + u8 *); +struct nx_sg *nx_walk_and_build(struct nx_sg *, unsigned int, + struct scatterlist *, unsigned int, + unsigned int); + +#ifdef CONFIG_DEBUG_FS +#define NX_DEBUGFS_INIT(drv) nx_debugfs_init(drv) +#define NX_DEBUGFS_FINI(drv) nx_debugfs_fini(drv) + +int nx_debugfs_init(struct nx_crypto_driver *); +void nx_debugfs_fini(struct nx_crypto_driver *); +#else +#define NX_DEBUGFS_INIT(drv) (0) +#define NX_DEBUGFS_FINI(drv) (0) +#endif + +#define NX_PAGE_NUM(x) ((u64)(x) & 0xfffffffffffff000ULL) + +extern struct crypto_alg nx_cbc_aes_alg; +extern struct crypto_alg nx_ecb_aes_alg; +extern struct crypto_alg nx_gcm_aes_alg; +extern struct crypto_alg nx_gcm4106_aes_alg; +extern struct crypto_alg nx_ctr_aes_alg; +extern struct crypto_alg nx_ctr3686_aes_alg; +extern struct crypto_alg nx_ccm_aes_alg; +extern struct crypto_alg nx_ccm4309_aes_alg; +extern struct shash_alg nx_shash_aes_xcbc_alg; +extern struct shash_alg nx_shash_sha512_alg; +extern struct shash_alg nx_shash_sha256_alg; + +extern struct nx_crypto_driver nx_driver; + +#define SCATTERWALK_TO_SG 1 +#define SCATTERWALK_FROM_SG 0 + +#endif diff --git a/drivers/crypto/nx/nx_csbcpb.h b/drivers/crypto/nx/nx_csbcpb.h new file mode 100644 index 000000000000..a304f956d6f8 --- /dev/null +++ b/drivers/crypto/nx/nx_csbcpb.h @@ -0,0 +1,205 @@ + +#ifndef __NX_CSBCPB_H__ +#define __NX_CSBCPB_H__ + +struct cop_symcpb_aes_ecb { + u8 key[32]; + u8 __rsvd[80]; +} __packed; + +struct cop_symcpb_aes_cbc { + u8 iv[16]; + u8 key[32]; + u8 cv[16]; + u32 spbc; + u8 __rsvd[44]; +} __packed; + +struct cop_symcpb_aes_gca { + u8 in_pat[16]; + u8 key[32]; + u8 out_pat[16]; + u32 spbc; + u8 __rsvd[44]; +} __packed; + +struct cop_symcpb_aes_gcm { + u8 in_pat_or_aad[16]; + u8 iv_or_cnt[16]; + u64 bit_length_aad; + u64 bit_length_data; + u8 in_s0[16]; + u8 key[32]; + u8 __rsvd1[16]; + u8 out_pat_or_mac[16]; + u8 out_s0[16]; + u8 out_cnt[16]; + u32 spbc; + u8 __rsvd2[12]; +} __packed; + +struct cop_symcpb_aes_ctr { + u8 iv[16]; + u8 key[32]; + u8 cv[16]; + u32 spbc; + u8 __rsvd2[44]; +} __packed; + +struct cop_symcpb_aes_cca { + u8 b0[16]; + u8 b1[16]; + u8 key[16]; + u8 out_pat_or_b0[16]; + u32 spbc; + u8 __rsvd[44]; +} __packed; + +struct cop_symcpb_aes_ccm { + u8 in_pat_or_b0[16]; + u8 iv_or_ctr[16]; + u8 in_s0[16]; + u8 key[16]; + u8 __rsvd1[48]; + u8 out_pat_or_mac[16]; + u8 out_s0[16]; + u8 out_ctr[16]; + u32 spbc; + u8 __rsvd2[12]; +} __packed; + +struct cop_symcpb_aes_xcbc { + u8 cv[16]; + u8 key[16]; + u8 __rsvd1[16]; + u8 out_cv_mac[16]; + u32 spbc; + u8 __rsvd2[44]; +} __packed; + +struct cop_symcpb_sha256 { + u64 message_bit_length; + u64 __rsvd1; + u8 input_partial_digest[32]; + u8 message_digest[32]; + u32 spbc; + u8 __rsvd2[44]; +} __packed; + +struct cop_symcpb_sha512 { + u64 message_bit_length_hi; + u64 message_bit_length_lo; + u8 input_partial_digest[64]; + u8 __rsvd1[32]; + u8 message_digest[64]; + u32 spbc; + u8 __rsvd2[76]; +} __packed; + +#define NX_FDM_INTERMEDIATE 0x01 +#define NX_FDM_CONTINUATION 0x02 +#define NX_FDM_ENDE_ENCRYPT 0x80 + +#define NX_CPB_FDM(c) ((c)->cpb.hdr.fdm) +#define NX_CPB_KS_DS(c) ((c)->cpb.hdr.ks_ds) + +#define NX_CPB_KEY_SIZE(c) (NX_CPB_KS_DS(c) >> 4) +#define NX_CPB_SET_KEY_SIZE(c, x) NX_CPB_KS_DS(c) |= ((x) << 4) +#define NX_CPB_SET_DIGEST_SIZE(c, x) NX_CPB_KS_DS(c) |= (x) + +struct cop_symcpb_header { + u8 mode; + u8 fdm; + u8 ks_ds; + u8 pad_byte; + u8 __rsvd[12]; +} __packed; + +struct cop_parameter_block { + struct cop_symcpb_header hdr; + union { + struct cop_symcpb_aes_ecb aes_ecb; + struct cop_symcpb_aes_cbc aes_cbc; + struct cop_symcpb_aes_gca aes_gca; + struct cop_symcpb_aes_gcm aes_gcm; + struct cop_symcpb_aes_cca aes_cca; + struct cop_symcpb_aes_ccm aes_ccm; + struct cop_symcpb_aes_ctr aes_ctr; + struct cop_symcpb_aes_xcbc aes_xcbc; + struct cop_symcpb_sha256 sha256; + struct cop_symcpb_sha512 sha512; + }; +} __packed; + +#define NX_CSB_VALID_BIT 0x80 + +/* co-processor status block */ +struct cop_status_block { + u8 valid; + u8 crb_seq_number; + u8 completion_code; + u8 completion_extension; + u32 processed_byte_count; + u64 address; +} __packed; + +/* Nest accelerator workbook section 4.4 */ +struct nx_csbcpb { + unsigned char __rsvd[112]; + struct cop_status_block csb; + struct cop_parameter_block cpb; +} __packed; + +/* nx_csbcpb related definitions */ +#define NX_MODE_AES_ECB 0 +#define NX_MODE_AES_CBC 1 +#define NX_MODE_AES_GMAC 2 +#define NX_MODE_AES_GCA 3 +#define NX_MODE_AES_GCM 4 +#define NX_MODE_AES_CCA 5 +#define NX_MODE_AES_CCM 6 +#define NX_MODE_AES_CTR 7 +#define NX_MODE_AES_XCBC_MAC 20 +#define NX_MODE_SHA 0 +#define NX_MODE_SHA_HMAC 1 +#define NX_MODE_AES_CBC_HMAC_ETA 8 +#define NX_MODE_AES_CBC_HMAC_ATE 9 +#define NX_MODE_AES_CBC_HMAC_EAA 10 +#define NX_MODE_AES_CTR_HMAC_ETA 12 +#define NX_MODE_AES_CTR_HMAC_ATE 13 +#define NX_MODE_AES_CTR_HMAC_EAA 14 + +#define NX_FDM_CI_FULL 0 +#define NX_FDM_CI_FIRST 1 +#define NX_FDM_CI_LAST 2 +#define NX_FDM_CI_MIDDLE 3 + +#define NX_FDM_PR_NONE 0 +#define NX_FDM_PR_PAD 1 + +#define NX_KS_AES_128 1 +#define NX_KS_AES_192 2 +#define NX_KS_AES_256 3 + +#define NX_DS_SHA256 2 +#define NX_DS_SHA512 3 + +#define NX_FC_AES 0 +#define NX_FC_SHA 2 +#define NX_FC_AES_HMAC 6 + +#define NX_MAX_FC (NX_FC_AES_HMAC + 1) +#define NX_MAX_MODE (NX_MODE_AES_XCBC_MAC + 1) + +#define HCOP_FC_AES NX_FC_AES +#define HCOP_FC_SHA NX_FC_SHA +#define HCOP_FC_AES_HMAC NX_FC_AES_HMAC + +/* indices into the array of algorithm properties */ +#define NX_PROPS_AES_128 0 +#define NX_PROPS_AES_192 1 +#define NX_PROPS_AES_256 2 +#define NX_PROPS_SHA256 1 +#define NX_PROPS_SHA512 2 + +#endif diff --git a/drivers/crypto/nx/nx_debugfs.c b/drivers/crypto/nx/nx_debugfs.c new file mode 100644 index 000000000000..7ab2e8dcd9b4 --- /dev/null +++ b/drivers/crypto/nx/nx_debugfs.c @@ -0,0 +1,103 @@ +/** + * debugfs routines supporting the Power 7+ Nest Accelerators driver + * + * Copyright (C) 2011-2012 International Business Machines Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Author: Kent Yoder <[email protected]> + */ + +#include <linux/device.h> +#include <linux/kobject.h> +#include <linux/string.h> +#include <linux/debugfs.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/crypto.h> +#include <crypto/hash.h> +#include <asm/vio.h> + +#include "nx_csbcpb.h" +#include "nx.h" + +#ifdef CONFIG_DEBUG_FS + +/* + * debugfs + * + * For documentation on these attributes, please see: + * + * Documentation/ABI/testing/debugfs-pfo-nx-crypto + */ + +int nx_debugfs_init(struct nx_crypto_driver *drv) +{ + struct nx_debugfs *dfs = &drv->dfs; + + dfs->dfs_root = debugfs_create_dir(NX_NAME, NULL); + + dfs->dfs_aes_ops = + debugfs_create_u32("aes_ops", + S_IRUSR | S_IRGRP | S_IROTH, + dfs->dfs_root, (u32 *)&drv->stats.aes_ops); + dfs->dfs_sha256_ops = + debugfs_create_u32("sha256_ops", + S_IRUSR | S_IRGRP | S_IROTH, + dfs->dfs_root, + (u32 *)&drv->stats.sha256_ops); + dfs->dfs_sha512_ops = + debugfs_create_u32("sha512_ops", + S_IRUSR | S_IRGRP | S_IROTH, + dfs->dfs_root, + (u32 *)&drv->stats.sha512_ops); + dfs->dfs_aes_bytes = + debugfs_create_u64("aes_bytes", + S_IRUSR | S_IRGRP | S_IROTH, + dfs->dfs_root, + (u64 *)&drv->stats.aes_bytes); + dfs->dfs_sha256_bytes = + debugfs_create_u64("sha256_bytes", + S_IRUSR | S_IRGRP | S_IROTH, + dfs->dfs_root, + (u64 *)&drv->stats.sha256_bytes); + dfs->dfs_sha512_bytes = + debugfs_create_u64("sha512_bytes", + S_IRUSR | S_IRGRP | S_IROTH, + dfs->dfs_root, + (u64 *)&drv->stats.sha512_bytes); + dfs->dfs_errors = + debugfs_create_u32("errors", + S_IRUSR | S_IRGRP | S_IROTH, + dfs->dfs_root, (u32 *)&drv->stats.errors); + dfs->dfs_last_error = + debugfs_create_u32("last_error", + S_IRUSR | S_IRGRP | S_IROTH, + dfs->dfs_root, + (u32 *)&drv->stats.last_error); + dfs->dfs_last_error_pid = + debugfs_create_u32("last_error_pid", + S_IRUSR | S_IRGRP | S_IROTH, + dfs->dfs_root, + (u32 *)&drv->stats.last_error_pid); + return 0; +} + +void +nx_debugfs_fini(struct nx_crypto_driver *drv) +{ + debugfs_remove_recursive(drv->dfs.dfs_root); +} + +#endif diff --git a/drivers/crypto/ux500/Kconfig b/drivers/crypto/ux500/Kconfig new file mode 100644 index 000000000000..b35e5c4b025a --- /dev/null +++ b/drivers/crypto/ux500/Kconfig @@ -0,0 +1,30 @@ +# +# Copyright (C) ST-Ericsson SA 2010 +# Author: Shujuan Chen ([email protected]) +# License terms: GNU General Public License (GPL) version 2 +# + +config CRYPTO_DEV_UX500_CRYP + tristate "UX500 crypto driver for CRYP block" + depends on CRYPTO_DEV_UX500 + select CRYPTO_DES + help + This selects the crypto driver for the UX500_CRYP hardware. It supports + AES-ECB, CBC and CTR with keys sizes of 128, 192 and 256 bit sizes. + +config CRYPTO_DEV_UX500_HASH + tristate "UX500 crypto driver for HASH block" + depends on CRYPTO_DEV_UX500 + select CRYPTO_HASH + select CRYPTO_HMAC + help + This selects the hash driver for the UX500_HASH hardware. + Depends on UX500/STM DMA if running in DMA mode. + +config CRYPTO_DEV_UX500_DEBUG + bool "Activate ux500 platform debug-mode for crypto and hash block" + depends on CRYPTO_DEV_UX500_CRYP || CRYPTO_DEV_UX500_HASH + default n + help + Say Y if you want to add debug prints to ux500_hash and + ux500_cryp devices. diff --git a/drivers/crypto/ux500/Makefile b/drivers/crypto/ux500/Makefile new file mode 100644 index 000000000000..b9a365bade86 --- /dev/null +++ b/drivers/crypto/ux500/Makefile @@ -0,0 +1,8 @@ +# +# Copyright (C) ST-Ericsson SA 2010 +# Author: Shujuan Chen ([email protected]) +# License terms: GNU General Public License (GPL) version 2 +# + +obj-$(CONFIG_CRYPTO_DEV_UX500_HASH) += hash/ +obj-$(CONFIG_CRYPTO_DEV_UX500_CRYP) += cryp/ diff --git a/drivers/crypto/ux500/cryp/Makefile b/drivers/crypto/ux500/cryp/Makefile new file mode 100644 index 000000000000..e5d362a6f680 --- /dev/null +++ b/drivers/crypto/ux500/cryp/Makefile @@ -0,0 +1,13 @@ +#/* +# * Copyright (C) ST-Ericsson SA 2010 +# * Author: [email protected] for ST-Ericsson. +# * License terms: GNU General Public License (GPL) version 2 */ + +ifdef CONFIG_CRYPTO_DEV_UX500_DEBUG +CFLAGS_cryp_core.o := -DDEBUG -O0 +CFLAGS_cryp.o := -DDEBUG -O0 +CFLAGS_cryp_irq.o := -DDEBUG -O0 +endif + +obj-$(CONFIG_CRYPTO_DEV_UX500_CRYP) += ux500_cryp.o +ux500_cryp-objs := cryp.o cryp_irq.o cryp_core.o diff --git a/drivers/crypto/ux500/cryp/cryp.c b/drivers/crypto/ux500/cryp/cryp.c new file mode 100644 index 000000000000..e208ceaf81c9 --- /dev/null +++ b/drivers/crypto/ux500/cryp/cryp.c @@ -0,0 +1,389 @@ +/** + * Copyright (C) ST-Ericsson SA 2010 + * Author: Shujuan Chen <[email protected]> for ST-Ericsson. + * Author: Jonas Linde <[email protected]> for ST-Ericsson. + * Author: Niklas Hernaeus <[email protected]> for ST-Ericsson. + * Author: Joakim Bech <[email protected]> for ST-Ericsson. + * Author: Berne Hebark <[email protected]> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/types.h> + +#include <mach/hardware.h> + +#include "cryp_p.h" +#include "cryp.h" + +/** + * cryp_wait_until_done - wait until the device logic is not busy + */ +void cryp_wait_until_done(struct cryp_device_data *device_data) +{ + while (cryp_is_logic_busy(device_data)) + cpu_relax(); +} + +/** + * cryp_check - This routine checks Peripheral and PCell Id + * @device_data: Pointer to the device data struct for base address. + */ +int cryp_check(struct cryp_device_data *device_data) +{ + int peripheralid2 = 0; + + if (NULL == device_data) + return -EINVAL; + + peripheralid2 = readl_relaxed(&device_data->base->periphId2); + + if (peripheralid2 != CRYP_PERIPHERAL_ID2_DB8500) + return -EPERM; + + /* Check Peripheral and Pcell Id Register for CRYP */ + if ((CRYP_PERIPHERAL_ID0 == + readl_relaxed(&device_data->base->periphId0)) + && (CRYP_PERIPHERAL_ID1 == + readl_relaxed(&device_data->base->periphId1)) + && (CRYP_PERIPHERAL_ID3 == + readl_relaxed(&device_data->base->periphId3)) + && (CRYP_PCELL_ID0 == + readl_relaxed(&device_data->base->pcellId0)) + && (CRYP_PCELL_ID1 == + readl_relaxed(&device_data->base->pcellId1)) + && (CRYP_PCELL_ID2 == + readl_relaxed(&device_data->base->pcellId2)) + && (CRYP_PCELL_ID3 == + readl_relaxed(&device_data->base->pcellId3))) { + return 0; + } + + return -EPERM; +} + +/** + * cryp_activity - This routine enables/disable the cryptography function. + * @device_data: Pointer to the device data struct for base address. + * @cryp_crypen: Enable/Disable functionality + */ +void cryp_activity(struct cryp_device_data *device_data, + enum cryp_crypen cryp_crypen) +{ + CRYP_PUT_BITS(&device_data->base->cr, + cryp_crypen, + CRYP_CR_CRYPEN_POS, + CRYP_CR_CRYPEN_MASK); +} + +/** + * cryp_flush_inoutfifo - Resets both the input and the output FIFOs + * @device_data: Pointer to the device data struct for base address. + */ +void cryp_flush_inoutfifo(struct cryp_device_data *device_data) +{ + /* + * We always need to disble the hardware before trying to flush the + * FIFO. This is something that isn't written in the design + * specification, but we have been informed by the hardware designers + * that this must be done. + */ + cryp_activity(device_data, CRYP_CRYPEN_DISABLE); + cryp_wait_until_done(device_data); + + CRYP_SET_BITS(&device_data->base->cr, CRYP_CR_FFLUSH_MASK); + /* + * CRYP_SR_INFIFO_READY_MASK is the expected value on the status + * register when starting a new calculation, which means Input FIFO is + * not full and input FIFO is empty. + */ + while (readl_relaxed(&device_data->base->sr) != + CRYP_SR_INFIFO_READY_MASK) + cpu_relax(); +} + +/** + * cryp_set_configuration - This routine set the cr CRYP IP + * @device_data: Pointer to the device data struct for base address. + * @cryp_config: Pointer to the configuration parameter + * @control_register: The control register to be written later on. + */ +int cryp_set_configuration(struct cryp_device_data *device_data, + struct cryp_config *cryp_config, + u32 *control_register) +{ + u32 cr_for_kse; + + if (NULL == device_data || NULL == cryp_config) + return -EINVAL; + + *control_register |= (cryp_config->keysize << CRYP_CR_KEYSIZE_POS); + + /* Prepare key for decryption in AES_ECB and AES_CBC mode. */ + if ((CRYP_ALGORITHM_DECRYPT == cryp_config->algodir) && + ((CRYP_ALGO_AES_ECB == cryp_config->algomode) || + (CRYP_ALGO_AES_CBC == cryp_config->algomode))) { + cr_for_kse = *control_register; + /* + * This seems a bit odd, but it is indeed needed to set this to + * encrypt even though it is a decryption that we are doing. It + * also mentioned in the design spec that you need to do this. + * After the keyprepartion for decrypting is done you should set + * algodir back to decryption, which is done outside this if + * statement. + * + * According to design specification we should set mode ECB + * during key preparation even though we might be running CBC + * when enter this function. + * + * Writing to KSE_ENABLED will drop CRYPEN when key preparation + * is done. Therefore we need to set CRYPEN again outside this + * if statement when running decryption. + */ + cr_for_kse |= ((CRYP_ALGORITHM_ENCRYPT << CRYP_CR_ALGODIR_POS) | + (CRYP_ALGO_AES_ECB << CRYP_CR_ALGOMODE_POS) | + (CRYP_CRYPEN_ENABLE << CRYP_CR_CRYPEN_POS) | + (KSE_ENABLED << CRYP_CR_KSE_POS)); + + writel_relaxed(cr_for_kse, &device_data->base->cr); + cryp_wait_until_done(device_data); + } + + *control_register |= + ((cryp_config->algomode << CRYP_CR_ALGOMODE_POS) | + (cryp_config->algodir << CRYP_CR_ALGODIR_POS)); + + return 0; +} + +/** + * cryp_configure_protection - set the protection bits in the CRYP logic. + * @device_data: Pointer to the device data struct for base address. + * @p_protect_config: Pointer to the protection mode and + * secure mode configuration + */ +int cryp_configure_protection(struct cryp_device_data *device_data, + struct cryp_protection_config *p_protect_config) +{ + if (NULL == p_protect_config) + return -EINVAL; + + CRYP_WRITE_BIT(&device_data->base->cr, + (u32) p_protect_config->secure_access, + CRYP_CR_SECURE_MASK); + CRYP_PUT_BITS(&device_data->base->cr, + p_protect_config->privilege_access, + CRYP_CR_PRLG_POS, + CRYP_CR_PRLG_MASK); + + return 0; +} + +/** + * cryp_is_logic_busy - returns the busy status of the CRYP logic + * @device_data: Pointer to the device data struct for base address. + */ +int cryp_is_logic_busy(struct cryp_device_data *device_data) +{ + return CRYP_TEST_BITS(&device_data->base->sr, + CRYP_SR_BUSY_MASK); +} + +/** + * cryp_configure_for_dma - configures the CRYP IP for DMA operation + * @device_data: Pointer to the device data struct for base address. + * @dma_req: Specifies the DMA request type value. + */ +void cryp_configure_for_dma(struct cryp_device_data *device_data, + enum cryp_dma_req_type dma_req) +{ + CRYP_SET_BITS(&device_data->base->dmacr, + (u32) dma_req); +} + +/** + * cryp_configure_key_values - configures the key values for CRYP operations + * @device_data: Pointer to the device data struct for base address. + * @key_reg_index: Key value index register + * @key_value: The key value struct + */ +int cryp_configure_key_values(struct cryp_device_data *device_data, + enum cryp_key_reg_index key_reg_index, + struct cryp_key_value key_value) +{ + while (cryp_is_logic_busy(device_data)) + cpu_relax(); + + switch (key_reg_index) { + case CRYP_KEY_REG_1: + writel_relaxed(key_value.key_value_left, + &device_data->base->key_1_l); + writel_relaxed(key_value.key_value_right, + &device_data->base->key_1_r); + break; + case CRYP_KEY_REG_2: + writel_relaxed(key_value.key_value_left, + &device_data->base->key_2_l); + writel_relaxed(key_value.key_value_right, + &device_data->base->key_2_r); + break; + case CRYP_KEY_REG_3: + writel_relaxed(key_value.key_value_left, + &device_data->base->key_3_l); + writel_relaxed(key_value.key_value_right, + &device_data->base->key_3_r); + break; + case CRYP_KEY_REG_4: + writel_relaxed(key_value.key_value_left, + &device_data->base->key_4_l); + writel_relaxed(key_value.key_value_right, + &device_data->base->key_4_r); + break; + default: + return -EINVAL; + } + + return 0; +} + +/** + * cryp_configure_init_vector - configures the initialization vector register + * @device_data: Pointer to the device data struct for base address. + * @init_vector_index: Specifies the index of the init vector. + * @init_vector_value: Specifies the value for the init vector. + */ +int cryp_configure_init_vector(struct cryp_device_data *device_data, + enum cryp_init_vector_index + init_vector_index, + struct cryp_init_vector_value + init_vector_value) +{ + while (cryp_is_logic_busy(device_data)) + cpu_relax(); + + switch (init_vector_index) { + case CRYP_INIT_VECTOR_INDEX_0: + writel_relaxed(init_vector_value.init_value_left, + &device_data->base->init_vect_0_l); + writel_relaxed(init_vector_value.init_value_right, + &device_data->base->init_vect_0_r); + break; + case CRYP_INIT_VECTOR_INDEX_1: + writel_relaxed(init_vector_value.init_value_left, + &device_data->base->init_vect_1_l); + writel_relaxed(init_vector_value.init_value_right, + &device_data->base->init_vect_1_r); + break; + default: + return -EINVAL; + } + + return 0; +} + +/** + * cryp_save_device_context - Store hardware registers and + * other device context parameter + * @device_data: Pointer to the device data struct for base address. + * @ctx: Crypto device context + */ +void cryp_save_device_context(struct cryp_device_data *device_data, + struct cryp_device_context *ctx, + int cryp_mode) +{ + enum cryp_algo_mode algomode; + struct cryp_register *src_reg = device_data->base; + struct cryp_config *config = + (struct cryp_config *)device_data->current_ctx; + + /* + * Always start by disable the hardware and wait for it to finish the + * ongoing calculations before trying to reprogram it. + */ + cryp_activity(device_data, CRYP_CRYPEN_DISABLE); + cryp_wait_until_done(device_data); + + if (cryp_mode == CRYP_MODE_DMA) + cryp_configure_for_dma(device_data, CRYP_DMA_DISABLE_BOTH); + + if (CRYP_TEST_BITS(&src_reg->sr, CRYP_SR_IFEM_MASK) == 0) + ctx->din = readl_relaxed(&src_reg->din); + + ctx->cr = readl_relaxed(&src_reg->cr) & CRYP_CR_CONTEXT_SAVE_MASK; + + switch (config->keysize) { + case CRYP_KEY_SIZE_256: + ctx->key_4_l = readl_relaxed(&src_reg->key_4_l); + ctx->key_4_r = readl_relaxed(&src_reg->key_4_r); + + case CRYP_KEY_SIZE_192: + ctx->key_3_l = readl_relaxed(&src_reg->key_3_l); + ctx->key_3_r = readl_relaxed(&src_reg->key_3_r); + + case CRYP_KEY_SIZE_128: + ctx->key_2_l = readl_relaxed(&src_reg->key_2_l); + ctx->key_2_r = readl_relaxed(&src_reg->key_2_r); + + default: + ctx->key_1_l = readl_relaxed(&src_reg->key_1_l); + ctx->key_1_r = readl_relaxed(&src_reg->key_1_r); + } + + /* Save IV for CBC mode for both AES and DES. */ + algomode = ((ctx->cr & CRYP_CR_ALGOMODE_MASK) >> CRYP_CR_ALGOMODE_POS); + if (algomode == CRYP_ALGO_TDES_CBC || + algomode == CRYP_ALGO_DES_CBC || + algomode == CRYP_ALGO_AES_CBC) { + ctx->init_vect_0_l = readl_relaxed(&src_reg->init_vect_0_l); + ctx->init_vect_0_r = readl_relaxed(&src_reg->init_vect_0_r); + ctx->init_vect_1_l = readl_relaxed(&src_reg->init_vect_1_l); + ctx->init_vect_1_r = readl_relaxed(&src_reg->init_vect_1_r); + } +} + +/** + * cryp_restore_device_context - Restore hardware registers and + * other device context parameter + * @device_data: Pointer to the device data struct for base address. + * @ctx: Crypto device context + */ +void cryp_restore_device_context(struct cryp_device_data *device_data, + struct cryp_device_context *ctx) +{ + struct cryp_register *reg = device_data->base; + struct cryp_config *config = + (struct cryp_config *)device_data->current_ctx; + + /* + * Fall through for all items in switch statement. DES is captured in + * the default. + */ + switch (config->keysize) { + case CRYP_KEY_SIZE_256: + writel_relaxed(ctx->key_4_l, ®->key_4_l); + writel_relaxed(ctx->key_4_r, ®->key_4_r); + + case CRYP_KEY_SIZE_192: + writel_relaxed(ctx->key_3_l, ®->key_3_l); + writel_relaxed(ctx->key_3_r, ®->key_3_r); + + case CRYP_KEY_SIZE_128: + writel_relaxed(ctx->key_2_l, ®->key_2_l); + writel_relaxed(ctx->key_2_r, ®->key_2_r); + + default: + writel_relaxed(ctx->key_1_l, ®->key_1_l); + writel_relaxed(ctx->key_1_r, ®->key_1_r); + } + + /* Restore IV for CBC mode for AES and DES. */ + if (config->algomode == CRYP_ALGO_TDES_CBC || + config->algomode == CRYP_ALGO_DES_CBC || + config->algomode == CRYP_ALGO_AES_CBC) { + writel_relaxed(ctx->init_vect_0_l, ®->init_vect_0_l); + writel_relaxed(ctx->init_vect_0_r, ®->init_vect_0_r); + writel_relaxed(ctx->init_vect_1_l, ®->init_vect_1_l); + writel_relaxed(ctx->init_vect_1_r, ®->init_vect_1_r); + } +} diff --git a/drivers/crypto/ux500/cryp/cryp.h b/drivers/crypto/ux500/cryp/cryp.h new file mode 100644 index 000000000000..14cfd05b777a --- /dev/null +++ b/drivers/crypto/ux500/cryp/cryp.h @@ -0,0 +1,308 @@ +/** + * Copyright (C) ST-Ericsson SA 2010 + * Author: Shujuan Chen <[email protected]> for ST-Ericsson. + * Author: Jonas Linde <[email protected]> for ST-Ericsson. + * Author: Joakim Bech <[email protected]> for ST-Ericsson. + * Author: Berne Hebark <[email protected]> for ST-Ericsson. + * Author: Niklas Hernaeus <[email protected]> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2 + */ + +#ifndef _CRYP_H_ +#define _CRYP_H_ + +#include <linux/completion.h> +#include <linux/dmaengine.h> +#include <linux/klist.h> +#include <linux/mutex.h> + +#define DEV_DBG_NAME "crypX crypX:" + +/* CRYP enable/disable */ +enum cryp_crypen { + CRYP_CRYPEN_DISABLE = 0, + CRYP_CRYPEN_ENABLE = 1 +}; + +/* CRYP Start Computation enable/disable */ +enum cryp_start { + CRYP_START_DISABLE = 0, + CRYP_START_ENABLE = 1 +}; + +/* CRYP Init Signal enable/disable */ +enum cryp_init { + CRYP_INIT_DISABLE = 0, + CRYP_INIT_ENABLE = 1 +}; + +/* Cryp State enable/disable */ +enum cryp_state { + CRYP_STATE_DISABLE = 0, + CRYP_STATE_ENABLE = 1 +}; + +/* Key preparation bit enable */ +enum cryp_key_prep { + KSE_DISABLED = 0, + KSE_ENABLED = 1 +}; + +/* Key size for AES */ +#define CRYP_KEY_SIZE_128 (0) +#define CRYP_KEY_SIZE_192 (1) +#define CRYP_KEY_SIZE_256 (2) + +/* AES modes */ +enum cryp_algo_mode { + CRYP_ALGO_TDES_ECB, + CRYP_ALGO_TDES_CBC, + CRYP_ALGO_DES_ECB, + CRYP_ALGO_DES_CBC, + CRYP_ALGO_AES_ECB, + CRYP_ALGO_AES_CBC, + CRYP_ALGO_AES_CTR, + CRYP_ALGO_AES_XTS +}; + +/* Cryp Encryption or Decryption */ +enum cryp_algorithm_dir { + CRYP_ALGORITHM_ENCRYPT, + CRYP_ALGORITHM_DECRYPT +}; + +/* Hardware access method */ +enum cryp_mode { + CRYP_MODE_POLLING, + CRYP_MODE_INTERRUPT, + CRYP_MODE_DMA +}; + +/** + * struct cryp_config - + * @keysize: Key size for AES + * @algomode: AES modes + * @algodir: Cryp Encryption or Decryption + * + * CRYP configuration structure to be passed to set configuration + */ +struct cryp_config { + int keysize; + enum cryp_algo_mode algomode; + enum cryp_algorithm_dir algodir; +}; + +/** + * struct cryp_protection_config - + * @privilege_access: Privileged cryp state enable/disable + * @secure_access: Secure cryp state enable/disable + * + * Protection configuration structure for setting privilage access + */ +struct cryp_protection_config { + enum cryp_state privilege_access; + enum cryp_state secure_access; +}; + +/* Cryp status */ +enum cryp_status_id { + CRYP_STATUS_BUSY = 0x10, + CRYP_STATUS_OUTPUT_FIFO_FULL = 0x08, + CRYP_STATUS_OUTPUT_FIFO_NOT_EMPTY = 0x04, + CRYP_STATUS_INPUT_FIFO_NOT_FULL = 0x02, + CRYP_STATUS_INPUT_FIFO_EMPTY = 0x01 +}; + +/* Cryp DMA interface */ +enum cryp_dma_req_type { + CRYP_DMA_DISABLE_BOTH, + CRYP_DMA_ENABLE_IN_DATA, + CRYP_DMA_ENABLE_OUT_DATA, + CRYP_DMA_ENABLE_BOTH_DIRECTIONS +}; + +enum cryp_dma_channel { + CRYP_DMA_RX = 0, + CRYP_DMA_TX +}; + +/* Key registers */ +enum cryp_key_reg_index { + CRYP_KEY_REG_1, + CRYP_KEY_REG_2, + CRYP_KEY_REG_3, + CRYP_KEY_REG_4 +}; + +/* Key register left and right */ +struct cryp_key_value { + u32 key_value_left; + u32 key_value_right; +}; + +/* Cryp Initialization structure */ +enum cryp_init_vector_index { + CRYP_INIT_VECTOR_INDEX_0, + CRYP_INIT_VECTOR_INDEX_1 +}; + +/* struct cryp_init_vector_value - + * @init_value_left + * @init_value_right + * */ +struct cryp_init_vector_value { + u32 init_value_left; + u32 init_value_right; +}; + +/** + * struct cryp_device_context - structure for a cryp context. + * @cr: control register + * @dmacr: DMA control register + * @imsc: Interrupt mask set/clear register + * @key_1_l: Key 1l register + * @key_1_r: Key 1r register + * @key_2_l: Key 2l register + * @key_2_r: Key 2r register + * @key_3_l: Key 3l register + * @key_3_r: Key 3r register + * @key_4_l: Key 4l register + * @key_4_r: Key 4r register + * @init_vect_0_l: Initialization vector 0l register + * @init_vect_0_r: Initialization vector 0r register + * @init_vect_1_l: Initialization vector 1l register + * @init_vect_1_r: Initialization vector 0r register + * @din: Data in register + * @dout: Data out register + * + * CRYP power management specifc structure. + */ +struct cryp_device_context { + u32 cr; + u32 dmacr; + u32 imsc; + + u32 key_1_l; + u32 key_1_r; + u32 key_2_l; + u32 key_2_r; + u32 key_3_l; + u32 key_3_r; + u32 key_4_l; + u32 key_4_r; + + u32 init_vect_0_l; + u32 init_vect_0_r; + u32 init_vect_1_l; + u32 init_vect_1_r; + + u32 din; + u32 dout; +}; + +struct cryp_dma { + dma_cap_mask_t mask; + struct completion cryp_dma_complete; + struct dma_chan *chan_cryp2mem; + struct dma_chan *chan_mem2cryp; + struct stedma40_chan_cfg *cfg_cryp2mem; + struct stedma40_chan_cfg *cfg_mem2cryp; + int sg_src_len; + int sg_dst_len; + struct scatterlist *sg_src; + struct scatterlist *sg_dst; + int nents_src; + int nents_dst; +}; + +/** + * struct cryp_device_data - structure for a cryp device. + * @base: Pointer to the hardware base address. + * @dev: Pointer to the devices dev structure. + * @clk: Pointer to the device's clock control. + * @pwr_regulator: Pointer to the device's power control. + * @power_status: Current status of the power. + * @ctx_lock: Lock for current_ctx. + * @current_ctx: Pointer to the currently allocated context. + * @list_node: For inclusion into a klist. + * @dma: The dma structure holding channel configuration. + * @power_state: TRUE = power state on, FALSE = power state off. + * @power_state_spinlock: Spinlock for power_state. + * @restore_dev_ctx: TRUE = saved ctx, FALSE = no saved ctx. + */ +struct cryp_device_data { + struct cryp_register __iomem *base; + struct device *dev; + struct clk *clk; + struct regulator *pwr_regulator; + int power_status; + struct spinlock ctx_lock; + struct cryp_ctx *current_ctx; + struct klist_node list_node; + struct cryp_dma dma; + bool power_state; + struct spinlock power_state_spinlock; + bool restore_dev_ctx; +}; + +void cryp_wait_until_done(struct cryp_device_data *device_data); + +/* Initialization functions */ + +int cryp_check(struct cryp_device_data *device_data); + +void cryp_activity(struct cryp_device_data *device_data, + enum cryp_crypen cryp_crypen); + +void cryp_flush_inoutfifo(struct cryp_device_data *device_data); + +int cryp_set_configuration(struct cryp_device_data *device_data, + struct cryp_config *cryp_config, + u32 *control_register); + +void cryp_configure_for_dma(struct cryp_device_data *device_data, + enum cryp_dma_req_type dma_req); + +int cryp_configure_key_values(struct cryp_device_data *device_data, + enum cryp_key_reg_index key_reg_index, + struct cryp_key_value key_value); + +int cryp_configure_init_vector(struct cryp_device_data *device_data, + enum cryp_init_vector_index + init_vector_index, + struct cryp_init_vector_value + init_vector_value); + +int cryp_configure_protection(struct cryp_device_data *device_data, + struct cryp_protection_config *p_protect_config); + +/* Power management funtions */ +void cryp_save_device_context(struct cryp_device_data *device_data, + struct cryp_device_context *ctx, + int cryp_mode); + +void cryp_restore_device_context(struct cryp_device_data *device_data, + struct cryp_device_context *ctx); + +/* Data transfer and status bits. */ +int cryp_is_logic_busy(struct cryp_device_data *device_data); + +int cryp_get_status(struct cryp_device_data *device_data); + +/** + * cryp_write_indata - This routine writes 32 bit data into the data input + * register of the cryptography IP. + * @device_data: Pointer to the device data struct for base address. + * @write_data: Data to write. + */ +int cryp_write_indata(struct cryp_device_data *device_data, u32 write_data); + +/** + * cryp_read_outdata - This routine reads the data from the data output + * register of the CRYP logic + * @device_data: Pointer to the device data struct for base address. + * @read_data: Read the data from the output FIFO. + */ +int cryp_read_outdata(struct cryp_device_data *device_data, u32 *read_data); + +#endif /* _CRYP_H_ */ diff --git a/drivers/crypto/ux500/cryp/cryp_core.c b/drivers/crypto/ux500/cryp/cryp_core.c new file mode 100644 index 000000000000..7cac12793a4b --- /dev/null +++ b/drivers/crypto/ux500/cryp/cryp_core.c @@ -0,0 +1,1784 @@ +/** + * Copyright (C) ST-Ericsson SA 2010 + * Author: Shujuan Chen <[email protected]> for ST-Ericsson. + * Author: Joakim Bech <[email protected]> for ST-Ericsson. + * Author: Berne Hebark <[email protected]> for ST-Ericsson. + * Author: Niklas Hernaeus <[email protected]> for ST-Ericsson. + * Author: Jonas Linde <[email protected]> for ST-Ericsson. + * Author: Andreas Westin <[email protected]> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/clk.h> +#include <linux/completion.h> +#include <linux/crypto.h> +#include <linux/dmaengine.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/irqreturn.h> +#include <linux/klist.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/semaphore.h> + +#include <crypto/aes.h> +#include <crypto/algapi.h> +#include <crypto/ctr.h> +#include <crypto/des.h> +#include <crypto/scatterwalk.h> + +#include <plat/ste_dma40.h> + +#include <mach/crypto-ux500.h> +#include <mach/hardware.h> + +#include "cryp_p.h" +#include "cryp.h" + +#define CRYP_MAX_KEY_SIZE 32 +#define BYTES_PER_WORD 4 + +static int cryp_mode; +static atomic_t session_id; + +static struct stedma40_chan_cfg *mem_to_engine; +static struct stedma40_chan_cfg *engine_to_mem; + +/** + * struct cryp_driver_data - data specific to the driver. + * + * @device_list: A list of registered devices to choose from. + * @device_allocation: A semaphore initialized with number of devices. + */ +struct cryp_driver_data { + struct klist device_list; + struct semaphore device_allocation; +}; + +/** + * struct cryp_ctx - Crypto context + * @config: Crypto mode. + * @key[CRYP_MAX_KEY_SIZE]: Key. + * @keylen: Length of key. + * @iv: Pointer to initialization vector. + * @indata: Pointer to indata. + * @outdata: Pointer to outdata. + * @datalen: Length of indata. + * @outlen: Length of outdata. + * @blocksize: Size of blocks. + * @updated: Updated flag. + * @dev_ctx: Device dependent context. + * @device: Pointer to the device. + */ +struct cryp_ctx { + struct cryp_config config; + u8 key[CRYP_MAX_KEY_SIZE]; + u32 keylen; + u8 *iv; + const u8 *indata; + u8 *outdata; + u32 datalen; + u32 outlen; + u32 blocksize; + u8 updated; + struct cryp_device_context dev_ctx; + struct cryp_device_data *device; + u32 session_id; +}; + +static struct cryp_driver_data driver_data; + +/** + * uint8p_to_uint32_be - 4*uint8 to uint32 big endian + * @in: Data to convert. + */ +static inline u32 uint8p_to_uint32_be(u8 *in) +{ + u32 *data = (u32 *)in; + + return cpu_to_be32p(data); +} + +/** + * swap_bits_in_byte - mirror the bits in a byte + * @b: the byte to be mirrored + * + * The bits are swapped the following way: + * Byte b include bits 0-7, nibble 1 (n1) include bits 0-3 and + * nibble 2 (n2) bits 4-7. + * + * Nibble 1 (n1): + * (The "old" (moved) bit is replaced with a zero) + * 1. Move bit 6 and 7, 4 positions to the left. + * 2. Move bit 3 and 5, 2 positions to the left. + * 3. Move bit 1-4, 1 position to the left. + * + * Nibble 2 (n2): + * 1. Move bit 0 and 1, 4 positions to the right. + * 2. Move bit 2 and 4, 2 positions to the right. + * 3. Move bit 3-6, 1 position to the right. + * + * Combine the two nibbles to a complete and swapped byte. + */ + +static inline u8 swap_bits_in_byte(u8 b) +{ +#define R_SHIFT_4_MASK 0xc0 /* Bits 6 and 7, right shift 4 */ +#define R_SHIFT_2_MASK 0x28 /* (After right shift 4) Bits 3 and 5, + right shift 2 */ +#define R_SHIFT_1_MASK 0x1e /* (After right shift 2) Bits 1-4, + right shift 1 */ +#define L_SHIFT_4_MASK 0x03 /* Bits 0 and 1, left shift 4 */ +#define L_SHIFT_2_MASK 0x14 /* (After left shift 4) Bits 2 and 4, + left shift 2 */ +#define L_SHIFT_1_MASK 0x78 /* (After left shift 1) Bits 3-6, + left shift 1 */ + + u8 n1; + u8 n2; + + /* Swap most significant nibble */ + /* Right shift 4, bits 6 and 7 */ + n1 = ((b & R_SHIFT_4_MASK) >> 4) | (b & ~(R_SHIFT_4_MASK >> 4)); + /* Right shift 2, bits 3 and 5 */ + n1 = ((n1 & R_SHIFT_2_MASK) >> 2) | (n1 & ~(R_SHIFT_2_MASK >> 2)); + /* Right shift 1, bits 1-4 */ + n1 = (n1 & R_SHIFT_1_MASK) >> 1; + + /* Swap least significant nibble */ + /* Left shift 4, bits 0 and 1 */ + n2 = ((b & L_SHIFT_4_MASK) << 4) | (b & ~(L_SHIFT_4_MASK << 4)); + /* Left shift 2, bits 2 and 4 */ + n2 = ((n2 & L_SHIFT_2_MASK) << 2) | (n2 & ~(L_SHIFT_2_MASK << 2)); + /* Left shift 1, bits 3-6 */ + n2 = (n2 & L_SHIFT_1_MASK) << 1; + + return n1 | n2; +} + +static inline void swap_words_in_key_and_bits_in_byte(const u8 *in, + u8 *out, u32 len) +{ + unsigned int i = 0; + int j; + int index = 0; + + j = len - BYTES_PER_WORD; + while (j >= 0) { + for (i = 0; i < BYTES_PER_WORD; i++) { + index = len - j - BYTES_PER_WORD + i; + out[j + i] = + swap_bits_in_byte(in[index]); + } + j -= BYTES_PER_WORD; + } +} + +static void add_session_id(struct cryp_ctx *ctx) +{ + /* + * We never want 0 to be a valid value, since this is the default value + * for the software context. + */ + if (unlikely(atomic_inc_and_test(&session_id))) + atomic_inc(&session_id); + + ctx->session_id = atomic_read(&session_id); +} + +static irqreturn_t cryp_interrupt_handler(int irq, void *param) +{ + struct cryp_ctx *ctx; + int i; + struct cryp_device_data *device_data; + + if (param == NULL) { + BUG_ON(!param); + return IRQ_HANDLED; + } + + /* The device is coming from the one found in hw_crypt_noxts. */ + device_data = (struct cryp_device_data *)param; + + ctx = device_data->current_ctx; + + if (ctx == NULL) { + BUG_ON(!ctx); + return IRQ_HANDLED; + } + + dev_dbg(ctx->device->dev, "[%s] (len: %d) %s, ", __func__, ctx->outlen, + cryp_pending_irq_src(device_data, CRYP_IRQ_SRC_OUTPUT_FIFO) ? + "out" : "in"); + + if (cryp_pending_irq_src(device_data, + CRYP_IRQ_SRC_OUTPUT_FIFO)) { + if (ctx->outlen / ctx->blocksize > 0) { + for (i = 0; i < ctx->blocksize / 4; i++) { + *(ctx->outdata) = readl_relaxed( + &device_data->base->dout); + ctx->outdata += 4; + ctx->outlen -= 4; + } + + if (ctx->outlen == 0) { + cryp_disable_irq_src(device_data, + CRYP_IRQ_SRC_OUTPUT_FIFO); + } + } + } else if (cryp_pending_irq_src(device_data, + CRYP_IRQ_SRC_INPUT_FIFO)) { + if (ctx->datalen / ctx->blocksize > 0) { + for (i = 0 ; i < ctx->blocksize / 4; i++) { + writel_relaxed(ctx->indata, + &device_data->base->din); + ctx->indata += 4; + ctx->datalen -= 4; + } + + if (ctx->datalen == 0) + cryp_disable_irq_src(device_data, + CRYP_IRQ_SRC_INPUT_FIFO); + + if (ctx->config.algomode == CRYP_ALGO_AES_XTS) { + CRYP_PUT_BITS(&device_data->base->cr, + CRYP_START_ENABLE, + CRYP_CR_START_POS, + CRYP_CR_START_MASK); + + cryp_wait_until_done(device_data); + } + } + } + + return IRQ_HANDLED; +} + +static int mode_is_aes(enum cryp_algo_mode mode) +{ + return CRYP_ALGO_AES_ECB == mode || + CRYP_ALGO_AES_CBC == mode || + CRYP_ALGO_AES_CTR == mode || + CRYP_ALGO_AES_XTS == mode; +} + +static int cfg_iv(struct cryp_device_data *device_data, u32 left, u32 right, + enum cryp_init_vector_index index) +{ + struct cryp_init_vector_value vector_value; + + dev_dbg(device_data->dev, "[%s]", __func__); + + vector_value.init_value_left = left; + vector_value.init_value_right = right; + + return cryp_configure_init_vector(device_data, + index, + vector_value); +} + +static int cfg_ivs(struct cryp_device_data *device_data, struct cryp_ctx *ctx) +{ + int i; + int status = 0; + int num_of_regs = ctx->blocksize / 8; + u32 iv[AES_BLOCK_SIZE / 4]; + + dev_dbg(device_data->dev, "[%s]", __func__); + + /* + * Since we loop on num_of_regs we need to have a check in case + * someone provides an incorrect blocksize which would force calling + * cfg_iv with i greater than 2 which is an error. + */ + if (num_of_regs > 2) { + dev_err(device_data->dev, "[%s] Incorrect blocksize %d", + __func__, ctx->blocksize); + return -EINVAL; + } + + for (i = 0; i < ctx->blocksize / 4; i++) + iv[i] = uint8p_to_uint32_be(ctx->iv + i*4); + + for (i = 0; i < num_of_regs; i++) { + status = cfg_iv(device_data, iv[i*2], iv[i*2+1], + (enum cryp_init_vector_index) i); + if (status != 0) + return status; + } + return status; +} + +static int set_key(struct cryp_device_data *device_data, + u32 left_key, + u32 right_key, + enum cryp_key_reg_index index) +{ + struct cryp_key_value key_value; + int cryp_error; + + dev_dbg(device_data->dev, "[%s]", __func__); + + key_value.key_value_left = left_key; + key_value.key_value_right = right_key; + + cryp_error = cryp_configure_key_values(device_data, + index, + key_value); + if (cryp_error != 0) + dev_err(device_data->dev, "[%s]: " + "cryp_configure_key_values() failed!", __func__); + + return cryp_error; +} + +static int cfg_keys(struct cryp_ctx *ctx) +{ + int i; + int num_of_regs = ctx->keylen / 8; + u32 swapped_key[CRYP_MAX_KEY_SIZE / 4]; + int cryp_error = 0; + + dev_dbg(ctx->device->dev, "[%s]", __func__); + + if (mode_is_aes(ctx->config.algomode)) { + swap_words_in_key_and_bits_in_byte((u8 *)ctx->key, + (u8 *)swapped_key, + ctx->keylen); + } else { + for (i = 0; i < ctx->keylen / 4; i++) + swapped_key[i] = uint8p_to_uint32_be(ctx->key + i*4); + } + + for (i = 0; i < num_of_regs; i++) { + cryp_error = set_key(ctx->device, + *(((u32 *)swapped_key)+i*2), + *(((u32 *)swapped_key)+i*2+1), + (enum cryp_key_reg_index) i); + + if (cryp_error != 0) { + dev_err(ctx->device->dev, "[%s]: set_key() failed!", + __func__); + return cryp_error; + } + } + return cryp_error; +} + +static int cryp_setup_context(struct cryp_ctx *ctx, + struct cryp_device_data *device_data) +{ + u32 control_register = CRYP_CR_DEFAULT; + + switch (cryp_mode) { + case CRYP_MODE_INTERRUPT: + writel_relaxed(CRYP_IMSC_DEFAULT, &device_data->base->imsc); + break; + + case CRYP_MODE_DMA: + writel_relaxed(CRYP_DMACR_DEFAULT, &device_data->base->dmacr); + break; + + default: + break; + } + + if (ctx->updated == 0) { + cryp_flush_inoutfifo(device_data); + if (cfg_keys(ctx) != 0) { + dev_err(ctx->device->dev, "[%s]: cfg_keys failed!", + __func__); + return -EINVAL; + } + + if (ctx->iv && + CRYP_ALGO_AES_ECB != ctx->config.algomode && + CRYP_ALGO_DES_ECB != ctx->config.algomode && + CRYP_ALGO_TDES_ECB != ctx->config.algomode) { + if (cfg_ivs(device_data, ctx) != 0) + return -EPERM; + } + + cryp_set_configuration(device_data, &ctx->config, + &control_register); + add_session_id(ctx); + } else if (ctx->updated == 1 && + ctx->session_id != atomic_read(&session_id)) { + cryp_flush_inoutfifo(device_data); + cryp_restore_device_context(device_data, &ctx->dev_ctx); + + add_session_id(ctx); + control_register = ctx->dev_ctx.cr; + } else + control_register = ctx->dev_ctx.cr; + + writel(control_register | + (CRYP_CRYPEN_ENABLE << CRYP_CR_CRYPEN_POS), + &device_data->base->cr); + + return 0; +} + +static int cryp_get_device_data(struct cryp_ctx *ctx, + struct cryp_device_data **device_data) +{ + int ret; + struct klist_iter device_iterator; + struct klist_node *device_node; + struct cryp_device_data *local_device_data = NULL; + pr_debug(DEV_DBG_NAME " [%s]", __func__); + + /* Wait until a device is available */ + ret = down_interruptible(&driver_data.device_allocation); + if (ret) + return ret; /* Interrupted */ + + /* Select a device */ + klist_iter_init(&driver_data.device_list, &device_iterator); + + device_node = klist_next(&device_iterator); + while (device_node) { + local_device_data = container_of(device_node, + struct cryp_device_data, list_node); + spin_lock(&local_device_data->ctx_lock); + /* current_ctx allocates a device, NULL = unallocated */ + if (local_device_data->current_ctx) { + device_node = klist_next(&device_iterator); + } else { + local_device_data->current_ctx = ctx; + ctx->device = local_device_data; + spin_unlock(&local_device_data->ctx_lock); + break; + } + spin_unlock(&local_device_data->ctx_lock); + } + klist_iter_exit(&device_iterator); + + if (!device_node) { + /** + * No free device found. + * Since we allocated a device with down_interruptible, this + * should not be able to happen. + * Number of available devices, which are contained in + * device_allocation, is therefore decremented by not doing + * an up(device_allocation). + */ + return -EBUSY; + } + + *device_data = local_device_data; + + return 0; +} + +static void cryp_dma_setup_channel(struct cryp_device_data *device_data, + struct device *dev) +{ + dma_cap_zero(device_data->dma.mask); + dma_cap_set(DMA_SLAVE, device_data->dma.mask); + + device_data->dma.cfg_mem2cryp = mem_to_engine; + device_data->dma.chan_mem2cryp = + dma_request_channel(device_data->dma.mask, + stedma40_filter, + device_data->dma.cfg_mem2cryp); + + device_data->dma.cfg_cryp2mem = engine_to_mem; + device_data->dma.chan_cryp2mem = + dma_request_channel(device_data->dma.mask, + stedma40_filter, + device_data->dma.cfg_cryp2mem); + + init_completion(&device_data->dma.cryp_dma_complete); +} + +static void cryp_dma_out_callback(void *data) +{ + struct cryp_ctx *ctx = (struct cryp_ctx *) data; + dev_dbg(ctx->device->dev, "[%s]: ", __func__); + + complete(&ctx->device->dma.cryp_dma_complete); +} + +static int cryp_set_dma_transfer(struct cryp_ctx *ctx, + struct scatterlist *sg, + int len, + enum dma_data_direction direction) +{ + struct dma_async_tx_descriptor *desc; + struct dma_chan *channel = NULL; + dma_cookie_t cookie; + + dev_dbg(ctx->device->dev, "[%s]: ", __func__); + + if (unlikely(!IS_ALIGNED((u32)sg, 4))) { + dev_err(ctx->device->dev, "[%s]: Data in sg list isn't " + "aligned! Addr: 0x%08x", __func__, (u32)sg); + return -EFAULT; + } + + switch (direction) { + case DMA_TO_DEVICE: + channel = ctx->device->dma.chan_mem2cryp; + ctx->device->dma.sg_src = sg; + ctx->device->dma.sg_src_len = dma_map_sg(channel->device->dev, + ctx->device->dma.sg_src, + ctx->device->dma.nents_src, + direction); + + if (!ctx->device->dma.sg_src_len) { + dev_dbg(ctx->device->dev, + "[%s]: Could not map the sg list (TO_DEVICE)", + __func__); + return -EFAULT; + } + + dev_dbg(ctx->device->dev, "[%s]: Setting up DMA for buffer " + "(TO_DEVICE)", __func__); + + desc = channel->device->device_prep_slave_sg(channel, + ctx->device->dma.sg_src, + ctx->device->dma.sg_src_len, + direction, DMA_CTRL_ACK, NULL); + break; + + case DMA_FROM_DEVICE: + channel = ctx->device->dma.chan_cryp2mem; + ctx->device->dma.sg_dst = sg; + ctx->device->dma.sg_dst_len = dma_map_sg(channel->device->dev, + ctx->device->dma.sg_dst, + ctx->device->dma.nents_dst, + direction); + + if (!ctx->device->dma.sg_dst_len) { + dev_dbg(ctx->device->dev, + "[%s]: Could not map the sg list (FROM_DEVICE)", + __func__); + return -EFAULT; + } + + dev_dbg(ctx->device->dev, "[%s]: Setting up DMA for buffer " + "(FROM_DEVICE)", __func__); + + desc = channel->device->device_prep_slave_sg(channel, + ctx->device->dma.sg_dst, + ctx->device->dma.sg_dst_len, + direction, + DMA_CTRL_ACK | + DMA_PREP_INTERRUPT, NULL); + + desc->callback = cryp_dma_out_callback; + desc->callback_param = ctx; + break; + + default: + dev_dbg(ctx->device->dev, "[%s]: Invalid DMA direction", + __func__); + return -EFAULT; + } + + cookie = desc->tx_submit(desc); + dma_async_issue_pending(channel); + + return 0; +} + +static void cryp_dma_done(struct cryp_ctx *ctx) +{ + struct dma_chan *chan; + + dev_dbg(ctx->device->dev, "[%s]: ", __func__); + + chan = ctx->device->dma.chan_mem2cryp; + chan->device->device_control(chan, DMA_TERMINATE_ALL, 0); + dma_unmap_sg(chan->device->dev, ctx->device->dma.sg_src, + ctx->device->dma.sg_src_len, DMA_TO_DEVICE); + + chan = ctx->device->dma.chan_cryp2mem; + chan->device->device_control(chan, DMA_TERMINATE_ALL, 0); + dma_unmap_sg(chan->device->dev, ctx->device->dma.sg_dst, + ctx->device->dma.sg_dst_len, DMA_FROM_DEVICE); +} + +static int cryp_dma_write(struct cryp_ctx *ctx, struct scatterlist *sg, + int len) +{ + int error = cryp_set_dma_transfer(ctx, sg, len, DMA_TO_DEVICE); + dev_dbg(ctx->device->dev, "[%s]: ", __func__); + + if (error) { + dev_dbg(ctx->device->dev, "[%s]: cryp_set_dma_transfer() " + "failed", __func__); + return error; + } + + return len; +} + +static int cryp_dma_read(struct cryp_ctx *ctx, struct scatterlist *sg, int len) +{ + int error = cryp_set_dma_transfer(ctx, sg, len, DMA_FROM_DEVICE); + if (error) { + dev_dbg(ctx->device->dev, "[%s]: cryp_set_dma_transfer() " + "failed", __func__); + return error; + } + + return len; +} + +static void cryp_polling_mode(struct cryp_ctx *ctx, + struct cryp_device_data *device_data) +{ + int len = ctx->blocksize / BYTES_PER_WORD; + int remaining_length = ctx->datalen; + u32 *indata = (u32 *)ctx->indata; + u32 *outdata = (u32 *)ctx->outdata; + + while (remaining_length > 0) { + writesl(&device_data->base->din, indata, len); + indata += len; + remaining_length -= (len * BYTES_PER_WORD); + cryp_wait_until_done(device_data); + + readsl(&device_data->base->dout, outdata, len); + outdata += len; + cryp_wait_until_done(device_data); + } +} + +static int cryp_disable_power(struct device *dev, + struct cryp_device_data *device_data, + bool save_device_context) +{ + int ret = 0; + + dev_dbg(dev, "[%s]", __func__); + + spin_lock(&device_data->power_state_spinlock); + if (!device_data->power_state) + goto out; + + spin_lock(&device_data->ctx_lock); + if (save_device_context && device_data->current_ctx) { + cryp_save_device_context(device_data, + &device_data->current_ctx->dev_ctx, + cryp_mode); + device_data->restore_dev_ctx = true; + } + spin_unlock(&device_data->ctx_lock); + + clk_disable(device_data->clk); + ret = regulator_disable(device_data->pwr_regulator); + if (ret) + dev_err(dev, "[%s]: " + "regulator_disable() failed!", + __func__); + + device_data->power_state = false; + +out: + spin_unlock(&device_data->power_state_spinlock); + + return ret; +} + +static int cryp_enable_power( + struct device *dev, + struct cryp_device_data *device_data, + bool restore_device_context) +{ + int ret = 0; + + dev_dbg(dev, "[%s]", __func__); + + spin_lock(&device_data->power_state_spinlock); + if (!device_data->power_state) { + ret = regulator_enable(device_data->pwr_regulator); + if (ret) { + dev_err(dev, "[%s]: regulator_enable() failed!", + __func__); + goto out; + } + + ret = clk_enable(device_data->clk); + if (ret) { + dev_err(dev, "[%s]: clk_enable() failed!", + __func__); + regulator_disable(device_data->pwr_regulator); + goto out; + } + device_data->power_state = true; + } + + if (device_data->restore_dev_ctx) { + spin_lock(&device_data->ctx_lock); + if (restore_device_context && device_data->current_ctx) { + device_data->restore_dev_ctx = false; + cryp_restore_device_context(device_data, + &device_data->current_ctx->dev_ctx); + } + spin_unlock(&device_data->ctx_lock); + } +out: + spin_unlock(&device_data->power_state_spinlock); + + return ret; +} + +static int hw_crypt_noxts(struct cryp_ctx *ctx, + struct cryp_device_data *device_data) +{ + int ret = 0; + + const u8 *indata = ctx->indata; + u8 *outdata = ctx->outdata; + u32 datalen = ctx->datalen; + u32 outlen = datalen; + + pr_debug(DEV_DBG_NAME " [%s]", __func__); + + ctx->outlen = ctx->datalen; + + if (unlikely(!IS_ALIGNED((u32)indata, 4))) { + pr_debug(DEV_DBG_NAME " [%s]: Data isn't aligned! Addr: " + "0x%08x", __func__, (u32)indata); + return -EINVAL; + } + + ret = cryp_setup_context(ctx, device_data); + + if (ret) + goto out; + + if (cryp_mode == CRYP_MODE_INTERRUPT) { + cryp_enable_irq_src(device_data, CRYP_IRQ_SRC_INPUT_FIFO | + CRYP_IRQ_SRC_OUTPUT_FIFO); + + /* + * ctx->outlen is decremented in the cryp_interrupt_handler + * function. We had to add cpu_relax() (barrier) to make sure + * that gcc didn't optimze away this variable. + */ + while (ctx->outlen > 0) + cpu_relax(); + } else if (cryp_mode == CRYP_MODE_POLLING || + cryp_mode == CRYP_MODE_DMA) { + /* + * The reason for having DMA in this if case is that if we are + * running cryp_mode = 2, then we separate DMA routines for + * handling cipher/plaintext > blocksize, except when + * running the normal CRYPTO_ALG_TYPE_CIPHER, then we still use + * the polling mode. Overhead of doing DMA setup eats up the + * benefits using it. + */ + cryp_polling_mode(ctx, device_data); + } else { + dev_err(ctx->device->dev, "[%s]: Invalid operation mode!", + __func__); + ret = -EPERM; + goto out; + } + + cryp_save_device_context(device_data, &ctx->dev_ctx, cryp_mode); + ctx->updated = 1; + +out: + ctx->indata = indata; + ctx->outdata = outdata; + ctx->datalen = datalen; + ctx->outlen = outlen; + + return ret; +} + +static int get_nents(struct scatterlist *sg, int nbytes) +{ + int nents = 0; + + while (nbytes > 0) { + nbytes -= sg->length; + sg = scatterwalk_sg_next(sg); + nents++; + } + + return nents; +} + +static int ablk_dma_crypt(struct ablkcipher_request *areq) +{ + struct crypto_ablkcipher *cipher = crypto_ablkcipher_reqtfm(areq); + struct cryp_ctx *ctx = crypto_ablkcipher_ctx(cipher); + struct cryp_device_data *device_data; + + int bytes_written = 0; + int bytes_read = 0; + int ret; + + pr_debug(DEV_DBG_NAME " [%s]", __func__); + + ctx->datalen = areq->nbytes; + ctx->outlen = areq->nbytes; + + ret = cryp_get_device_data(ctx, &device_data); + if (ret) + return ret; + + ret = cryp_setup_context(ctx, device_data); + if (ret) + goto out; + + /* We have the device now, so store the nents in the dma struct. */ + ctx->device->dma.nents_src = get_nents(areq->src, ctx->datalen); + ctx->device->dma.nents_dst = get_nents(areq->dst, ctx->outlen); + + /* Enable DMA in- and output. */ + cryp_configure_for_dma(device_data, CRYP_DMA_ENABLE_BOTH_DIRECTIONS); + + bytes_written = cryp_dma_write(ctx, areq->src, ctx->datalen); + bytes_read = cryp_dma_read(ctx, areq->dst, bytes_written); + + wait_for_completion(&ctx->device->dma.cryp_dma_complete); + cryp_dma_done(ctx); + + cryp_save_device_context(device_data, &ctx->dev_ctx, cryp_mode); + ctx->updated = 1; + +out: + spin_lock(&device_data->ctx_lock); + device_data->current_ctx = NULL; + ctx->device = NULL; + spin_unlock(&device_data->ctx_lock); + + /* + * The down_interruptible part for this semaphore is called in + * cryp_get_device_data. + */ + up(&driver_data.device_allocation); + + if (unlikely(bytes_written != bytes_read)) + return -EPERM; + + return 0; +} + +static int ablk_crypt(struct ablkcipher_request *areq) +{ + struct ablkcipher_walk walk; + struct crypto_ablkcipher *cipher = crypto_ablkcipher_reqtfm(areq); + struct cryp_ctx *ctx = crypto_ablkcipher_ctx(cipher); + struct cryp_device_data *device_data; + unsigned long src_paddr; + unsigned long dst_paddr; + int ret; + int nbytes; + + pr_debug(DEV_DBG_NAME " [%s]", __func__); + + ret = cryp_get_device_data(ctx, &device_data); + if (ret) + goto out; + + ablkcipher_walk_init(&walk, areq->dst, areq->src, areq->nbytes); + ret = ablkcipher_walk_phys(areq, &walk); + + if (ret) { + pr_err(DEV_DBG_NAME "[%s]: ablkcipher_walk_phys() failed!", + __func__); + goto out; + } + + while ((nbytes = walk.nbytes) > 0) { + ctx->iv = walk.iv; + src_paddr = (page_to_phys(walk.src.page) + walk.src.offset); + ctx->indata = phys_to_virt(src_paddr); + + dst_paddr = (page_to_phys(walk.dst.page) + walk.dst.offset); + ctx->outdata = phys_to_virt(dst_paddr); + + ctx->datalen = nbytes - (nbytes % ctx->blocksize); + + ret = hw_crypt_noxts(ctx, device_data); + if (ret) + goto out; + + nbytes -= ctx->datalen; + ret = ablkcipher_walk_done(areq, &walk, nbytes); + if (ret) + goto out; + } + ablkcipher_walk_complete(&walk); + +out: + /* Release the device */ + spin_lock(&device_data->ctx_lock); + device_data->current_ctx = NULL; + ctx->device = NULL; + spin_unlock(&device_data->ctx_lock); + + /* + * The down_interruptible part for this semaphore is called in + * cryp_get_device_data. + */ + up(&driver_data.device_allocation); + + return ret; +} + +static int aes_ablkcipher_setkey(struct crypto_ablkcipher *cipher, + const u8 *key, unsigned int keylen) +{ + struct cryp_ctx *ctx = crypto_ablkcipher_ctx(cipher); + u32 *flags = &cipher->base.crt_flags; + + pr_debug(DEV_DBG_NAME " [%s]", __func__); + + switch (keylen) { + case AES_KEYSIZE_128: + ctx->config.keysize = CRYP_KEY_SIZE_128; + break; + + case AES_KEYSIZE_192: + ctx->config.keysize = CRYP_KEY_SIZE_192; + break; + + case AES_KEYSIZE_256: + ctx->config.keysize = CRYP_KEY_SIZE_256; + break; + + default: + pr_err(DEV_DBG_NAME "[%s]: Unknown keylen!", __func__); + *flags |= CRYPTO_TFM_RES_BAD_KEY_LEN; + return -EINVAL; + } + + memcpy(ctx->key, key, keylen); + ctx->keylen = keylen; + + ctx->updated = 0; + + return 0; +} + +static int des_ablkcipher_setkey(struct crypto_ablkcipher *cipher, + const u8 *key, unsigned int keylen) +{ + struct cryp_ctx *ctx = crypto_ablkcipher_ctx(cipher); + u32 *flags = &cipher->base.crt_flags; + u32 tmp[DES_EXPKEY_WORDS]; + int ret; + + pr_debug(DEV_DBG_NAME " [%s]", __func__); + if (keylen != DES_KEY_SIZE) { + *flags |= CRYPTO_TFM_RES_BAD_KEY_LEN; + pr_debug(DEV_DBG_NAME " [%s]: CRYPTO_TFM_RES_BAD_KEY_LEN", + __func__); + return -EINVAL; + } + + ret = des_ekey(tmp, key); + if (unlikely(ret == 0) && (*flags & CRYPTO_TFM_REQ_WEAK_KEY)) { + *flags |= CRYPTO_TFM_RES_WEAK_KEY; + pr_debug(DEV_DBG_NAME " [%s]: CRYPTO_TFM_REQ_WEAK_KEY", + __func__); + return -EINVAL; + } + + memcpy(ctx->key, key, keylen); + ctx->keylen = keylen; + + ctx->updated = 0; + return 0; +} + +static int des3_ablkcipher_setkey(struct crypto_ablkcipher *cipher, + const u8 *key, unsigned int keylen) +{ + struct cryp_ctx *ctx = crypto_ablkcipher_ctx(cipher); + u32 *flags = &cipher->base.crt_flags; + const u32 *K = (const u32 *)key; + u32 tmp[DES3_EDE_EXPKEY_WORDS]; + int i, ret; + + pr_debug(DEV_DBG_NAME " [%s]", __func__); + if (keylen != DES3_EDE_KEY_SIZE) { + *flags |= CRYPTO_TFM_RES_BAD_KEY_LEN; + pr_debug(DEV_DBG_NAME " [%s]: CRYPTO_TFM_RES_BAD_KEY_LEN", + __func__); + return -EINVAL; + } + + /* Checking key interdependency for weak key detection. */ + if (unlikely(!((K[0] ^ K[2]) | (K[1] ^ K[3])) || + !((K[2] ^ K[4]) | (K[3] ^ K[5]))) && + (*flags & CRYPTO_TFM_REQ_WEAK_KEY)) { + *flags |= CRYPTO_TFM_RES_WEAK_KEY; + pr_debug(DEV_DBG_NAME " [%s]: CRYPTO_TFM_REQ_WEAK_KEY", + __func__); + return -EINVAL; + } + for (i = 0; i < 3; i++) { + ret = des_ekey(tmp, key + i*DES_KEY_SIZE); + if (unlikely(ret == 0) && (*flags & CRYPTO_TFM_REQ_WEAK_KEY)) { + *flags |= CRYPTO_TFM_RES_WEAK_KEY; + pr_debug(DEV_DBG_NAME " [%s]: " + "CRYPTO_TFM_REQ_WEAK_KEY", __func__); + return -EINVAL; + } + } + + memcpy(ctx->key, key, keylen); + ctx->keylen = keylen; + + ctx->updated = 0; + return 0; +} + +static int cryp_blk_encrypt(struct ablkcipher_request *areq) +{ + struct crypto_ablkcipher *cipher = crypto_ablkcipher_reqtfm(areq); + struct cryp_ctx *ctx = crypto_ablkcipher_ctx(cipher); + + pr_debug(DEV_DBG_NAME " [%s]", __func__); + + ctx->config.algodir = CRYP_ALGORITHM_ENCRYPT; + + /* + * DMA does not work for DES due to a hw bug */ + if (cryp_mode == CRYP_MODE_DMA && mode_is_aes(ctx->config.algomode)) + return ablk_dma_crypt(areq); + + /* For everything except DMA, we run the non DMA version. */ + return ablk_crypt(areq); +} + +static int cryp_blk_decrypt(struct ablkcipher_request *areq) +{ + struct crypto_ablkcipher *cipher = crypto_ablkcipher_reqtfm(areq); + struct cryp_ctx *ctx = crypto_ablkcipher_ctx(cipher); + + pr_debug(DEV_DBG_NAME " [%s]", __func__); + + ctx->config.algodir = CRYP_ALGORITHM_DECRYPT; + + /* DMA does not work for DES due to a hw bug */ + if (cryp_mode == CRYP_MODE_DMA && mode_is_aes(ctx->config.algomode)) + return ablk_dma_crypt(areq); + + /* For everything except DMA, we run the non DMA version. */ + return ablk_crypt(areq); +} + +struct cryp_algo_template { + enum cryp_algo_mode algomode; + struct crypto_alg crypto; +}; + +static int cryp_cra_init(struct crypto_tfm *tfm) +{ + struct cryp_ctx *ctx = crypto_tfm_ctx(tfm); + struct crypto_alg *alg = tfm->__crt_alg; + struct cryp_algo_template *cryp_alg = container_of(alg, + struct cryp_algo_template, + crypto); + + ctx->config.algomode = cryp_alg->algomode; + ctx->blocksize = crypto_tfm_alg_blocksize(tfm); + + return 0; +} + +static struct cryp_algo_template cryp_algs[] = { + { + .algomode = CRYP_ALGO_AES_ECB, + .crypto = { + .cra_name = "aes", + .cra_driver_name = "aes-ux500", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_ABLKCIPHER | + CRYPTO_ALG_ASYNC, + .cra_blocksize = AES_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct cryp_ctx), + .cra_alignmask = 3, + .cra_type = &crypto_ablkcipher_type, + .cra_init = cryp_cra_init, + .cra_module = THIS_MODULE, + .cra_u = { + .ablkcipher = { + .min_keysize = AES_MIN_KEY_SIZE, + .max_keysize = AES_MAX_KEY_SIZE, + .setkey = aes_ablkcipher_setkey, + .encrypt = cryp_blk_encrypt, + .decrypt = cryp_blk_decrypt + } + } + } + }, + { + .algomode = CRYP_ALGO_AES_ECB, + .crypto = { + .cra_name = "ecb(aes)", + .cra_driver_name = "ecb-aes-ux500", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_ABLKCIPHER | + CRYPTO_ALG_ASYNC, + .cra_blocksize = AES_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct cryp_ctx), + .cra_alignmask = 3, + .cra_type = &crypto_ablkcipher_type, + .cra_init = cryp_cra_init, + .cra_module = THIS_MODULE, + .cra_u = { + .ablkcipher = { + .min_keysize = AES_MIN_KEY_SIZE, + .max_keysize = AES_MAX_KEY_SIZE, + .setkey = aes_ablkcipher_setkey, + .encrypt = cryp_blk_encrypt, + .decrypt = cryp_blk_decrypt, + } + } + } + }, + { + .algomode = CRYP_ALGO_AES_CBC, + .crypto = { + .cra_name = "cbc(aes)", + .cra_driver_name = "cbc-aes-ux500", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_ABLKCIPHER | + CRYPTO_ALG_ASYNC, + .cra_blocksize = AES_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct cryp_ctx), + .cra_alignmask = 3, + .cra_type = &crypto_ablkcipher_type, + .cra_init = cryp_cra_init, + .cra_module = THIS_MODULE, + .cra_u = { + .ablkcipher = { + .min_keysize = AES_MIN_KEY_SIZE, + .max_keysize = AES_MAX_KEY_SIZE, + .setkey = aes_ablkcipher_setkey, + .encrypt = cryp_blk_encrypt, + .decrypt = cryp_blk_decrypt, + .ivsize = AES_BLOCK_SIZE, + } + } + } + }, + { + .algomode = CRYP_ALGO_AES_CTR, + .crypto = { + .cra_name = "ctr(aes)", + .cra_driver_name = "ctr-aes-ux500", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_ABLKCIPHER | + CRYPTO_ALG_ASYNC, + .cra_blocksize = AES_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct cryp_ctx), + .cra_alignmask = 3, + .cra_type = &crypto_ablkcipher_type, + .cra_init = cryp_cra_init, + .cra_module = THIS_MODULE, + .cra_u = { + .ablkcipher = { + .min_keysize = AES_MIN_KEY_SIZE, + .max_keysize = AES_MAX_KEY_SIZE, + .setkey = aes_ablkcipher_setkey, + .encrypt = cryp_blk_encrypt, + .decrypt = cryp_blk_decrypt, + .ivsize = AES_BLOCK_SIZE, + } + } + } + }, + { + .algomode = CRYP_ALGO_DES_ECB, + .crypto = { + .cra_name = "des", + .cra_driver_name = "des-ux500", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_ABLKCIPHER | + CRYPTO_ALG_ASYNC, + .cra_blocksize = DES_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct cryp_ctx), + .cra_alignmask = 3, + .cra_type = &crypto_ablkcipher_type, + .cra_init = cryp_cra_init, + .cra_module = THIS_MODULE, + .cra_u = { + .ablkcipher = { + .min_keysize = DES_KEY_SIZE, + .max_keysize = DES_KEY_SIZE, + .setkey = des_ablkcipher_setkey, + .encrypt = cryp_blk_encrypt, + .decrypt = cryp_blk_decrypt + } + } + } + + }, + { + .algomode = CRYP_ALGO_TDES_ECB, + .crypto = { + .cra_name = "des3_ede", + .cra_driver_name = "des3_ede-ux500", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_ABLKCIPHER | + CRYPTO_ALG_ASYNC, + .cra_blocksize = DES3_EDE_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct cryp_ctx), + .cra_alignmask = 3, + .cra_type = &crypto_ablkcipher_type, + .cra_init = cryp_cra_init, + .cra_module = THIS_MODULE, + .cra_u = { + .ablkcipher = { + .min_keysize = DES3_EDE_KEY_SIZE, + .max_keysize = DES3_EDE_KEY_SIZE, + .setkey = des_ablkcipher_setkey, + .encrypt = cryp_blk_encrypt, + .decrypt = cryp_blk_decrypt + } + } + } + }, + { + .algomode = CRYP_ALGO_DES_ECB, + .crypto = { + .cra_name = "ecb(des)", + .cra_driver_name = "ecb-des-ux500", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_ABLKCIPHER | + CRYPTO_ALG_ASYNC, + .cra_blocksize = DES_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct cryp_ctx), + .cra_alignmask = 3, + .cra_type = &crypto_ablkcipher_type, + .cra_init = cryp_cra_init, + .cra_module = THIS_MODULE, + .cra_u = { + .ablkcipher = { + .min_keysize = DES_KEY_SIZE, + .max_keysize = DES_KEY_SIZE, + .setkey = des_ablkcipher_setkey, + .encrypt = cryp_blk_encrypt, + .decrypt = cryp_blk_decrypt, + } + } + } + }, + { + .algomode = CRYP_ALGO_TDES_ECB, + .crypto = { + .cra_name = "ecb(des3_ede)", + .cra_driver_name = "ecb-des3_ede-ux500", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_ABLKCIPHER | + CRYPTO_ALG_ASYNC, + .cra_blocksize = DES3_EDE_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct cryp_ctx), + .cra_alignmask = 3, + .cra_type = &crypto_ablkcipher_type, + .cra_init = cryp_cra_init, + .cra_module = THIS_MODULE, + .cra_u = { + .ablkcipher = { + .min_keysize = DES3_EDE_KEY_SIZE, + .max_keysize = DES3_EDE_KEY_SIZE, + .setkey = des3_ablkcipher_setkey, + .encrypt = cryp_blk_encrypt, + .decrypt = cryp_blk_decrypt, + } + } + } + }, + { + .algomode = CRYP_ALGO_DES_CBC, + .crypto = { + .cra_name = "cbc(des)", + .cra_driver_name = "cbc-des-ux500", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_ABLKCIPHER | + CRYPTO_ALG_ASYNC, + .cra_blocksize = DES_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct cryp_ctx), + .cra_alignmask = 3, + .cra_type = &crypto_ablkcipher_type, + .cra_init = cryp_cra_init, + .cra_module = THIS_MODULE, + .cra_u = { + .ablkcipher = { + .min_keysize = DES_KEY_SIZE, + .max_keysize = DES_KEY_SIZE, + .setkey = des_ablkcipher_setkey, + .encrypt = cryp_blk_encrypt, + .decrypt = cryp_blk_decrypt, + } + } + } + }, + { + .algomode = CRYP_ALGO_TDES_CBC, + .crypto = { + .cra_name = "cbc(des3_ede)", + .cra_driver_name = "cbc-des3_ede-ux500", + .cra_priority = 300, + .cra_flags = CRYPTO_ALG_TYPE_ABLKCIPHER | + CRYPTO_ALG_ASYNC, + .cra_blocksize = DES3_EDE_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct cryp_ctx), + .cra_alignmask = 3, + .cra_type = &crypto_ablkcipher_type, + .cra_init = cryp_cra_init, + .cra_module = THIS_MODULE, + .cra_u = { + .ablkcipher = { + .min_keysize = DES3_EDE_KEY_SIZE, + .max_keysize = DES3_EDE_KEY_SIZE, + .setkey = des3_ablkcipher_setkey, + .encrypt = cryp_blk_encrypt, + .decrypt = cryp_blk_decrypt, + .ivsize = DES3_EDE_BLOCK_SIZE, + } + } + } + } +}; + +/** + * cryp_algs_register_all - + */ +static int cryp_algs_register_all(void) +{ + int ret; + int i; + int count; + + pr_debug("[%s]", __func__); + + for (i = 0; i < ARRAY_SIZE(cryp_algs); i++) { + ret = crypto_register_alg(&cryp_algs[i].crypto); + if (ret) { + count = i; + pr_err("[%s] alg registration failed", + cryp_algs[i].crypto.cra_driver_name); + goto unreg; + } + } + return 0; +unreg: + for (i = 0; i < count; i++) + crypto_unregister_alg(&cryp_algs[i].crypto); + return ret; +} + +/** + * cryp_algs_unregister_all - + */ +static void cryp_algs_unregister_all(void) +{ + int i; + + pr_debug(DEV_DBG_NAME " [%s]", __func__); + + for (i = 0; i < ARRAY_SIZE(cryp_algs); i++) + crypto_unregister_alg(&cryp_algs[i].crypto); +} + +static int ux500_cryp_probe(struct platform_device *pdev) +{ + int ret; + int cryp_error = 0; + struct resource *res = NULL; + struct resource *res_irq = NULL; + struct cryp_device_data *device_data; + struct cryp_protection_config prot = { + .privilege_access = CRYP_STATE_ENABLE + }; + struct device *dev = &pdev->dev; + + dev_dbg(dev, "[%s]", __func__); + device_data = kzalloc(sizeof(struct cryp_device_data), GFP_ATOMIC); + if (!device_data) { + dev_err(dev, "[%s]: kzalloc() failed!", __func__); + ret = -ENOMEM; + goto out; + } + + device_data->dev = dev; + device_data->current_ctx = NULL; + + /* Grab the DMA configuration from platform data. */ + mem_to_engine = &((struct cryp_platform_data *) + dev->platform_data)->mem_to_engine; + engine_to_mem = &((struct cryp_platform_data *) + dev->platform_data)->engine_to_mem; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "[%s]: platform_get_resource() failed", + __func__); + ret = -ENODEV; + goto out_kfree; + } + + res = request_mem_region(res->start, resource_size(res), pdev->name); + if (res == NULL) { + dev_err(dev, "[%s]: request_mem_region() failed", + __func__); + ret = -EBUSY; + goto out_kfree; + } + + device_data->base = ioremap(res->start, resource_size(res)); + if (!device_data->base) { + dev_err(dev, "[%s]: ioremap failed!", __func__); + ret = -ENOMEM; + goto out_free_mem; + } + + spin_lock_init(&device_data->ctx_lock); + spin_lock_init(&device_data->power_state_spinlock); + + /* Enable power for CRYP hardware block */ + device_data->pwr_regulator = regulator_get(&pdev->dev, "v-ape"); + if (IS_ERR(device_data->pwr_regulator)) { + dev_err(dev, "[%s]: could not get cryp regulator", __func__); + ret = PTR_ERR(device_data->pwr_regulator); + device_data->pwr_regulator = NULL; + goto out_unmap; + } + + /* Enable the clk for CRYP hardware block */ + device_data->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(device_data->clk)) { + dev_err(dev, "[%s]: clk_get() failed!", __func__); + ret = PTR_ERR(device_data->clk); + goto out_regulator; + } + + /* Enable device power (and clock) */ + ret = cryp_enable_power(device_data->dev, device_data, false); + if (ret) { + dev_err(dev, "[%s]: cryp_enable_power() failed!", __func__); + goto out_clk; + } + + cryp_error = cryp_check(device_data); + if (cryp_error != 0) { + dev_err(dev, "[%s]: cryp_init() failed!", __func__); + ret = -EINVAL; + goto out_power; + } + + cryp_error = cryp_configure_protection(device_data, &prot); + if (cryp_error != 0) { + dev_err(dev, "[%s]: cryp_configure_protection() failed!", + __func__); + ret = -EINVAL; + goto out_power; + } + + res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res_irq) { + dev_err(dev, "[%s]: IORESOURCE_IRQ unavailable", + __func__); + goto out_power; + } + + ret = request_irq(res_irq->start, + cryp_interrupt_handler, + 0, + "cryp1", + device_data); + if (ret) { + dev_err(dev, "[%s]: Unable to request IRQ", __func__); + goto out_power; + } + + if (cryp_mode == CRYP_MODE_DMA) + cryp_dma_setup_channel(device_data, dev); + + platform_set_drvdata(pdev, device_data); + + /* Put the new device into the device list... */ + klist_add_tail(&device_data->list_node, &driver_data.device_list); + + /* ... and signal that a new device is available. */ + up(&driver_data.device_allocation); + + atomic_set(&session_id, 1); + + ret = cryp_algs_register_all(); + if (ret) { + dev_err(dev, "[%s]: cryp_algs_register_all() failed!", + __func__); + goto out_power; + } + + return 0; + +out_power: + cryp_disable_power(device_data->dev, device_data, false); + +out_clk: + clk_put(device_data->clk); + +out_regulator: + regulator_put(device_data->pwr_regulator); + +out_unmap: + iounmap(device_data->base); + +out_free_mem: + release_mem_region(res->start, resource_size(res)); + +out_kfree: + kfree(device_data); +out: + return ret; +} + +static int ux500_cryp_remove(struct platform_device *pdev) +{ + struct resource *res = NULL; + struct resource *res_irq = NULL; + struct cryp_device_data *device_data; + + dev_dbg(&pdev->dev, "[%s]", __func__); + device_data = platform_get_drvdata(pdev); + if (!device_data) { + dev_err(&pdev->dev, "[%s]: platform_get_drvdata() failed!", + __func__); + return -ENOMEM; + } + + /* Try to decrease the number of available devices. */ + if (down_trylock(&driver_data.device_allocation)) + return -EBUSY; + + /* Check that the device is free */ + spin_lock(&device_data->ctx_lock); + /* current_ctx allocates a device, NULL = unallocated */ + if (device_data->current_ctx) { + /* The device is busy */ + spin_unlock(&device_data->ctx_lock); + /* Return the device to the pool. */ + up(&driver_data.device_allocation); + return -EBUSY; + } + + spin_unlock(&device_data->ctx_lock); + + /* Remove the device from the list */ + if (klist_node_attached(&device_data->list_node)) + klist_remove(&device_data->list_node); + + /* If this was the last device, remove the services */ + if (list_empty(&driver_data.device_list.k_list)) + cryp_algs_unregister_all(); + + res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res_irq) + dev_err(&pdev->dev, "[%s]: IORESOURCE_IRQ, unavailable", + __func__); + else { + disable_irq(res_irq->start); + free_irq(res_irq->start, device_data); + } + + if (cryp_disable_power(&pdev->dev, device_data, false)) + dev_err(&pdev->dev, "[%s]: cryp_disable_power() failed", + __func__); + + clk_put(device_data->clk); + regulator_put(device_data->pwr_regulator); + + iounmap(device_data->base); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res) + release_mem_region(res->start, res->end - res->start + 1); + + kfree(device_data); + + return 0; +} + +static void ux500_cryp_shutdown(struct platform_device *pdev) +{ + struct resource *res_irq = NULL; + struct cryp_device_data *device_data; + + dev_dbg(&pdev->dev, "[%s]", __func__); + + device_data = platform_get_drvdata(pdev); + if (!device_data) { + dev_err(&pdev->dev, "[%s]: platform_get_drvdata() failed!", + __func__); + return; + } + + /* Check that the device is free */ + spin_lock(&device_data->ctx_lock); + /* current_ctx allocates a device, NULL = unallocated */ + if (!device_data->current_ctx) { + if (down_trylock(&driver_data.device_allocation)) + dev_dbg(&pdev->dev, "[%s]: Cryp still in use!" + "Shutting down anyway...", __func__); + /** + * (Allocate the device) + * Need to set this to non-null (dummy) value, + * to avoid usage if context switching. + */ + device_data->current_ctx++; + } + spin_unlock(&device_data->ctx_lock); + + /* Remove the device from the list */ + if (klist_node_attached(&device_data->list_node)) + klist_remove(&device_data->list_node); + + /* If this was the last device, remove the services */ + if (list_empty(&driver_data.device_list.k_list)) + cryp_algs_unregister_all(); + + res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res_irq) + dev_err(&pdev->dev, "[%s]: IORESOURCE_IRQ, unavailable", + __func__); + else { + disable_irq(res_irq->start); + free_irq(res_irq->start, device_data); + } + + if (cryp_disable_power(&pdev->dev, device_data, false)) + dev_err(&pdev->dev, "[%s]: cryp_disable_power() failed", + __func__); + +} + +static int ux500_cryp_suspend(struct platform_device *pdev, pm_message_t state) +{ + int ret; + struct cryp_device_data *device_data; + struct resource *res_irq; + struct cryp_ctx *temp_ctx = NULL; + + dev_dbg(&pdev->dev, "[%s]", __func__); + + /* Handle state? */ + device_data = platform_get_drvdata(pdev); + if (!device_data) { + dev_err(&pdev->dev, "[%s]: platform_get_drvdata() failed!", + __func__); + return -ENOMEM; + } + + res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res_irq) + dev_err(&pdev->dev, "[%s]: IORESOURCE_IRQ, unavailable", + __func__); + else + disable_irq(res_irq->start); + + spin_lock(&device_data->ctx_lock); + if (!device_data->current_ctx) + device_data->current_ctx++; + spin_unlock(&device_data->ctx_lock); + + if (device_data->current_ctx == ++temp_ctx) { + if (down_interruptible(&driver_data.device_allocation)) + dev_dbg(&pdev->dev, "[%s]: down_interruptible() " + "failed", __func__); + ret = cryp_disable_power(&pdev->dev, device_data, false); + + } else + ret = cryp_disable_power(&pdev->dev, device_data, true); + + if (ret) + dev_err(&pdev->dev, "[%s]: cryp_disable_power()", __func__); + + return ret; +} + +static int ux500_cryp_resume(struct platform_device *pdev) +{ + int ret = 0; + struct cryp_device_data *device_data; + struct resource *res_irq; + struct cryp_ctx *temp_ctx = NULL; + + dev_dbg(&pdev->dev, "[%s]", __func__); + + device_data = platform_get_drvdata(pdev); + if (!device_data) { + dev_err(&pdev->dev, "[%s]: platform_get_drvdata() failed!", + __func__); + return -ENOMEM; + } + + spin_lock(&device_data->ctx_lock); + if (device_data->current_ctx == ++temp_ctx) + device_data->current_ctx = NULL; + spin_unlock(&device_data->ctx_lock); + + + if (!device_data->current_ctx) + up(&driver_data.device_allocation); + else + ret = cryp_enable_power(&pdev->dev, device_data, true); + + if (ret) + dev_err(&pdev->dev, "[%s]: cryp_enable_power() failed!", + __func__); + else { + res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (res_irq) + enable_irq(res_irq->start); + } + + return ret; +} + +static struct platform_driver cryp_driver = { + .probe = ux500_cryp_probe, + .remove = ux500_cryp_remove, + .shutdown = ux500_cryp_shutdown, + .suspend = ux500_cryp_suspend, + .resume = ux500_cryp_resume, + .driver = { + .owner = THIS_MODULE, + .name = "cryp1" + } +}; + +static int __init ux500_cryp_mod_init(void) +{ + pr_debug("[%s] is called!", __func__); + klist_init(&driver_data.device_list, NULL, NULL); + /* Initialize the semaphore to 0 devices (locked state) */ + sema_init(&driver_data.device_allocation, 0); + return platform_driver_register(&cryp_driver); +} + +static void __exit ux500_cryp_mod_fini(void) +{ + pr_debug("[%s] is called!", __func__); + platform_driver_unregister(&cryp_driver); + return; +} + +module_init(ux500_cryp_mod_init); +module_exit(ux500_cryp_mod_fini); + +module_param(cryp_mode, int, 0); + +MODULE_DESCRIPTION("Driver for ST-Ericsson UX500 CRYP crypto engine."); +MODULE_ALIAS("aes-all"); +MODULE_ALIAS("des-all"); + +MODULE_LICENSE("GPL"); diff --git a/drivers/crypto/ux500/cryp/cryp_irq.c b/drivers/crypto/ux500/cryp/cryp_irq.c new file mode 100644 index 000000000000..08d291cdbe6d --- /dev/null +++ b/drivers/crypto/ux500/cryp/cryp_irq.c @@ -0,0 +1,45 @@ +/** + * Copyright (C) ST-Ericsson SA 2010 + * Author: Shujuan Chen <[email protected]> for ST-Ericsson. + * Author: Jonas Linde <[email protected]> for ST-Ericsson. + * Author: Joakim Bech <[email protected]> for ST-Ericsson. + * Author: Berne Hebark <[email protected]> for ST-Ericsson. + * Author: Niklas Hernaeus <[email protected]> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2. + */ + +#include <linux/kernel.h> +#include <linux/bitmap.h> +#include <linux/device.h> + +#include "cryp.h" +#include "cryp_p.h" +#include "cryp_irq.h" +#include "cryp_irqp.h" + +void cryp_enable_irq_src(struct cryp_device_data *device_data, u32 irq_src) +{ + u32 i; + + dev_dbg(device_data->dev, "[%s]", __func__); + + i = readl_relaxed(&device_data->base->imsc); + i = i | irq_src; + writel_relaxed(i, &device_data->base->imsc); +} + +void cryp_disable_irq_src(struct cryp_device_data *device_data, u32 irq_src) +{ + u32 i; + + dev_dbg(device_data->dev, "[%s]", __func__); + + i = readl_relaxed(&device_data->base->imsc); + i = i & ~irq_src; + writel_relaxed(i, &device_data->base->imsc); +} + +bool cryp_pending_irq_src(struct cryp_device_data *device_data, u32 irq_src) +{ + return (readl_relaxed(&device_data->base->mis) & irq_src) > 0; +} diff --git a/drivers/crypto/ux500/cryp/cryp_irq.h b/drivers/crypto/ux500/cryp/cryp_irq.h new file mode 100644 index 000000000000..5a7837f1b8f9 --- /dev/null +++ b/drivers/crypto/ux500/cryp/cryp_irq.h @@ -0,0 +1,31 @@ +/** + * Copyright (C) ST-Ericsson SA 2010 + * Author: Shujuan Chen <[email protected]> for ST-Ericsson. + * Author: Jonas Linde <[email protected]> for ST-Ericsson. + * Author: Joakim Bech <[email protected]> for ST-Ericsson. + * Author: Berne Hebark <[email protected]> for ST-Ericsson. + * Author: Niklas Hernaeus <[email protected]> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2 + */ + +#ifndef _CRYP_IRQ_H_ +#define _CRYP_IRQ_H_ + +#include "cryp.h" + +enum cryp_irq_src_id { + CRYP_IRQ_SRC_INPUT_FIFO = 0x1, + CRYP_IRQ_SRC_OUTPUT_FIFO = 0x2, + CRYP_IRQ_SRC_ALL = 0x3 +}; + +/** + * M0 Funtions + */ +void cryp_enable_irq_src(struct cryp_device_data *device_data, u32 irq_src); + +void cryp_disable_irq_src(struct cryp_device_data *device_data, u32 irq_src); + +bool cryp_pending_irq_src(struct cryp_device_data *device_data, u32 irq_src); + +#endif /* _CRYP_IRQ_H_ */ diff --git a/drivers/crypto/ux500/cryp/cryp_irqp.h b/drivers/crypto/ux500/cryp/cryp_irqp.h new file mode 100644 index 000000000000..8b339cc34bf8 --- /dev/null +++ b/drivers/crypto/ux500/cryp/cryp_irqp.h @@ -0,0 +1,125 @@ +/** + * Copyright (C) ST-Ericsson SA 2010 + * Author: Shujuan Chen <[email protected]> for ST-Ericsson. + * Author: Jonas Linde <[email protected]> for ST-Ericsson. + * Author: Joakim Bech <[email protected]> for ST-Ericsson. + * Author: Berne Hebark <[email protected]> for ST-Ericsson. + * Author: Niklas Hernaeus <[email protected]> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2 + */ + +#ifndef __CRYP_IRQP_H_ +#define __CRYP_IRQP_H_ + +#include "cryp_irq.h" + +/** + * + * CRYP Registers - Offset mapping + * +-----------------+ + * 00h | CRYP_CR | Configuration register + * +-----------------+ + * 04h | CRYP_SR | Status register + * +-----------------+ + * 08h | CRYP_DIN | Data In register + * +-----------------+ + * 0ch | CRYP_DOUT | Data out register + * +-----------------+ + * 10h | CRYP_DMACR | DMA control register + * +-----------------+ + * 14h | CRYP_IMSC | IMSC + * +-----------------+ + * 18h | CRYP_RIS | Raw interrupt status + * +-----------------+ + * 1ch | CRYP_MIS | Masked interrupt status. + * +-----------------+ + * Key registers + * IVR registers + * Peripheral + * Cell IDs + * + * Refer data structure for other register map + */ + +/** + * struct cryp_register + * @cr - Configuration register + * @status - Status register + * @din - Data input register + * @din_size - Data input size register + * @dout - Data output register + * @dout_size - Data output size register + * @dmacr - Dma control register + * @imsc - Interrupt mask set/clear register + * @ris - Raw interrupt status + * @mis - Masked interrupt statu register + * @key_1_l - Key register 1 L + * @key_1_r - Key register 1 R + * @key_2_l - Key register 2 L + * @key_2_r - Key register 2 R + * @key_3_l - Key register 3 L + * @key_3_r - Key register 3 R + * @key_4_l - Key register 4 L + * @key_4_r - Key register 4 R + * @init_vect_0_l - init vector 0 L + * @init_vect_0_r - init vector 0 R + * @init_vect_1_l - init vector 1 L + * @init_vect_1_r - init vector 1 R + * @cryp_unused1 - unused registers + * @itcr - Integration test control register + * @itip - Integration test input register + * @itop - Integration test output register + * @cryp_unused2 - unused registers + * @periphId0 - FE0 CRYP Peripheral Identication Register + * @periphId1 - FE4 + * @periphId2 - FE8 + * @periphId3 - FEC + * @pcellId0 - FF0 CRYP PCell Identication Register + * @pcellId1 - FF4 + * @pcellId2 - FF8 + * @pcellId3 - FFC + */ +struct cryp_register { + u32 cr; /* Configuration register */ + u32 sr; /* Status register */ + u32 din; /* Data input register */ + u32 din_size; /* Data input size register */ + u32 dout; /* Data output register */ + u32 dout_size; /* Data output size register */ + u32 dmacr; /* Dma control register */ + u32 imsc; /* Interrupt mask set/clear register */ + u32 ris; /* Raw interrupt status */ + u32 mis; /* Masked interrupt statu register */ + + u32 key_1_l; /*Key register 1 L */ + u32 key_1_r; /*Key register 1 R */ + u32 key_2_l; /*Key register 2 L */ + u32 key_2_r; /*Key register 2 R */ + u32 key_3_l; /*Key register 3 L */ + u32 key_3_r; /*Key register 3 R */ + u32 key_4_l; /*Key register 4 L */ + u32 key_4_r; /*Key register 4 R */ + + u32 init_vect_0_l; /*init vector 0 L */ + u32 init_vect_0_r; /*init vector 0 R */ + u32 init_vect_1_l; /*init vector 1 L */ + u32 init_vect_1_r; /*init vector 1 R */ + + u32 cryp_unused1[(0x80 - 0x58) / sizeof(u32)]; /* unused registers */ + u32 itcr; /*Integration test control register */ + u32 itip; /*Integration test input register */ + u32 itop; /*Integration test output register */ + u32 cryp_unused2[(0xFE0 - 0x8C) / sizeof(u32)]; /* unused registers */ + + u32 periphId0; /* FE0 CRYP Peripheral Identication Register */ + u32 periphId1; /* FE4 */ + u32 periphId2; /* FE8 */ + u32 periphId3; /* FEC */ + + u32 pcellId0; /* FF0 CRYP PCell Identication Register */ + u32 pcellId1; /* FF4 */ + u32 pcellId2; /* FF8 */ + u32 pcellId3; /* FFC */ +}; + +#endif diff --git a/drivers/crypto/ux500/cryp/cryp_p.h b/drivers/crypto/ux500/cryp/cryp_p.h new file mode 100644 index 000000000000..6dcffe15c2bc --- /dev/null +++ b/drivers/crypto/ux500/cryp/cryp_p.h @@ -0,0 +1,123 @@ +/** + * Copyright (C) ST-Ericsson SA 2010 + * Author: Shujuan Chen <[email protected]> for ST-Ericsson. + * Author: Jonas Linde <[email protected]> for ST-Ericsson. + * Author: Joakim Bech <[email protected]> for ST-Ericsson. + * Author: Berne Hebark <[email protected]> for ST-Ericsson. + * Author: Niklas Hernaeus <[email protected]> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2 + */ + +#ifndef _CRYP_P_H_ +#define _CRYP_P_H_ + +#include <linux/io.h> +#include <linux/bitops.h> + +#include "cryp.h" +#include "cryp_irqp.h" + +/** + * Generic Macros + */ +#define CRYP_SET_BITS(reg_name, mask) \ + writel_relaxed((readl_relaxed(reg_name) | mask), reg_name) + +#define CRYP_WRITE_BIT(reg_name, val, mask) \ + writel_relaxed(((readl_relaxed(reg_name) & ~(mask)) |\ + ((val) & (mask))), reg_name) + +#define CRYP_TEST_BITS(reg_name, val) \ + (readl_relaxed(reg_name) & (val)) + +#define CRYP_PUT_BITS(reg, val, shift, mask) \ + writel_relaxed(((readl_relaxed(reg) & ~(mask)) | \ + (((u32)val << shift) & (mask))), reg) + +/** + * CRYP specific Macros + */ +#define CRYP_PERIPHERAL_ID0 0xE3 +#define CRYP_PERIPHERAL_ID1 0x05 + +#define CRYP_PERIPHERAL_ID2_DB8500 0x28 +#define CRYP_PERIPHERAL_ID3 0x00 + +#define CRYP_PCELL_ID0 0x0D +#define CRYP_PCELL_ID1 0xF0 +#define CRYP_PCELL_ID2 0x05 +#define CRYP_PCELL_ID3 0xB1 + +/** + * CRYP register default values + */ +#define MAX_DEVICE_SUPPORT 2 + +/* Priv set, keyrden set and datatype 8bits swapped set as default. */ +#define CRYP_CR_DEFAULT 0x0482 +#define CRYP_DMACR_DEFAULT 0x0 +#define CRYP_IMSC_DEFAULT 0x0 +#define CRYP_DIN_DEFAULT 0x0 +#define CRYP_DOUT_DEFAULT 0x0 +#define CRYP_KEY_DEFAULT 0x0 +#define CRYP_INIT_VECT_DEFAULT 0x0 + +/** + * CRYP Control register specific mask + */ +#define CRYP_CR_SECURE_MASK BIT(0) +#define CRYP_CR_PRLG_MASK BIT(1) +#define CRYP_CR_ALGODIR_MASK BIT(2) +#define CRYP_CR_ALGOMODE_MASK (BIT(5) | BIT(4) | BIT(3)) +#define CRYP_CR_DATATYPE_MASK (BIT(7) | BIT(6)) +#define CRYP_CR_KEYSIZE_MASK (BIT(9) | BIT(8)) +#define CRYP_CR_KEYRDEN_MASK BIT(10) +#define CRYP_CR_KSE_MASK BIT(11) +#define CRYP_CR_START_MASK BIT(12) +#define CRYP_CR_INIT_MASK BIT(13) +#define CRYP_CR_FFLUSH_MASK BIT(14) +#define CRYP_CR_CRYPEN_MASK BIT(15) +#define CRYP_CR_CONTEXT_SAVE_MASK (CRYP_CR_SECURE_MASK |\ + CRYP_CR_PRLG_MASK |\ + CRYP_CR_ALGODIR_MASK |\ + CRYP_CR_ALGOMODE_MASK |\ + CRYP_CR_DATATYPE_MASK |\ + CRYP_CR_KEYSIZE_MASK |\ + CRYP_CR_KEYRDEN_MASK |\ + CRYP_CR_DATATYPE_MASK) + + +#define CRYP_SR_INFIFO_READY_MASK (BIT(0) | BIT(1)) +#define CRYP_SR_IFEM_MASK BIT(0) +#define CRYP_SR_BUSY_MASK BIT(4) + +/** + * Bit position used while setting bits in register + */ +#define CRYP_CR_PRLG_POS 1 +#define CRYP_CR_ALGODIR_POS 2 +#define CRYP_CR_ALGOMODE_POS 3 +#define CRYP_CR_DATATYPE_POS 6 +#define CRYP_CR_KEYSIZE_POS 8 +#define CRYP_CR_KEYRDEN_POS 10 +#define CRYP_CR_KSE_POS 11 +#define CRYP_CR_START_POS 12 +#define CRYP_CR_INIT_POS 13 +#define CRYP_CR_CRYPEN_POS 15 + +#define CRYP_SR_BUSY_POS 4 + +/** + * CRYP PCRs------PC_NAND control register + * BIT_MASK + */ +#define CRYP_DMA_REQ_MASK (BIT(1) | BIT(0)) +#define CRYP_DMA_REQ_MASK_POS 0 + + +struct cryp_system_context { + /* CRYP Register structure */ + struct cryp_register *p_cryp_reg[MAX_DEVICE_SUPPORT]; +}; + +#endif diff --git a/drivers/crypto/ux500/hash/Makefile b/drivers/crypto/ux500/hash/Makefile new file mode 100644 index 000000000000..b2f90d9bac72 --- /dev/null +++ b/drivers/crypto/ux500/hash/Makefile @@ -0,0 +1,11 @@ +# +# Copyright (C) ST-Ericsson SA 2010 +# Author: Shujuan Chen ([email protected]) +# License terms: GNU General Public License (GPL) version 2 +# +ifdef CONFIG_CRYPTO_DEV_UX500_DEBUG +CFLAGS_hash_core.o := -DDEBUG -O0 +endif + +obj-$(CONFIG_CRYPTO_DEV_UX500_HASH) += ux500_hash.o +ux500_hash-objs := hash_core.o diff --git a/drivers/crypto/ux500/hash/hash_alg.h b/drivers/crypto/ux500/hash/hash_alg.h new file mode 100644 index 000000000000..cd9351cb24df --- /dev/null +++ b/drivers/crypto/ux500/hash/hash_alg.h @@ -0,0 +1,395 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Shujuan Chen ([email protected]) + * Author: Joakim Bech ([email protected]) + * Author: Berne Hebark ([email protected])) + * License terms: GNU General Public License (GPL) version 2 + */ +#ifndef _HASH_ALG_H +#define _HASH_ALG_H + +#include <linux/bitops.h> + +#define HASH_BLOCK_SIZE 64 +#define HASH_DMA_ALIGN_SIZE 4 +#define HASH_DMA_PERFORMANCE_MIN_SIZE 1024 +#define HASH_BYTES_PER_WORD 4 + +/* Maximum value of the length's high word */ +#define HASH_HIGH_WORD_MAX_VAL 0xFFFFFFFFUL + +/* Power on Reset values HASH registers */ +#define HASH_RESET_CR_VALUE 0x0 +#define HASH_RESET_STR_VALUE 0x0 + +/* Number of context swap registers */ +#define HASH_CSR_COUNT 52 + +#define HASH_RESET_CSRX_REG_VALUE 0x0 +#define HASH_RESET_CSFULL_REG_VALUE 0x0 +#define HASH_RESET_CSDATAIN_REG_VALUE 0x0 + +#define HASH_RESET_INDEX_VAL 0x0 +#define HASH_RESET_BIT_INDEX_VAL 0x0 +#define HASH_RESET_BUFFER_VAL 0x0 +#define HASH_RESET_LEN_HIGH_VAL 0x0 +#define HASH_RESET_LEN_LOW_VAL 0x0 + +/* Control register bitfields */ +#define HASH_CR_RESUME_MASK 0x11FCF + +#define HASH_CR_SWITCHON_POS 31 +#define HASH_CR_SWITCHON_MASK BIT(31) + +#define HASH_CR_EMPTYMSG_POS 20 +#define HASH_CR_EMPTYMSG_MASK BIT(20) + +#define HASH_CR_DINF_POS 12 +#define HASH_CR_DINF_MASK BIT(12) + +#define HASH_CR_NBW_POS 8 +#define HASH_CR_NBW_MASK 0x00000F00UL + +#define HASH_CR_LKEY_POS 16 +#define HASH_CR_LKEY_MASK BIT(16) + +#define HASH_CR_ALGO_POS 7 +#define HASH_CR_ALGO_MASK BIT(7) + +#define HASH_CR_MODE_POS 6 +#define HASH_CR_MODE_MASK BIT(6) + +#define HASH_CR_DATAFORM_POS 4 +#define HASH_CR_DATAFORM_MASK (BIT(4) | BIT(5)) + +#define HASH_CR_DMAE_POS 3 +#define HASH_CR_DMAE_MASK BIT(3) + +#define HASH_CR_INIT_POS 2 +#define HASH_CR_INIT_MASK BIT(2) + +#define HASH_CR_PRIVN_POS 1 +#define HASH_CR_PRIVN_MASK BIT(1) + +#define HASH_CR_SECN_POS 0 +#define HASH_CR_SECN_MASK BIT(0) + +/* Start register bitfields */ +#define HASH_STR_DCAL_POS 8 +#define HASH_STR_DCAL_MASK BIT(8) +#define HASH_STR_DEFAULT 0x0 + +#define HASH_STR_NBLW_POS 0 +#define HASH_STR_NBLW_MASK 0x0000001FUL + +#define HASH_NBLW_MAX_VAL 0x1F + +/* PrimeCell IDs */ +#define HASH_P_ID0 0xE0 +#define HASH_P_ID1 0x05 +#define HASH_P_ID2 0x38 +#define HASH_P_ID3 0x00 +#define HASH_CELL_ID0 0x0D +#define HASH_CELL_ID1 0xF0 +#define HASH_CELL_ID2 0x05 +#define HASH_CELL_ID3 0xB1 + +#define HASH_SET_BITS(reg_name, mask) \ + writel_relaxed((readl_relaxed(reg_name) | mask), reg_name) + +#define HASH_CLEAR_BITS(reg_name, mask) \ + writel_relaxed((readl_relaxed(reg_name) & ~mask), reg_name) + +#define HASH_PUT_BITS(reg, val, shift, mask) \ + writel_relaxed(((readl(reg) & ~(mask)) | \ + (((u32)val << shift) & (mask))), reg) + +#define HASH_SET_DIN(val, len) writesl(&device_data->base->din, (val), (len)) + +#define HASH_INITIALIZE \ + HASH_PUT_BITS( \ + &device_data->base->cr, \ + 0x01, HASH_CR_INIT_POS, \ + HASH_CR_INIT_MASK) + +#define HASH_SET_DATA_FORMAT(data_format) \ + HASH_PUT_BITS( \ + &device_data->base->cr, \ + (u32) (data_format), HASH_CR_DATAFORM_POS, \ + HASH_CR_DATAFORM_MASK) +#define HASH_SET_NBLW(val) \ + HASH_PUT_BITS( \ + &device_data->base->str, \ + (u32) (val), HASH_STR_NBLW_POS, \ + HASH_STR_NBLW_MASK) +#define HASH_SET_DCAL \ + HASH_PUT_BITS( \ + &device_data->base->str, \ + 0x01, HASH_STR_DCAL_POS, \ + HASH_STR_DCAL_MASK) + +/* Hardware access method */ +enum hash_mode { + HASH_MODE_CPU, + HASH_MODE_DMA +}; + +/** + * struct uint64 - Structure to handle 64 bits integers. + * @high_word: Most significant bits. + * @low_word: Least significant bits. + * + * Used to handle 64 bits integers. + */ +struct uint64 { + u32 high_word; + u32 low_word; +}; + +/** + * struct hash_register - Contains all registers in ux500 hash hardware. + * @cr: HASH control register (0x000). + * @din: HASH data input register (0x004). + * @str: HASH start register (0x008). + * @hx: HASH digest register 0..7 (0x00c-0x01C). + * @padding0: Reserved (0x02C). + * @itcr: Integration test control register (0x080). + * @itip: Integration test input register (0x084). + * @itop: Integration test output register (0x088). + * @padding1: Reserved (0x08C). + * @csfull: HASH context full register (0x0F8). + * @csdatain: HASH context swap data input register (0x0FC). + * @csrx: HASH context swap register 0..51 (0x100-0x1CC). + * @padding2: Reserved (0x1D0). + * @periphid0: HASH peripheral identification register 0 (0xFE0). + * @periphid1: HASH peripheral identification register 1 (0xFE4). + * @periphid2: HASH peripheral identification register 2 (0xFE8). + * @periphid3: HASH peripheral identification register 3 (0xFEC). + * @cellid0: HASH PCell identification register 0 (0xFF0). + * @cellid1: HASH PCell identification register 1 (0xFF4). + * @cellid2: HASH PCell identification register 2 (0xFF8). + * @cellid3: HASH PCell identification register 3 (0xFFC). + * + * The device communicates to the HASH via 32-bit-wide control registers + * accessible via the 32-bit width AMBA rev. 2.0 AHB Bus. Below is a structure + * with the registers used. + */ +struct hash_register { + u32 cr; + u32 din; + u32 str; + u32 hx[8]; + + u32 padding0[(0x080 - 0x02C) / sizeof(u32)]; + + u32 itcr; + u32 itip; + u32 itop; + + u32 padding1[(0x0F8 - 0x08C) / sizeof(u32)]; + + u32 csfull; + u32 csdatain; + u32 csrx[HASH_CSR_COUNT]; + + u32 padding2[(0xFE0 - 0x1D0) / sizeof(u32)]; + + u32 periphid0; + u32 periphid1; + u32 periphid2; + u32 periphid3; + + u32 cellid0; + u32 cellid1; + u32 cellid2; + u32 cellid3; +}; + +/** + * struct hash_state - Hash context state. + * @temp_cr: Temporary HASH Control Register. + * @str_reg: HASH Start Register. + * @din_reg: HASH Data Input Register. + * @csr[52]: HASH Context Swap Registers 0-39. + * @csfull: HASH Context Swap Registers 40 ie Status flags. + * @csdatain: HASH Context Swap Registers 41 ie Input data. + * @buffer: Working buffer for messages going to the hardware. + * @length: Length of the part of message hashed so far (floor(N/64) * 64). + * @index: Valid number of bytes in buffer (N % 64). + * @bit_index: Valid number of bits in buffer (N % 8). + * + * This structure is used between context switches, i.e. when ongoing jobs are + * interupted with new jobs. When this happens we need to store intermediate + * results in software. + * + * WARNING: "index" is the member of the structure, to be sure that "buffer" + * is aligned on a 4-bytes boundary. This is highly implementation dependent + * and MUST be checked whenever this code is ported on new platforms. + */ +struct hash_state { + u32 temp_cr; + u32 str_reg; + u32 din_reg; + u32 csr[52]; + u32 csfull; + u32 csdatain; + u32 buffer[HASH_BLOCK_SIZE / sizeof(u32)]; + struct uint64 length; + u8 index; + u8 bit_index; +}; + +/** + * enum hash_device_id - HASH device ID. + * @HASH_DEVICE_ID_0: Hash hardware with ID 0 + * @HASH_DEVICE_ID_1: Hash hardware with ID 1 + */ +enum hash_device_id { + HASH_DEVICE_ID_0 = 0, + HASH_DEVICE_ID_1 = 1 +}; + +/** + * enum hash_data_format - HASH data format. + * @HASH_DATA_32_BITS: 32 bits data format + * @HASH_DATA_16_BITS: 16 bits data format + * @HASH_DATA_8_BITS: 8 bits data format. + * @HASH_DATA_1_BITS: 1 bit data format. + */ +enum hash_data_format { + HASH_DATA_32_BITS = 0x0, + HASH_DATA_16_BITS = 0x1, + HASH_DATA_8_BITS = 0x2, + HASH_DATA_1_BIT = 0x3 +}; + +/** + * enum hash_algo - Enumeration for selecting between SHA1 or SHA2 algorithm. + * @HASH_ALGO_SHA1: Indicates that SHA1 is used. + * @HASH_ALGO_SHA2: Indicates that SHA2 (SHA256) is used. + */ +enum hash_algo { + HASH_ALGO_SHA1 = 0x0, + HASH_ALGO_SHA256 = 0x1 +}; + +/** + * enum hash_op - Enumeration for selecting between HASH or HMAC mode. + * @HASH_OPER_MODE_HASH: Indicates usage of normal HASH mode. + * @HASH_OPER_MODE_HMAC: Indicates usage of HMAC. + */ +enum hash_op { + HASH_OPER_MODE_HASH = 0x0, + HASH_OPER_MODE_HMAC = 0x1 +}; + +/** + * struct hash_config - Configuration data for the hardware. + * @data_format: Format of data entered into the hash data in register. + * @algorithm: Algorithm selection bit. + * @oper_mode: Operating mode selection bit. + */ +struct hash_config { + int data_format; + int algorithm; + int oper_mode; +}; + +/** + * struct hash_dma - Structure used for dma. + * @mask: DMA capabilities bitmap mask. + * @complete: Used to maintain state for a "completion". + * @chan_mem2hash: DMA channel. + * @cfg_mem2hash: DMA channel configuration. + * @sg_len: Scatterlist length. + * @sg: Scatterlist. + * @nents: Number of sg entries. + */ +struct hash_dma { + dma_cap_mask_t mask; + struct completion complete; + struct dma_chan *chan_mem2hash; + void *cfg_mem2hash; + int sg_len; + struct scatterlist *sg; + int nents; +}; + +/** + * struct hash_ctx - The context used for hash calculations. + * @key: The key used in the operation. + * @keylen: The length of the key. + * @state: The state of the current calculations. + * @config: The current configuration. + * @digestsize: The size of current digest. + * @device: Pointer to the device structure. + */ +struct hash_ctx { + u8 *key; + u32 keylen; + struct hash_config config; + int digestsize; + struct hash_device_data *device; +}; + +/** + * struct hash_ctx - The request context used for hash calculations. + * @state: The state of the current calculations. + * @dma_mode: Used in special cases (workaround), e.g. need to change to + * cpu mode, if not supported/working in dma mode. + * @updated: Indicates if hardware is initialized for new operations. + */ +struct hash_req_ctx { + struct hash_state state; + bool dma_mode; + u8 updated; +}; + +/** + * struct hash_device_data - structure for a hash device. + * @base: Pointer to the hardware base address. + * @list_node: For inclusion in klist. + * @dev: Pointer to the device dev structure. + * @ctx_lock: Spinlock for current_ctx. + * @current_ctx: Pointer to the currently allocated context. + * @power_state: TRUE = power state on, FALSE = power state off. + * @power_state_lock: Spinlock for power_state. + * @regulator: Pointer to the device's power control. + * @clk: Pointer to the device's clock control. + * @restore_dev_state: TRUE = saved state, FALSE = no saved state. + * @dma: Structure used for dma. + */ +struct hash_device_data { + struct hash_register __iomem *base; + struct klist_node list_node; + struct device *dev; + struct spinlock ctx_lock; + struct hash_ctx *current_ctx; + bool power_state; + struct spinlock power_state_lock; + struct regulator *regulator; + struct clk *clk; + bool restore_dev_state; + struct hash_state state; /* Used for saving and resuming state */ + struct hash_dma dma; +}; + +int hash_check_hw(struct hash_device_data *device_data); + +int hash_setconfiguration(struct hash_device_data *device_data, + struct hash_config *config); + +void hash_begin(struct hash_device_data *device_data, struct hash_ctx *ctx); + +void hash_get_digest(struct hash_device_data *device_data, + u8 *digest, int algorithm); + +int hash_hw_update(struct ahash_request *req); + +int hash_save_state(struct hash_device_data *device_data, + struct hash_state *state); + +int hash_resume_state(struct hash_device_data *device_data, + const struct hash_state *state); + +#endif diff --git a/drivers/crypto/ux500/hash/hash_core.c b/drivers/crypto/ux500/hash/hash_core.c new file mode 100644 index 000000000000..6dbb9ec709a3 --- /dev/null +++ b/drivers/crypto/ux500/hash/hash_core.c @@ -0,0 +1,2009 @@ +/* + * Cryptographic API. + * Support for Nomadik hardware crypto engine. + + * Copyright (C) ST-Ericsson SA 2010 + * Author: Shujuan Chen <[email protected]> for ST-Ericsson + * Author: Joakim Bech <[email protected]> for ST-Ericsson + * Author: Berne Hebark <[email protected]> for ST-Ericsson. + * Author: Niklas Hernaeus <[email protected]> for ST-Ericsson. + * Author: Andreas Westin <[email protected]> for ST-Ericsson. + * License terms: GNU General Public License (GPL) version 2 + */ + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/klist.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/crypto.h> + +#include <linux/regulator/consumer.h> +#include <linux/dmaengine.h> +#include <linux/bitops.h> + +#include <crypto/internal/hash.h> +#include <crypto/sha.h> +#include <crypto/scatterwalk.h> +#include <crypto/algapi.h> + +#include <mach/crypto-ux500.h> +#include <mach/hardware.h> + +#include "hash_alg.h" + +#define DEV_DBG_NAME "hashX hashX:" + +static int hash_mode; +module_param(hash_mode, int, 0); +MODULE_PARM_DESC(hash_mode, "CPU or DMA mode. CPU = 0 (default), DMA = 1"); + +/** + * Pre-calculated empty message digests. + */ +static u8 zero_message_hash_sha1[SHA1_DIGEST_SIZE] = { + 0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, + 0x32, 0x55, 0xbf, 0xef, 0x95, 0x60, 0x18, 0x90, + 0xaf, 0xd8, 0x07, 0x09 +}; + +static u8 zero_message_hash_sha256[SHA256_DIGEST_SIZE] = { + 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, + 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, + 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, + 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55 +}; + +/* HMAC-SHA1, no key */ +static u8 zero_message_hmac_sha1[SHA1_DIGEST_SIZE] = { + 0xfb, 0xdb, 0x1d, 0x1b, 0x18, 0xaa, 0x6c, 0x08, + 0x32, 0x4b, 0x7d, 0x64, 0xb7, 0x1f, 0xb7, 0x63, + 0x70, 0x69, 0x0e, 0x1d +}; + +/* HMAC-SHA256, no key */ +static u8 zero_message_hmac_sha256[SHA256_DIGEST_SIZE] = { + 0xb6, 0x13, 0x67, 0x9a, 0x08, 0x14, 0xd9, 0xec, + 0x77, 0x2f, 0x95, 0xd7, 0x78, 0xc3, 0x5f, 0xc5, + 0xff, 0x16, 0x97, 0xc4, 0x93, 0x71, 0x56, 0x53, + 0xc6, 0xc7, 0x12, 0x14, 0x42, 0x92, 0xc5, 0xad +}; + +/** + * struct hash_driver_data - data specific to the driver. + * + * @device_list: A list of registered devices to choose from. + * @device_allocation: A semaphore initialized with number of devices. + */ +struct hash_driver_data { + struct klist device_list; + struct semaphore device_allocation; +}; + +static struct hash_driver_data driver_data; + +/* Declaration of functions */ +/** + * hash_messagepad - Pads a message and write the nblw bits. + * @device_data: Structure for the hash device. + * @message: Last word of a message + * @index_bytes: The number of bytes in the last message + * + * This function manages the final part of the digest calculation, when less + * than 512 bits (64 bytes) remain in message. This means index_bytes < 64. + * + */ +static void hash_messagepad(struct hash_device_data *device_data, + const u32 *message, u8 index_bytes); + +/** + * release_hash_device - Releases a previously allocated hash device. + * @device_data: Structure for the hash device. + * + */ +static void release_hash_device(struct hash_device_data *device_data) +{ + spin_lock(&device_data->ctx_lock); + device_data->current_ctx->device = NULL; + device_data->current_ctx = NULL; + spin_unlock(&device_data->ctx_lock); + + /* + * The down_interruptible part for this semaphore is called in + * cryp_get_device_data. + */ + up(&driver_data.device_allocation); +} + +static void hash_dma_setup_channel(struct hash_device_data *device_data, + struct device *dev) +{ + struct hash_platform_data *platform_data = dev->platform_data; + dma_cap_zero(device_data->dma.mask); + dma_cap_set(DMA_SLAVE, device_data->dma.mask); + + device_data->dma.cfg_mem2hash = platform_data->mem_to_engine; + device_data->dma.chan_mem2hash = + dma_request_channel(device_data->dma.mask, + platform_data->dma_filter, + device_data->dma.cfg_mem2hash); + + init_completion(&device_data->dma.complete); +} + +static void hash_dma_callback(void *data) +{ + struct hash_ctx *ctx = (struct hash_ctx *) data; + + complete(&ctx->device->dma.complete); +} + +static int hash_set_dma_transfer(struct hash_ctx *ctx, struct scatterlist *sg, + int len, enum dma_data_direction direction) +{ + struct dma_async_tx_descriptor *desc = NULL; + struct dma_chan *channel = NULL; + dma_cookie_t cookie; + + if (direction != DMA_TO_DEVICE) { + dev_err(ctx->device->dev, "[%s] Invalid DMA direction", + __func__); + return -EFAULT; + } + + sg->length = ALIGN(sg->length, HASH_DMA_ALIGN_SIZE); + + channel = ctx->device->dma.chan_mem2hash; + ctx->device->dma.sg = sg; + ctx->device->dma.sg_len = dma_map_sg(channel->device->dev, + ctx->device->dma.sg, ctx->device->dma.nents, + direction); + + if (!ctx->device->dma.sg_len) { + dev_err(ctx->device->dev, + "[%s]: Could not map the sg list (TO_DEVICE)", + __func__); + return -EFAULT; + } + + dev_dbg(ctx->device->dev, "[%s]: Setting up DMA for buffer " + "(TO_DEVICE)", __func__); + desc = channel->device->device_prep_slave_sg(channel, + ctx->device->dma.sg, ctx->device->dma.sg_len, + direction, DMA_CTRL_ACK | DMA_PREP_INTERRUPT, NULL); + if (!desc) { + dev_err(ctx->device->dev, + "[%s]: device_prep_slave_sg() failed!", __func__); + return -EFAULT; + } + + desc->callback = hash_dma_callback; + desc->callback_param = ctx; + + cookie = desc->tx_submit(desc); + dma_async_issue_pending(channel); + + return 0; +} + +static void hash_dma_done(struct hash_ctx *ctx) +{ + struct dma_chan *chan; + + chan = ctx->device->dma.chan_mem2hash; + chan->device->device_control(chan, DMA_TERMINATE_ALL, 0); + dma_unmap_sg(chan->device->dev, ctx->device->dma.sg, + ctx->device->dma.sg_len, DMA_TO_DEVICE); + +} + +static int hash_dma_write(struct hash_ctx *ctx, + struct scatterlist *sg, int len) +{ + int error = hash_set_dma_transfer(ctx, sg, len, DMA_TO_DEVICE); + if (error) { + dev_dbg(ctx->device->dev, "[%s]: hash_set_dma_transfer() " + "failed", __func__); + return error; + } + + return len; +} + +/** + * get_empty_message_digest - Returns a pre-calculated digest for + * the empty message. + * @device_data: Structure for the hash device. + * @zero_hash: Buffer to return the empty message digest. + * @zero_hash_size: Hash size of the empty message digest. + * @zero_digest: True if zero_digest returned. + */ +static int get_empty_message_digest( + struct hash_device_data *device_data, + u8 *zero_hash, u32 *zero_hash_size, bool *zero_digest) +{ + int ret = 0; + struct hash_ctx *ctx = device_data->current_ctx; + *zero_digest = false; + + /** + * Caller responsible for ctx != NULL. + */ + + if (HASH_OPER_MODE_HASH == ctx->config.oper_mode) { + if (HASH_ALGO_SHA1 == ctx->config.algorithm) { + memcpy(zero_hash, &zero_message_hash_sha1[0], + SHA1_DIGEST_SIZE); + *zero_hash_size = SHA1_DIGEST_SIZE; + *zero_digest = true; + } else if (HASH_ALGO_SHA256 == + ctx->config.algorithm) { + memcpy(zero_hash, &zero_message_hash_sha256[0], + SHA256_DIGEST_SIZE); + *zero_hash_size = SHA256_DIGEST_SIZE; + *zero_digest = true; + } else { + dev_err(device_data->dev, "[%s] " + "Incorrect algorithm!" + , __func__); + ret = -EINVAL; + goto out; + } + } else if (HASH_OPER_MODE_HMAC == ctx->config.oper_mode) { + if (!ctx->keylen) { + if (HASH_ALGO_SHA1 == ctx->config.algorithm) { + memcpy(zero_hash, &zero_message_hmac_sha1[0], + SHA1_DIGEST_SIZE); + *zero_hash_size = SHA1_DIGEST_SIZE; + *zero_digest = true; + } else if (HASH_ALGO_SHA256 == ctx->config.algorithm) { + memcpy(zero_hash, &zero_message_hmac_sha256[0], + SHA256_DIGEST_SIZE); + *zero_hash_size = SHA256_DIGEST_SIZE; + *zero_digest = true; + } else { + dev_err(device_data->dev, "[%s] " + "Incorrect algorithm!" + , __func__); + ret = -EINVAL; + goto out; + } + } else { + dev_dbg(device_data->dev, "[%s] Continue hash " + "calculation, since hmac key avalable", + __func__); + } + } +out: + + return ret; +} + +/** + * hash_disable_power - Request to disable power and clock. + * @device_data: Structure for the hash device. + * @save_device_state: If true, saves the current hw state. + * + * This function request for disabling power (regulator) and clock, + * and could also save current hw state. + */ +static int hash_disable_power( + struct hash_device_data *device_data, + bool save_device_state) +{ + int ret = 0; + struct device *dev = device_data->dev; + + spin_lock(&device_data->power_state_lock); + if (!device_data->power_state) + goto out; + + if (save_device_state) { + hash_save_state(device_data, + &device_data->state); + device_data->restore_dev_state = true; + } + + clk_disable(device_data->clk); + ret = regulator_disable(device_data->regulator); + if (ret) + dev_err(dev, "[%s] regulator_disable() failed!", __func__); + + device_data->power_state = false; + +out: + spin_unlock(&device_data->power_state_lock); + + return ret; +} + +/** + * hash_enable_power - Request to enable power and clock. + * @device_data: Structure for the hash device. + * @restore_device_state: If true, restores a previous saved hw state. + * + * This function request for enabling power (regulator) and clock, + * and could also restore a previously saved hw state. + */ +static int hash_enable_power( + struct hash_device_data *device_data, + bool restore_device_state) +{ + int ret = 0; + struct device *dev = device_data->dev; + + spin_lock(&device_data->power_state_lock); + if (!device_data->power_state) { + ret = regulator_enable(device_data->regulator); + if (ret) { + dev_err(dev, "[%s]: regulator_enable() failed!", + __func__); + goto out; + } + ret = clk_enable(device_data->clk); + if (ret) { + dev_err(dev, "[%s]: clk_enable() failed!", + __func__); + ret = regulator_disable( + device_data->regulator); + goto out; + } + device_data->power_state = true; + } + + if (device_data->restore_dev_state) { + if (restore_device_state) { + device_data->restore_dev_state = false; + hash_resume_state(device_data, + &device_data->state); + } + } +out: + spin_unlock(&device_data->power_state_lock); + + return ret; +} + +/** + * hash_get_device_data - Checks for an available hash device and return it. + * @hash_ctx: Structure for the hash context. + * @device_data: Structure for the hash device. + * + * This function check for an available hash device and return it to + * the caller. + * Note! Caller need to release the device, calling up(). + */ +static int hash_get_device_data(struct hash_ctx *ctx, + struct hash_device_data **device_data) +{ + int ret; + struct klist_iter device_iterator; + struct klist_node *device_node; + struct hash_device_data *local_device_data = NULL; + + /* Wait until a device is available */ + ret = down_interruptible(&driver_data.device_allocation); + if (ret) + return ret; /* Interrupted */ + + /* Select a device */ + klist_iter_init(&driver_data.device_list, &device_iterator); + device_node = klist_next(&device_iterator); + while (device_node) { + local_device_data = container_of(device_node, + struct hash_device_data, list_node); + spin_lock(&local_device_data->ctx_lock); + /* current_ctx allocates a device, NULL = unallocated */ + if (local_device_data->current_ctx) { + device_node = klist_next(&device_iterator); + } else { + local_device_data->current_ctx = ctx; + ctx->device = local_device_data; + spin_unlock(&local_device_data->ctx_lock); + break; + } + spin_unlock(&local_device_data->ctx_lock); + } + klist_iter_exit(&device_iterator); + + if (!device_node) { + /** + * No free device found. + * Since we allocated a device with down_interruptible, this + * should not be able to happen. + * Number of available devices, which are contained in + * device_allocation, is therefore decremented by not doing + * an up(device_allocation). + */ + return -EBUSY; + } + + *device_data = local_device_data; + + return 0; +} + +/** + * hash_hw_write_key - Writes the key to the hardware registries. + * + * @device_data: Structure for the hash device. + * @key: Key to be written. + * @keylen: The lengt of the key. + * + * Note! This function DOES NOT write to the NBLW registry, even though + * specified in the the hw design spec. Either due to incorrect info in the + * spec or due to a bug in the hw. + */ +static void hash_hw_write_key(struct hash_device_data *device_data, + const u8 *key, unsigned int keylen) +{ + u32 word = 0; + int nwords = 1; + + HASH_CLEAR_BITS(&device_data->base->str, HASH_STR_NBLW_MASK); + + while (keylen >= 4) { + u32 *key_word = (u32 *)key; + + HASH_SET_DIN(key_word, nwords); + keylen -= 4; + key += 4; + } + + /* Take care of the remaining bytes in the last word */ + if (keylen) { + word = 0; + while (keylen) { + word |= (key[keylen - 1] << (8 * (keylen - 1))); + keylen--; + } + + HASH_SET_DIN(&word, nwords); + } + + while (device_data->base->str & HASH_STR_DCAL_MASK) + cpu_relax(); + + HASH_SET_DCAL; + + while (device_data->base->str & HASH_STR_DCAL_MASK) + cpu_relax(); +} + +/** + * init_hash_hw - Initialise the hash hardware for a new calculation. + * @device_data: Structure for the hash device. + * @ctx: The hash context. + * + * This function will enable the bits needed to clear and start a new + * calculation. + */ +static int init_hash_hw(struct hash_device_data *device_data, + struct hash_ctx *ctx) +{ + int ret = 0; + + ret = hash_setconfiguration(device_data, &ctx->config); + if (ret) { + dev_err(device_data->dev, "[%s] hash_setconfiguration() " + "failed!", __func__); + return ret; + } + + hash_begin(device_data, ctx); + + if (ctx->config.oper_mode == HASH_OPER_MODE_HMAC) + hash_hw_write_key(device_data, ctx->key, ctx->keylen); + + return ret; +} + +/** + * hash_get_nents - Return number of entries (nents) in scatterlist (sg). + * + * @sg: Scatterlist. + * @size: Size in bytes. + * @aligned: True if sg data aligned to work in DMA mode. + * + */ +static int hash_get_nents(struct scatterlist *sg, int size, bool *aligned) +{ + int nents = 0; + bool aligned_data = true; + + while (size > 0 && sg) { + nents++; + size -= sg->length; + + /* hash_set_dma_transfer will align last nent */ + if ((aligned && !IS_ALIGNED(sg->offset, HASH_DMA_ALIGN_SIZE)) + || (!IS_ALIGNED(sg->length, HASH_DMA_ALIGN_SIZE) && + size > 0)) + aligned_data = false; + + sg = sg_next(sg); + } + + if (aligned) + *aligned = aligned_data; + + if (size != 0) + return -EFAULT; + + return nents; +} + +/** + * hash_dma_valid_data - checks for dma valid sg data. + * @sg: Scatterlist. + * @datasize: Datasize in bytes. + * + * NOTE! This function checks for dma valid sg data, since dma + * only accept datasizes of even wordsize. + */ +static bool hash_dma_valid_data(struct scatterlist *sg, int datasize) +{ + bool aligned; + + /* Need to include at least one nent, else error */ + if (hash_get_nents(sg, datasize, &aligned) < 1) + return false; + + return aligned; +} + +/** + * hash_init - Common hash init function for SHA1/SHA2 (SHA256). + * @req: The hash request for the job. + * + * Initialize structures. + */ +static int hash_init(struct ahash_request *req) +{ + struct crypto_ahash *tfm = crypto_ahash_reqtfm(req); + struct hash_ctx *ctx = crypto_ahash_ctx(tfm); + struct hash_req_ctx *req_ctx = ahash_request_ctx(req); + + if (!ctx->key) + ctx->keylen = 0; + + memset(&req_ctx->state, 0, sizeof(struct hash_state)); + req_ctx->updated = 0; + if (hash_mode == HASH_MODE_DMA) { + if (req->nbytes < HASH_DMA_ALIGN_SIZE) { + req_ctx->dma_mode = false; /* Don't use DMA */ + + pr_debug(DEV_DBG_NAME " [%s] DMA mode, but direct " + "to CPU mode for data size < %d", + __func__, HASH_DMA_ALIGN_SIZE); + } else { + if (req->nbytes >= HASH_DMA_PERFORMANCE_MIN_SIZE && + hash_dma_valid_data(req->src, + req->nbytes)) { + req_ctx->dma_mode = true; + } else { + req_ctx->dma_mode = false; + pr_debug(DEV_DBG_NAME " [%s] DMA mode, but use" + " CPU mode for datalength < %d" + " or non-aligned data, except " + "in last nent", __func__, + HASH_DMA_PERFORMANCE_MIN_SIZE); + } + } + } + return 0; +} + +/** + * hash_processblock - This function processes a single block of 512 bits (64 + * bytes), word aligned, starting at message. + * @device_data: Structure for the hash device. + * @message: Block (512 bits) of message to be written to + * the HASH hardware. + * + */ +static void hash_processblock( + struct hash_device_data *device_data, + const u32 *message, int length) +{ + int len = length / HASH_BYTES_PER_WORD; + /* + * NBLW bits. Reset the number of bits in last word (NBLW). + */ + HASH_CLEAR_BITS(&device_data->base->str, HASH_STR_NBLW_MASK); + + /* + * Write message data to the HASH_DIN register. + */ + HASH_SET_DIN(message, len); +} + +/** + * hash_messagepad - Pads a message and write the nblw bits. + * @device_data: Structure for the hash device. + * @message: Last word of a message. + * @index_bytes: The number of bytes in the last message. + * + * This function manages the final part of the digest calculation, when less + * than 512 bits (64 bytes) remain in message. This means index_bytes < 64. + * + */ +static void hash_messagepad(struct hash_device_data *device_data, + const u32 *message, u8 index_bytes) +{ + int nwords = 1; + + /* + * Clear hash str register, only clear NBLW + * since DCAL will be reset by hardware. + */ + HASH_CLEAR_BITS(&device_data->base->str, HASH_STR_NBLW_MASK); + + /* Main loop */ + while (index_bytes >= 4) { + HASH_SET_DIN(message, nwords); + index_bytes -= 4; + message++; + } + + if (index_bytes) + HASH_SET_DIN(message, nwords); + + while (device_data->base->str & HASH_STR_DCAL_MASK) + cpu_relax(); + + /* num_of_bytes == 0 => NBLW <- 0 (32 bits valid in DATAIN) */ + HASH_SET_NBLW(index_bytes * 8); + dev_dbg(device_data->dev, "[%s] DIN=0x%08x NBLW=%d", __func__, + readl_relaxed(&device_data->base->din), + (int)(readl_relaxed(&device_data->base->str) & + HASH_STR_NBLW_MASK)); + HASH_SET_DCAL; + dev_dbg(device_data->dev, "[%s] after dcal -> DIN=0x%08x NBLW=%d", + __func__, readl_relaxed(&device_data->base->din), + (int)(readl_relaxed(&device_data->base->str) & + HASH_STR_NBLW_MASK)); + + while (device_data->base->str & HASH_STR_DCAL_MASK) + cpu_relax(); +} + +/** + * hash_incrementlength - Increments the length of the current message. + * @ctx: Hash context + * @incr: Length of message processed already + * + * Overflow cannot occur, because conditions for overflow are checked in + * hash_hw_update. + */ +static void hash_incrementlength(struct hash_req_ctx *ctx, u32 incr) +{ + ctx->state.length.low_word += incr; + + /* Check for wrap-around */ + if (ctx->state.length.low_word < incr) + ctx->state.length.high_word++; +} + +/** + * hash_setconfiguration - Sets the required configuration for the hash + * hardware. + * @device_data: Structure for the hash device. + * @config: Pointer to a configuration structure. + */ +int hash_setconfiguration(struct hash_device_data *device_data, + struct hash_config *config) +{ + int ret = 0; + + if (config->algorithm != HASH_ALGO_SHA1 && + config->algorithm != HASH_ALGO_SHA256) + return -EPERM; + + /* + * DATAFORM bits. Set the DATAFORM bits to 0b11, which means the data + * to be written to HASH_DIN is considered as 32 bits. + */ + HASH_SET_DATA_FORMAT(config->data_format); + + /* + * ALGO bit. Set to 0b1 for SHA-1 and 0b0 for SHA-256 + */ + switch (config->algorithm) { + case HASH_ALGO_SHA1: + HASH_SET_BITS(&device_data->base->cr, HASH_CR_ALGO_MASK); + break; + + case HASH_ALGO_SHA256: + HASH_CLEAR_BITS(&device_data->base->cr, HASH_CR_ALGO_MASK); + break; + + default: + dev_err(device_data->dev, "[%s] Incorrect algorithm.", + __func__); + return -EPERM; + } + + /* + * MODE bit. This bit selects between HASH or HMAC mode for the + * selected algorithm. 0b0 = HASH and 0b1 = HMAC. + */ + if (HASH_OPER_MODE_HASH == config->oper_mode) + HASH_CLEAR_BITS(&device_data->base->cr, + HASH_CR_MODE_MASK); + else if (HASH_OPER_MODE_HMAC == config->oper_mode) { + HASH_SET_BITS(&device_data->base->cr, + HASH_CR_MODE_MASK); + if (device_data->current_ctx->keylen > HASH_BLOCK_SIZE) { + /* Truncate key to blocksize */ + dev_dbg(device_data->dev, "[%s] LKEY set", __func__); + HASH_SET_BITS(&device_data->base->cr, + HASH_CR_LKEY_MASK); + } else { + dev_dbg(device_data->dev, "[%s] LKEY cleared", + __func__); + HASH_CLEAR_BITS(&device_data->base->cr, + HASH_CR_LKEY_MASK); + } + } else { /* Wrong hash mode */ + ret = -EPERM; + dev_err(device_data->dev, "[%s] HASH_INVALID_PARAMETER!", + __func__); + } + return ret; +} + +/** + * hash_begin - This routine resets some globals and initializes the hash + * hardware. + * @device_data: Structure for the hash device. + * @ctx: Hash context. + */ +void hash_begin(struct hash_device_data *device_data, struct hash_ctx *ctx) +{ + /* HW and SW initializations */ + /* Note: there is no need to initialize buffer and digest members */ + + while (device_data->base->str & HASH_STR_DCAL_MASK) + cpu_relax(); + + /* + * INIT bit. Set this bit to 0b1 to reset the HASH processor core and + * prepare the initialize the HASH accelerator to compute the message + * digest of a new message. + */ + HASH_INITIALIZE; + + /* + * NBLW bits. Reset the number of bits in last word (NBLW). + */ + HASH_CLEAR_BITS(&device_data->base->str, HASH_STR_NBLW_MASK); +} + +int hash_process_data( + struct hash_device_data *device_data, + struct hash_ctx *ctx, struct hash_req_ctx *req_ctx, + int msg_length, u8 *data_buffer, u8 *buffer, u8 *index) +{ + int ret = 0; + u32 count; + + do { + if ((*index + msg_length) < HASH_BLOCK_SIZE) { + for (count = 0; count < msg_length; count++) { + buffer[*index + count] = + *(data_buffer + count); + } + *index += msg_length; + msg_length = 0; + } else { + if (req_ctx->updated) { + + ret = hash_resume_state(device_data, + &device_data->state); + memmove(req_ctx->state.buffer, + device_data->state.buffer, + HASH_BLOCK_SIZE / sizeof(u32)); + if (ret) { + dev_err(device_data->dev, "[%s] " + "hash_resume_state()" + " failed!", __func__); + goto out; + } + } else { + ret = init_hash_hw(device_data, ctx); + if (ret) { + dev_err(device_data->dev, "[%s] " + "init_hash_hw()" + " failed!", __func__); + goto out; + } + req_ctx->updated = 1; + } + /* + * If 'data_buffer' is four byte aligned and + * local buffer does not have any data, we can + * write data directly from 'data_buffer' to + * HW peripheral, otherwise we first copy data + * to a local buffer + */ + if ((0 == (((u32)data_buffer) % 4)) + && (0 == *index)) + hash_processblock(device_data, + (const u32 *) + data_buffer, HASH_BLOCK_SIZE); + else { + for (count = 0; count < + (u32)(HASH_BLOCK_SIZE - + *index); + count++) { + buffer[*index + count] = + *(data_buffer + count); + } + hash_processblock(device_data, + (const u32 *)buffer, + HASH_BLOCK_SIZE); + } + hash_incrementlength(req_ctx, HASH_BLOCK_SIZE); + data_buffer += (HASH_BLOCK_SIZE - *index); + + msg_length -= (HASH_BLOCK_SIZE - *index); + *index = 0; + + ret = hash_save_state(device_data, + &device_data->state); + + memmove(device_data->state.buffer, + req_ctx->state.buffer, + HASH_BLOCK_SIZE / sizeof(u32)); + if (ret) { + dev_err(device_data->dev, "[%s] " + "hash_save_state()" + " failed!", __func__); + goto out; + } + } + } while (msg_length != 0); +out: + + return ret; +} + +/** + * hash_dma_final - The hash dma final function for SHA1/SHA256. + * @req: The hash request for the job. + */ +static int hash_dma_final(struct ahash_request *req) +{ + int ret = 0; + struct crypto_ahash *tfm = crypto_ahash_reqtfm(req); + struct hash_ctx *ctx = crypto_ahash_ctx(tfm); + struct hash_req_ctx *req_ctx = ahash_request_ctx(req); + struct hash_device_data *device_data; + u8 digest[SHA256_DIGEST_SIZE]; + int bytes_written = 0; + + ret = hash_get_device_data(ctx, &device_data); + if (ret) + return ret; + + dev_dbg(device_data->dev, "[%s] (ctx=0x%x)!", __func__, (u32) ctx); + + if (req_ctx->updated) { + ret = hash_resume_state(device_data, &device_data->state); + + if (ret) { + dev_err(device_data->dev, "[%s] hash_resume_state() " + "failed!", __func__); + goto out; + } + + } + + if (!req_ctx->updated) { + ret = hash_setconfiguration(device_data, &ctx->config); + if (ret) { + dev_err(device_data->dev, "[%s] " + "hash_setconfiguration() failed!", + __func__); + goto out; + } + + /* Enable DMA input */ + if (hash_mode != HASH_MODE_DMA || !req_ctx->dma_mode) { + HASH_CLEAR_BITS(&device_data->base->cr, + HASH_CR_DMAE_MASK); + } else { + HASH_SET_BITS(&device_data->base->cr, + HASH_CR_DMAE_MASK); + HASH_SET_BITS(&device_data->base->cr, + HASH_CR_PRIVN_MASK); + } + + HASH_INITIALIZE; + + if (ctx->config.oper_mode == HASH_OPER_MODE_HMAC) + hash_hw_write_key(device_data, ctx->key, ctx->keylen); + + /* Number of bits in last word = (nbytes * 8) % 32 */ + HASH_SET_NBLW((req->nbytes * 8) % 32); + req_ctx->updated = 1; + } + + /* Store the nents in the dma struct. */ + ctx->device->dma.nents = hash_get_nents(req->src, req->nbytes, NULL); + if (!ctx->device->dma.nents) { + dev_err(device_data->dev, "[%s] " + "ctx->device->dma.nents = 0", __func__); + goto out; + } + + bytes_written = hash_dma_write(ctx, req->src, req->nbytes); + if (bytes_written != req->nbytes) { + dev_err(device_data->dev, "[%s] " + "hash_dma_write() failed!", __func__); + goto out; + } + + wait_for_completion(&ctx->device->dma.complete); + hash_dma_done(ctx); + + while (device_data->base->str & HASH_STR_DCAL_MASK) + cpu_relax(); + + if (ctx->config.oper_mode == HASH_OPER_MODE_HMAC && ctx->key) { + unsigned int keylen = ctx->keylen; + u8 *key = ctx->key; + + dev_dbg(device_data->dev, "[%s] keylen: %d", __func__, + ctx->keylen); + hash_hw_write_key(device_data, key, keylen); + } + + hash_get_digest(device_data, digest, ctx->config.algorithm); + memcpy(req->result, digest, ctx->digestsize); + +out: + release_hash_device(device_data); + + /** + * Allocated in setkey, and only used in HMAC. + */ + kfree(ctx->key); + + return ret; +} + +/** + * hash_hw_final - The final hash calculation function + * @req: The hash request for the job. + */ +int hash_hw_final(struct ahash_request *req) +{ + int ret = 0; + struct crypto_ahash *tfm = crypto_ahash_reqtfm(req); + struct hash_ctx *ctx = crypto_ahash_ctx(tfm); + struct hash_req_ctx *req_ctx = ahash_request_ctx(req); + struct hash_device_data *device_data; + u8 digest[SHA256_DIGEST_SIZE]; + + ret = hash_get_device_data(ctx, &device_data); + if (ret) + return ret; + + dev_dbg(device_data->dev, "[%s] (ctx=0x%x)!", __func__, (u32) ctx); + + if (req_ctx->updated) { + ret = hash_resume_state(device_data, &device_data->state); + + if (ret) { + dev_err(device_data->dev, "[%s] hash_resume_state() " + "failed!", __func__); + goto out; + } + } else if (req->nbytes == 0 && ctx->keylen == 0) { + u8 zero_hash[SHA256_DIGEST_SIZE]; + u32 zero_hash_size = 0; + bool zero_digest = false; + /** + * Use a pre-calculated empty message digest + * (workaround since hw return zeroes, hw bug!?) + */ + ret = get_empty_message_digest(device_data, &zero_hash[0], + &zero_hash_size, &zero_digest); + if (!ret && likely(zero_hash_size == ctx->digestsize) && + zero_digest) { + memcpy(req->result, &zero_hash[0], ctx->digestsize); + goto out; + } else if (!ret && !zero_digest) { + dev_dbg(device_data->dev, "[%s] HMAC zero msg with " + "key, continue...", __func__); + } else { + dev_err(device_data->dev, "[%s] ret=%d, or wrong " + "digest size? %s", __func__, ret, + (zero_hash_size == ctx->digestsize) ? + "true" : "false"); + /* Return error */ + goto out; + } + } else if (req->nbytes == 0 && ctx->keylen > 0) { + dev_err(device_data->dev, "[%s] Empty message with " + "keylength > 0, NOT supported.", __func__); + goto out; + } + + if (!req_ctx->updated) { + ret = init_hash_hw(device_data, ctx); + if (ret) { + dev_err(device_data->dev, "[%s] init_hash_hw() " + "failed!", __func__); + goto out; + } + } + + if (req_ctx->state.index) { + hash_messagepad(device_data, req_ctx->state.buffer, + req_ctx->state.index); + } else { + HASH_SET_DCAL; + while (device_data->base->str & HASH_STR_DCAL_MASK) + cpu_relax(); + } + + if (ctx->config.oper_mode == HASH_OPER_MODE_HMAC && ctx->key) { + unsigned int keylen = ctx->keylen; + u8 *key = ctx->key; + + dev_dbg(device_data->dev, "[%s] keylen: %d", __func__, + ctx->keylen); + hash_hw_write_key(device_data, key, keylen); + } + + hash_get_digest(device_data, digest, ctx->config.algorithm); + memcpy(req->result, digest, ctx->digestsize); + +out: + release_hash_device(device_data); + + /** + * Allocated in setkey, and only used in HMAC. + */ + kfree(ctx->key); + + return ret; +} + +/** + * hash_hw_update - Updates current HASH computation hashing another part of + * the message. + * @req: Byte array containing the message to be hashed (caller + * allocated). + */ +int hash_hw_update(struct ahash_request *req) +{ + int ret = 0; + u8 index = 0; + u8 *buffer; + struct hash_device_data *device_data; + u8 *data_buffer; + struct crypto_ahash *tfm = crypto_ahash_reqtfm(req); + struct hash_ctx *ctx = crypto_ahash_ctx(tfm); + struct hash_req_ctx *req_ctx = ahash_request_ctx(req); + struct crypto_hash_walk walk; + int msg_length = crypto_hash_walk_first(req, &walk); + + /* Empty message ("") is correct indata */ + if (msg_length == 0) + return ret; + + index = req_ctx->state.index; + buffer = (u8 *)req_ctx->state.buffer; + + /* Check if ctx->state.length + msg_length + overflows */ + if (msg_length > (req_ctx->state.length.low_word + msg_length) && + HASH_HIGH_WORD_MAX_VAL == + req_ctx->state.length.high_word) { + pr_err(DEV_DBG_NAME " [%s] HASH_MSG_LENGTH_OVERFLOW!", + __func__); + return -EPERM; + } + + ret = hash_get_device_data(ctx, &device_data); + if (ret) + return ret; + + /* Main loop */ + while (0 != msg_length) { + data_buffer = walk.data; + ret = hash_process_data(device_data, ctx, req_ctx, msg_length, + data_buffer, buffer, &index); + + if (ret) { + dev_err(device_data->dev, "[%s] hash_internal_hw_" + "update() failed!", __func__); + goto out; + } + + msg_length = crypto_hash_walk_done(&walk, 0); + } + + req_ctx->state.index = index; + dev_dbg(device_data->dev, "[%s] indata length=%d, bin=%d))", + __func__, req_ctx->state.index, + req_ctx->state.bit_index); + +out: + release_hash_device(device_data); + + return ret; +} + +/** + * hash_resume_state - Function that resumes the state of an calculation. + * @device_data: Pointer to the device structure. + * @device_state: The state to be restored in the hash hardware + */ +int hash_resume_state(struct hash_device_data *device_data, + const struct hash_state *device_state) +{ + u32 temp_cr; + s32 count; + int hash_mode = HASH_OPER_MODE_HASH; + + if (NULL == device_state) { + dev_err(device_data->dev, "[%s] HASH_INVALID_PARAMETER!", + __func__); + return -EPERM; + } + + /* Check correctness of index and length members */ + if (device_state->index > HASH_BLOCK_SIZE + || (device_state->length.low_word % HASH_BLOCK_SIZE) != 0) { + dev_err(device_data->dev, "[%s] HASH_INVALID_PARAMETER!", + __func__); + return -EPERM; + } + + /* + * INIT bit. Set this bit to 0b1 to reset the HASH processor core and + * prepare the initialize the HASH accelerator to compute the message + * digest of a new message. + */ + HASH_INITIALIZE; + + temp_cr = device_state->temp_cr; + writel_relaxed(temp_cr & HASH_CR_RESUME_MASK, &device_data->base->cr); + + if (device_data->base->cr & HASH_CR_MODE_MASK) + hash_mode = HASH_OPER_MODE_HMAC; + else + hash_mode = HASH_OPER_MODE_HASH; + + for (count = 0; count < HASH_CSR_COUNT; count++) { + if ((count >= 36) && (hash_mode == HASH_OPER_MODE_HASH)) + break; + + writel_relaxed(device_state->csr[count], + &device_data->base->csrx[count]); + } + + writel_relaxed(device_state->csfull, &device_data->base->csfull); + writel_relaxed(device_state->csdatain, &device_data->base->csdatain); + + writel_relaxed(device_state->str_reg, &device_data->base->str); + writel_relaxed(temp_cr, &device_data->base->cr); + + return 0; +} + +/** + * hash_save_state - Function that saves the state of hardware. + * @device_data: Pointer to the device structure. + * @device_state: The strucure where the hardware state should be saved. + */ +int hash_save_state(struct hash_device_data *device_data, + struct hash_state *device_state) +{ + u32 temp_cr; + u32 count; + int hash_mode = HASH_OPER_MODE_HASH; + + if (NULL == device_state) { + dev_err(device_data->dev, "[%s] HASH_INVALID_PARAMETER!", + __func__); + return -ENOTSUPP; + } + + /* Write dummy value to force digest intermediate calculation. This + * actually makes sure that there isn't any ongoing calculation in the + * hardware. + */ + while (device_data->base->str & HASH_STR_DCAL_MASK) + cpu_relax(); + + temp_cr = readl_relaxed(&device_data->base->cr); + + device_state->str_reg = readl_relaxed(&device_data->base->str); + + device_state->din_reg = readl_relaxed(&device_data->base->din); + + if (device_data->base->cr & HASH_CR_MODE_MASK) + hash_mode = HASH_OPER_MODE_HMAC; + else + hash_mode = HASH_OPER_MODE_HASH; + + for (count = 0; count < HASH_CSR_COUNT; count++) { + if ((count >= 36) && (hash_mode == HASH_OPER_MODE_HASH)) + break; + + device_state->csr[count] = + readl_relaxed(&device_data->base->csrx[count]); + } + + device_state->csfull = readl_relaxed(&device_data->base->csfull); + device_state->csdatain = readl_relaxed(&device_data->base->csdatain); + + device_state->temp_cr = temp_cr; + + return 0; +} + +/** + * hash_check_hw - This routine checks for peripheral Ids and PCell Ids. + * @device_data: + * + */ +int hash_check_hw(struct hash_device_data *device_data) +{ + /* Checking Peripheral Ids */ + if (HASH_P_ID0 == readl_relaxed(&device_data->base->periphid0) + && HASH_P_ID1 == readl_relaxed(&device_data->base->periphid1) + && HASH_P_ID2 == readl_relaxed(&device_data->base->periphid2) + && HASH_P_ID3 == readl_relaxed(&device_data->base->periphid3) + && HASH_CELL_ID0 == readl_relaxed(&device_data->base->cellid0) + && HASH_CELL_ID1 == readl_relaxed(&device_data->base->cellid1) + && HASH_CELL_ID2 == readl_relaxed(&device_data->base->cellid2) + && HASH_CELL_ID3 == readl_relaxed(&device_data->base->cellid3) + ) { + return 0; + } + + dev_err(device_data->dev, "[%s] HASH_UNSUPPORTED_HW!", + __func__); + return -ENOTSUPP; +} + +/** + * hash_get_digest - Gets the digest. + * @device_data: Pointer to the device structure. + * @digest: User allocated byte array for the calculated digest. + * @algorithm: The algorithm in use. + */ +void hash_get_digest(struct hash_device_data *device_data, + u8 *digest, int algorithm) +{ + u32 temp_hx_val, count; + int loop_ctr; + + if (algorithm != HASH_ALGO_SHA1 && algorithm != HASH_ALGO_SHA256) { + dev_err(device_data->dev, "[%s] Incorrect algorithm %d", + __func__, algorithm); + return; + } + + if (algorithm == HASH_ALGO_SHA1) + loop_ctr = SHA1_DIGEST_SIZE / sizeof(u32); + else + loop_ctr = SHA256_DIGEST_SIZE / sizeof(u32); + + dev_dbg(device_data->dev, "[%s] digest array:(0x%x)", + __func__, (u32) digest); + + /* Copy result into digest array */ + for (count = 0; count < loop_ctr; count++) { + temp_hx_val = readl_relaxed(&device_data->base->hx[count]); + digest[count * 4] = (u8) ((temp_hx_val >> 24) & 0xFF); + digest[count * 4 + 1] = (u8) ((temp_hx_val >> 16) & 0xFF); + digest[count * 4 + 2] = (u8) ((temp_hx_val >> 8) & 0xFF); + digest[count * 4 + 3] = (u8) ((temp_hx_val >> 0) & 0xFF); + } +} + +/** + * hash_update - The hash update function for SHA1/SHA2 (SHA256). + * @req: The hash request for the job. + */ +static int ahash_update(struct ahash_request *req) +{ + int ret = 0; + struct hash_req_ctx *req_ctx = ahash_request_ctx(req); + + if (hash_mode != HASH_MODE_DMA || !req_ctx->dma_mode) + ret = hash_hw_update(req); + /* Skip update for DMA, all data will be passed to DMA in final */ + + if (ret) { + pr_err(DEV_DBG_NAME " [%s] hash_hw_update() failed!", + __func__); + } + + return ret; +} + +/** + * hash_final - The hash final function for SHA1/SHA2 (SHA256). + * @req: The hash request for the job. + */ +static int ahash_final(struct ahash_request *req) +{ + int ret = 0; + struct hash_req_ctx *req_ctx = ahash_request_ctx(req); + + pr_debug(DEV_DBG_NAME " [%s] data size: %d", __func__, req->nbytes); + + if ((hash_mode == HASH_MODE_DMA) && req_ctx->dma_mode) + ret = hash_dma_final(req); + else + ret = hash_hw_final(req); + + if (ret) { + pr_err(DEV_DBG_NAME " [%s] hash_hw/dma_final() failed", + __func__); + } + + return ret; +} + +static int hash_setkey(struct crypto_ahash *tfm, + const u8 *key, unsigned int keylen, int alg) +{ + int ret = 0; + struct hash_ctx *ctx = crypto_ahash_ctx(tfm); + + /** + * Freed in final. + */ + ctx->key = kmalloc(keylen, GFP_KERNEL); + if (!ctx->key) { + pr_err(DEV_DBG_NAME " [%s] Failed to allocate ctx->key " + "for %d\n", __func__, alg); + return -ENOMEM; + } + + memcpy(ctx->key, key, keylen); + ctx->keylen = keylen; + + return ret; +} + +static int ahash_sha1_init(struct ahash_request *req) +{ + struct crypto_ahash *tfm = crypto_ahash_reqtfm(req); + struct hash_ctx *ctx = crypto_ahash_ctx(tfm); + + ctx->config.data_format = HASH_DATA_8_BITS; + ctx->config.algorithm = HASH_ALGO_SHA1; + ctx->config.oper_mode = HASH_OPER_MODE_HASH; + ctx->digestsize = SHA1_DIGEST_SIZE; + + return hash_init(req); +} + +static int ahash_sha256_init(struct ahash_request *req) +{ + struct crypto_ahash *tfm = crypto_ahash_reqtfm(req); + struct hash_ctx *ctx = crypto_ahash_ctx(tfm); + + ctx->config.data_format = HASH_DATA_8_BITS; + ctx->config.algorithm = HASH_ALGO_SHA256; + ctx->config.oper_mode = HASH_OPER_MODE_HASH; + ctx->digestsize = SHA256_DIGEST_SIZE; + + return hash_init(req); +} + +static int ahash_sha1_digest(struct ahash_request *req) +{ + int ret2, ret1; + + ret1 = ahash_sha1_init(req); + if (ret1) + goto out; + + ret1 = ahash_update(req); + ret2 = ahash_final(req); + +out: + return ret1 ? ret1 : ret2; +} + +static int ahash_sha256_digest(struct ahash_request *req) +{ + int ret2, ret1; + + ret1 = ahash_sha256_init(req); + if (ret1) + goto out; + + ret1 = ahash_update(req); + ret2 = ahash_final(req); + +out: + return ret1 ? ret1 : ret2; +} + +static int hmac_sha1_init(struct ahash_request *req) +{ + struct crypto_ahash *tfm = crypto_ahash_reqtfm(req); + struct hash_ctx *ctx = crypto_ahash_ctx(tfm); + + ctx->config.data_format = HASH_DATA_8_BITS; + ctx->config.algorithm = HASH_ALGO_SHA1; + ctx->config.oper_mode = HASH_OPER_MODE_HMAC; + ctx->digestsize = SHA1_DIGEST_SIZE; + + return hash_init(req); +} + +static int hmac_sha256_init(struct ahash_request *req) +{ + struct crypto_ahash *tfm = crypto_ahash_reqtfm(req); + struct hash_ctx *ctx = crypto_ahash_ctx(tfm); + + ctx->config.data_format = HASH_DATA_8_BITS; + ctx->config.algorithm = HASH_ALGO_SHA256; + ctx->config.oper_mode = HASH_OPER_MODE_HMAC; + ctx->digestsize = SHA256_DIGEST_SIZE; + + return hash_init(req); +} + +static int hmac_sha1_digest(struct ahash_request *req) +{ + int ret2, ret1; + + ret1 = hmac_sha1_init(req); + if (ret1) + goto out; + + ret1 = ahash_update(req); + ret2 = ahash_final(req); + +out: + return ret1 ? ret1 : ret2; +} + +static int hmac_sha256_digest(struct ahash_request *req) +{ + int ret2, ret1; + + ret1 = hmac_sha256_init(req); + if (ret1) + goto out; + + ret1 = ahash_update(req); + ret2 = ahash_final(req); + +out: + return ret1 ? ret1 : ret2; +} + +static int hmac_sha1_setkey(struct crypto_ahash *tfm, + const u8 *key, unsigned int keylen) +{ + return hash_setkey(tfm, key, keylen, HASH_ALGO_SHA1); +} + +static int hmac_sha256_setkey(struct crypto_ahash *tfm, + const u8 *key, unsigned int keylen) +{ + return hash_setkey(tfm, key, keylen, HASH_ALGO_SHA256); +} + +struct hash_algo_template { + struct hash_config conf; + struct ahash_alg hash; +}; + +static int hash_cra_init(struct crypto_tfm *tfm) +{ + struct hash_ctx *ctx = crypto_tfm_ctx(tfm); + struct crypto_alg *alg = tfm->__crt_alg; + struct hash_algo_template *hash_alg; + + hash_alg = container_of(__crypto_ahash_alg(alg), + struct hash_algo_template, + hash); + + crypto_ahash_set_reqsize(__crypto_ahash_cast(tfm), + sizeof(struct hash_req_ctx)); + + ctx->config.data_format = HASH_DATA_8_BITS; + ctx->config.algorithm = hash_alg->conf.algorithm; + ctx->config.oper_mode = hash_alg->conf.oper_mode; + + ctx->digestsize = hash_alg->hash.halg.digestsize; + + return 0; +} + +static struct hash_algo_template hash_algs[] = { + { + .conf.algorithm = HASH_ALGO_SHA1, + .conf.oper_mode = HASH_OPER_MODE_HASH, + .hash = { + .init = hash_init, + .update = ahash_update, + .final = ahash_final, + .digest = ahash_sha1_digest, + .halg.digestsize = SHA1_DIGEST_SIZE, + .halg.statesize = sizeof(struct hash_ctx), + .halg.base = { + .cra_name = "sha1", + .cra_driver_name = "sha1-ux500", + .cra_flags = CRYPTO_ALG_TYPE_AHASH | + CRYPTO_ALG_ASYNC, + .cra_blocksize = SHA1_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct hash_ctx), + .cra_init = hash_cra_init, + .cra_module = THIS_MODULE, + } + } + }, + { + .conf.algorithm = HASH_ALGO_SHA256, + .conf.oper_mode = HASH_OPER_MODE_HASH, + .hash = { + .init = hash_init, + .update = ahash_update, + .final = ahash_final, + .digest = ahash_sha256_digest, + .halg.digestsize = SHA256_DIGEST_SIZE, + .halg.statesize = sizeof(struct hash_ctx), + .halg.base = { + .cra_name = "sha256", + .cra_driver_name = "sha256-ux500", + .cra_flags = CRYPTO_ALG_TYPE_AHASH | + CRYPTO_ALG_ASYNC, + .cra_blocksize = SHA256_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct hash_ctx), + .cra_type = &crypto_ahash_type, + .cra_init = hash_cra_init, + .cra_module = THIS_MODULE, + } + } + + }, + { + .conf.algorithm = HASH_ALGO_SHA1, + .conf.oper_mode = HASH_OPER_MODE_HMAC, + .hash = { + .init = hash_init, + .update = ahash_update, + .final = ahash_final, + .digest = hmac_sha1_digest, + .setkey = hmac_sha1_setkey, + .halg.digestsize = SHA1_DIGEST_SIZE, + .halg.statesize = sizeof(struct hash_ctx), + .halg.base = { + .cra_name = "hmac(sha1)", + .cra_driver_name = "hmac-sha1-ux500", + .cra_flags = CRYPTO_ALG_TYPE_AHASH | + CRYPTO_ALG_ASYNC, + .cra_blocksize = SHA1_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct hash_ctx), + .cra_type = &crypto_ahash_type, + .cra_init = hash_cra_init, + .cra_module = THIS_MODULE, + } + } + }, + { + .conf.algorithm = HASH_ALGO_SHA256, + .conf.oper_mode = HASH_OPER_MODE_HMAC, + .hash = { + .init = hash_init, + .update = ahash_update, + .final = ahash_final, + .digest = hmac_sha256_digest, + .setkey = hmac_sha256_setkey, + .halg.digestsize = SHA256_DIGEST_SIZE, + .halg.statesize = sizeof(struct hash_ctx), + .halg.base = { + .cra_name = "hmac(sha256)", + .cra_driver_name = "hmac-sha256-ux500", + .cra_flags = CRYPTO_ALG_TYPE_AHASH | + CRYPTO_ALG_ASYNC, + .cra_blocksize = SHA256_BLOCK_SIZE, + .cra_ctxsize = sizeof(struct hash_ctx), + .cra_type = &crypto_ahash_type, + .cra_init = hash_cra_init, + .cra_module = THIS_MODULE, + } + } + } +}; + +/** + * hash_algs_register_all - + */ +static int ahash_algs_register_all(struct hash_device_data *device_data) +{ + int ret; + int i; + int count; + + for (i = 0; i < ARRAY_SIZE(hash_algs); i++) { + ret = crypto_register_ahash(&hash_algs[i].hash); + if (ret) { + count = i; + dev_err(device_data->dev, "[%s] alg registration failed", + hash_algs[i].hash.halg.base.cra_driver_name); + goto unreg; + } + } + return 0; +unreg: + for (i = 0; i < count; i++) + crypto_unregister_ahash(&hash_algs[i].hash); + return ret; +} + +/** + * hash_algs_unregister_all - + */ +static void ahash_algs_unregister_all(struct hash_device_data *device_data) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(hash_algs); i++) + crypto_unregister_ahash(&hash_algs[i].hash); +} + +/** + * ux500_hash_probe - Function that probes the hash hardware. + * @pdev: The platform device. + */ +static int ux500_hash_probe(struct platform_device *pdev) +{ + int ret = 0; + struct resource *res = NULL; + struct hash_device_data *device_data; + struct device *dev = &pdev->dev; + + device_data = kzalloc(sizeof(struct hash_device_data), GFP_ATOMIC); + if (!device_data) { + dev_dbg(dev, "[%s] kzalloc() failed!", __func__); + ret = -ENOMEM; + goto out; + } + + device_data->dev = dev; + device_data->current_ctx = NULL; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_dbg(dev, "[%s] platform_get_resource() failed!", __func__); + ret = -ENODEV; + goto out_kfree; + } + + res = request_mem_region(res->start, resource_size(res), pdev->name); + if (res == NULL) { + dev_dbg(dev, "[%s] request_mem_region() failed!", __func__); + ret = -EBUSY; + goto out_kfree; + } + + device_data->base = ioremap(res->start, resource_size(res)); + if (!device_data->base) { + dev_err(dev, "[%s] ioremap() failed!", + __func__); + ret = -ENOMEM; + goto out_free_mem; + } + spin_lock_init(&device_data->ctx_lock); + spin_lock_init(&device_data->power_state_lock); + + /* Enable power for HASH1 hardware block */ + device_data->regulator = regulator_get(dev, "v-ape"); + if (IS_ERR(device_data->regulator)) { + dev_err(dev, "[%s] regulator_get() failed!", __func__); + ret = PTR_ERR(device_data->regulator); + device_data->regulator = NULL; + goto out_unmap; + } + + /* Enable the clock for HASH1 hardware block */ + device_data->clk = clk_get(dev, NULL); + if (IS_ERR(device_data->clk)) { + dev_err(dev, "[%s] clk_get() failed!", __func__); + ret = PTR_ERR(device_data->clk); + goto out_regulator; + } + + /* Enable device power (and clock) */ + ret = hash_enable_power(device_data, false); + if (ret) { + dev_err(dev, "[%s]: hash_enable_power() failed!", __func__); + goto out_clk; + } + + ret = hash_check_hw(device_data); + if (ret) { + dev_err(dev, "[%s] hash_check_hw() failed!", __func__); + goto out_power; + } + + if (hash_mode == HASH_MODE_DMA) + hash_dma_setup_channel(device_data, dev); + + platform_set_drvdata(pdev, device_data); + + /* Put the new device into the device list... */ + klist_add_tail(&device_data->list_node, &driver_data.device_list); + /* ... and signal that a new device is available. */ + up(&driver_data.device_allocation); + + ret = ahash_algs_register_all(device_data); + if (ret) { + dev_err(dev, "[%s] ahash_algs_register_all() " + "failed!", __func__); + goto out_power; + } + + dev_info(dev, "[%s] successfully probed\n", __func__); + return 0; + +out_power: + hash_disable_power(device_data, false); + +out_clk: + clk_put(device_data->clk); + +out_regulator: + regulator_put(device_data->regulator); + +out_unmap: + iounmap(device_data->base); + +out_free_mem: + release_mem_region(res->start, resource_size(res)); + +out_kfree: + kfree(device_data); +out: + return ret; +} + +/** + * ux500_hash_remove - Function that removes the hash device from the platform. + * @pdev: The platform device. + */ +static int ux500_hash_remove(struct platform_device *pdev) +{ + struct resource *res; + struct hash_device_data *device_data; + struct device *dev = &pdev->dev; + + device_data = platform_get_drvdata(pdev); + if (!device_data) { + dev_err(dev, "[%s]: platform_get_drvdata() failed!", + __func__); + return -ENOMEM; + } + + /* Try to decrease the number of available devices. */ + if (down_trylock(&driver_data.device_allocation)) + return -EBUSY; + + /* Check that the device is free */ + spin_lock(&device_data->ctx_lock); + /* current_ctx allocates a device, NULL = unallocated */ + if (device_data->current_ctx) { + /* The device is busy */ + spin_unlock(&device_data->ctx_lock); + /* Return the device to the pool. */ + up(&driver_data.device_allocation); + return -EBUSY; + } + + spin_unlock(&device_data->ctx_lock); + + /* Remove the device from the list */ + if (klist_node_attached(&device_data->list_node)) + klist_remove(&device_data->list_node); + + /* If this was the last device, remove the services */ + if (list_empty(&driver_data.device_list.k_list)) + ahash_algs_unregister_all(device_data); + + if (hash_disable_power(device_data, false)) + dev_err(dev, "[%s]: hash_disable_power() failed", + __func__); + + clk_put(device_data->clk); + regulator_put(device_data->regulator); + + iounmap(device_data->base); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res) + release_mem_region(res->start, resource_size(res)); + + kfree(device_data); + + return 0; +} + +/** + * ux500_hash_shutdown - Function that shutdown the hash device. + * @pdev: The platform device + */ +static void ux500_hash_shutdown(struct platform_device *pdev) +{ + struct resource *res = NULL; + struct hash_device_data *device_data; + + device_data = platform_get_drvdata(pdev); + if (!device_data) { + dev_err(&pdev->dev, "[%s] platform_get_drvdata() failed!", + __func__); + return; + } + + /* Check that the device is free */ + spin_lock(&device_data->ctx_lock); + /* current_ctx allocates a device, NULL = unallocated */ + if (!device_data->current_ctx) { + if (down_trylock(&driver_data.device_allocation)) + dev_dbg(&pdev->dev, "[%s]: Cryp still in use!" + "Shutting down anyway...", __func__); + /** + * (Allocate the device) + * Need to set this to non-null (dummy) value, + * to avoid usage if context switching. + */ + device_data->current_ctx++; + } + spin_unlock(&device_data->ctx_lock); + + /* Remove the device from the list */ + if (klist_node_attached(&device_data->list_node)) + klist_remove(&device_data->list_node); + + /* If this was the last device, remove the services */ + if (list_empty(&driver_data.device_list.k_list)) + ahash_algs_unregister_all(device_data); + + iounmap(device_data->base); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res) + release_mem_region(res->start, resource_size(res)); + + if (hash_disable_power(device_data, false)) + dev_err(&pdev->dev, "[%s] hash_disable_power() failed", + __func__); +} + +/** + * ux500_hash_suspend - Function that suspends the hash device. + * @pdev: The platform device. + * @state: - + */ +static int ux500_hash_suspend(struct platform_device *pdev, pm_message_t state) +{ + int ret; + struct hash_device_data *device_data; + struct hash_ctx *temp_ctx = NULL; + + device_data = platform_get_drvdata(pdev); + if (!device_data) { + dev_err(&pdev->dev, "[%s] platform_get_drvdata() failed!", + __func__); + return -ENOMEM; + } + + spin_lock(&device_data->ctx_lock); + if (!device_data->current_ctx) + device_data->current_ctx++; + spin_unlock(&device_data->ctx_lock); + + if (device_data->current_ctx == ++temp_ctx) { + if (down_interruptible(&driver_data.device_allocation)) + dev_dbg(&pdev->dev, "[%s]: down_interruptible() " + "failed", __func__); + ret = hash_disable_power(device_data, false); + + } else + ret = hash_disable_power(device_data, true); + + if (ret) + dev_err(&pdev->dev, "[%s]: hash_disable_power()", __func__); + + return ret; +} + +/** + * ux500_hash_resume - Function that resume the hash device. + * @pdev: The platform device. + */ +static int ux500_hash_resume(struct platform_device *pdev) +{ + int ret = 0; + struct hash_device_data *device_data; + struct hash_ctx *temp_ctx = NULL; + + device_data = platform_get_drvdata(pdev); + if (!device_data) { + dev_err(&pdev->dev, "[%s] platform_get_drvdata() failed!", + __func__); + return -ENOMEM; + } + + spin_lock(&device_data->ctx_lock); + if (device_data->current_ctx == ++temp_ctx) + device_data->current_ctx = NULL; + spin_unlock(&device_data->ctx_lock); + + if (!device_data->current_ctx) + up(&driver_data.device_allocation); + else + ret = hash_enable_power(device_data, true); + + if (ret) + dev_err(&pdev->dev, "[%s]: hash_enable_power() failed!", + __func__); + + return ret; +} + +static struct platform_driver hash_driver = { + .probe = ux500_hash_probe, + .remove = ux500_hash_remove, + .shutdown = ux500_hash_shutdown, + .suspend = ux500_hash_suspend, + .resume = ux500_hash_resume, + .driver = { + .owner = THIS_MODULE, + .name = "hash1", + } +}; + +/** + * ux500_hash_mod_init - The kernel module init function. + */ +static int __init ux500_hash_mod_init(void) +{ + klist_init(&driver_data.device_list, NULL, NULL); + /* Initialize the semaphore to 0 devices (locked state) */ + sema_init(&driver_data.device_allocation, 0); + + return platform_driver_register(&hash_driver); +} + +/** + * ux500_hash_mod_fini - The kernel module exit function. + */ +static void __exit ux500_hash_mod_fini(void) +{ + platform_driver_unregister(&hash_driver); + return; +} + +module_init(ux500_hash_mod_init); +module_exit(ux500_hash_mod_fini); + +MODULE_DESCRIPTION("Driver for ST-Ericsson UX500 HASH engine."); +MODULE_LICENSE("GPL"); + +MODULE_ALIAS("sha1-all"); +MODULE_ALIAS("sha256-all"); +MODULE_ALIAS("hmac-sha1-all"); +MODULE_ALIAS("hmac-sha256-all"); diff --git a/drivers/devfreq/governor_performance.c b/drivers/devfreq/governor_performance.c index 574a06b1b1de..af75ddd4f158 100644 --- a/drivers/devfreq/governor_performance.c +++ b/drivers/devfreq/governor_performance.c @@ -10,6 +10,7 @@ */ #include <linux/devfreq.h> +#include "governor.h" static int devfreq_performance_func(struct devfreq *df, unsigned long *freq) @@ -25,8 +26,14 @@ static int devfreq_performance_func(struct devfreq *df, return 0; } +static int performance_init(struct devfreq *devfreq) +{ + return update_devfreq(devfreq); +} + const struct devfreq_governor devfreq_performance = { .name = "performance", + .init = performance_init, .get_target_freq = devfreq_performance_func, .no_central_polling = true, }; diff --git a/drivers/devfreq/governor_powersave.c b/drivers/devfreq/governor_powersave.c index d742d4a82d6a..fec0cdbd2477 100644 --- a/drivers/devfreq/governor_powersave.c +++ b/drivers/devfreq/governor_powersave.c @@ -10,6 +10,7 @@ */ #include <linux/devfreq.h> +#include "governor.h" static int devfreq_powersave_func(struct devfreq *df, unsigned long *freq) @@ -22,8 +23,14 @@ static int devfreq_powersave_func(struct devfreq *df, return 0; } +static int powersave_init(struct devfreq *devfreq) +{ + return update_devfreq(devfreq); +} + const struct devfreq_governor devfreq_powersave = { .name = "powersave", + .init = powersave_init, .get_target_freq = devfreq_powersave_func, .no_central_polling = true, }; diff --git a/drivers/firewire/core-card.c b/drivers/firewire/core-card.c index cc595eba7ba9..f5552b362efc 100644 --- a/drivers/firewire/core-card.c +++ b/drivers/firewire/core-card.c @@ -676,6 +676,7 @@ void fw_card_release(struct kref *kref) complete(&card->done); } +EXPORT_SYMBOL_GPL(fw_card_release); void fw_core_remove_card(struct fw_card *card) { diff --git a/drivers/firewire/core-transaction.c b/drivers/firewire/core-transaction.c index dea2dcc9310d..db8a965cf712 100644 --- a/drivers/firewire/core-transaction.c +++ b/drivers/firewire/core-transaction.c @@ -820,6 +820,15 @@ void fw_send_response(struct fw_card *card, } EXPORT_SYMBOL(fw_send_response); +/** + * fw_get_request_speed() - returns speed at which the @request was received + */ +int fw_get_request_speed(struct fw_request *request) +{ + return request->response.speed; +} +EXPORT_SYMBOL(fw_get_request_speed); + static void handle_exclusive_region_request(struct fw_card *card, struct fw_packet *p, struct fw_request *request, diff --git a/drivers/firewire/core.h b/drivers/firewire/core.h index 9047f5547d98..b5a2f6197053 100644 --- a/drivers/firewire/core.h +++ b/drivers/firewire/core.h @@ -120,21 +120,6 @@ int fw_compute_block_crc(__be32 *block); void fw_schedule_bus_reset(struct fw_card *card, bool delayed, bool short_reset); void fw_schedule_bm_work(struct fw_card *card, unsigned long delay); -static inline struct fw_card *fw_card_get(struct fw_card *card) -{ - kref_get(&card->kref); - - return card; -} - -void fw_card_release(struct kref *kref); - -static inline void fw_card_put(struct fw_card *card) -{ - kref_put(&card->kref, fw_card_release); -} - - /* -cdev */ extern const struct file_operations fw_device_ops; diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index e466ecba8dc1..7cd9bf42108b 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1102,6 +1102,19 @@ config SENSORS_AMC6821 This driver can also be build as a module. If so, the module will be called amc6821. +config SENSORS_INA2XX + tristate "Texas Instruments INA219, INA226" + depends on I2C && EXPERIMENTAL + help + If you say yes here you get support for INA219 and INA226 power + monitor chips. + + The INA2xx driver is configured for the default configuration of + the part as described in the datasheet. + Default value for Rshunt is 10 mOhms. + This driver can also be built as a module. If so, the module + will be called ina2xx. + config SENSORS_THMC50 tristate "Texas Instruments THMC50 / Analog Devices ADM1022" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 6d3f11f71815..e1eeac13b851 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -62,6 +62,7 @@ obj-$(CONFIG_SENSORS_ULTRA45) += ultra45_env.o obj-$(CONFIG_SENSORS_I5K_AMB) += i5k_amb.o obj-$(CONFIG_SENSORS_IBMAEM) += ibmaem.o obj-$(CONFIG_SENSORS_IBMPEX) += ibmpex.o +obj-$(CONFIG_SENSORS_INA2XX) += ina2xx.o obj-$(CONFIG_SENSORS_IT87) += it87.o obj-$(CONFIG_SENSORS_JC42) += jc42.o obj-$(CONFIG_SENSORS_JZ4740) += jz4740-hwmon.o diff --git a/drivers/hwmon/acpi_power_meter.c b/drivers/hwmon/acpi_power_meter.c index 9140236a0182..34ad5a27a7e9 100644 --- a/drivers/hwmon/acpi_power_meter.c +++ b/drivers/hwmon/acpi_power_meter.c @@ -107,15 +107,7 @@ struct acpi_power_meter_resource { struct kobject *holders_dir; }; -struct ro_sensor_template { - char *label; - ssize_t (*show)(struct device *dev, - struct device_attribute *devattr, - char *buf); - int index; -}; - -struct rw_sensor_template { +struct sensor_template { char *label; ssize_t (*show)(struct device *dev, struct device_attribute *devattr, @@ -469,52 +461,67 @@ static ssize_t show_name(struct device *dev, return sprintf(buf, "%s\n", ACPI_POWER_METER_NAME); } +#define RO_SENSOR_TEMPLATE(_label, _show, _index) \ + { \ + .label = _label, \ + .show = _show, \ + .index = _index, \ + } + +#define RW_SENSOR_TEMPLATE(_label, _show, _set, _index) \ + { \ + .label = _label, \ + .show = _show, \ + .set = _set, \ + .index = _index, \ + } + /* Sensor descriptions. If you add a sensor, update NUM_SENSORS above! */ -static struct ro_sensor_template meter_ro_attrs[] = { -{POWER_AVERAGE_NAME, show_power, 0}, -{"power1_accuracy", show_accuracy, 0}, -{"power1_average_interval_min", show_val, 0}, -{"power1_average_interval_max", show_val, 1}, -{"power1_is_battery", show_val, 5}, -{NULL, NULL, 0}, +static struct sensor_template meter_attrs[] = { + RO_SENSOR_TEMPLATE(POWER_AVERAGE_NAME, show_power, 0), + RO_SENSOR_TEMPLATE("power1_accuracy", show_accuracy, 0), + RO_SENSOR_TEMPLATE("power1_average_interval_min", show_val, 0), + RO_SENSOR_TEMPLATE("power1_average_interval_max", show_val, 1), + RO_SENSOR_TEMPLATE("power1_is_battery", show_val, 5), + RW_SENSOR_TEMPLATE(POWER_AVG_INTERVAL_NAME, show_avg_interval, + set_avg_interval, 0), + {}, }; -static struct rw_sensor_template meter_rw_attrs[] = { -{POWER_AVG_INTERVAL_NAME, show_avg_interval, set_avg_interval, 0}, -{NULL, NULL, NULL, 0}, +static struct sensor_template misc_cap_attrs[] = { + RO_SENSOR_TEMPLATE("power1_cap_min", show_val, 2), + RO_SENSOR_TEMPLATE("power1_cap_max", show_val, 3), + RO_SENSOR_TEMPLATE("power1_cap_hyst", show_val, 4), + RO_SENSOR_TEMPLATE(POWER_ALARM_NAME, show_val, 6), + {}, }; -static struct ro_sensor_template misc_cap_attrs[] = { -{"power1_cap_min", show_val, 2}, -{"power1_cap_max", show_val, 3}, -{"power1_cap_hyst", show_val, 4}, -{POWER_ALARM_NAME, show_val, 6}, -{NULL, NULL, 0}, +static struct sensor_template ro_cap_attrs[] = { + RO_SENSOR_TEMPLATE(POWER_CAP_NAME, show_cap, 0), + {}, }; -static struct ro_sensor_template ro_cap_attrs[] = { -{POWER_CAP_NAME, show_cap, 0}, -{NULL, NULL, 0}, +static struct sensor_template rw_cap_attrs[] = { + RW_SENSOR_TEMPLATE(POWER_CAP_NAME, show_cap, set_cap, 0), + {}, }; -static struct rw_sensor_template rw_cap_attrs[] = { -{POWER_CAP_NAME, show_cap, set_cap, 0}, -{NULL, NULL, NULL, 0}, +static struct sensor_template trip_attrs[] = { + RW_SENSOR_TEMPLATE("power1_average_min", show_val, set_trip, 7), + RW_SENSOR_TEMPLATE("power1_average_max", show_val, set_trip, 8), + {}, }; -static struct rw_sensor_template trip_attrs[] = { -{"power1_average_min", show_val, set_trip, 7}, -{"power1_average_max", show_val, set_trip, 8}, -{NULL, NULL, NULL, 0}, +static struct sensor_template misc_attrs[] = { + RO_SENSOR_TEMPLATE("name", show_name, 0), + RO_SENSOR_TEMPLATE("power1_model_number", show_str, 0), + RO_SENSOR_TEMPLATE("power1_oem_info", show_str, 2), + RO_SENSOR_TEMPLATE("power1_serial_number", show_str, 1), + {}, }; -static struct ro_sensor_template misc_attrs[] = { -{"name", show_name, 0}, -{"power1_model_number", show_str, 0}, -{"power1_oem_info", show_str, 2}, -{"power1_serial_number", show_str, 1}, -{NULL, NULL, 0}, -}; +#undef RO_SENSOR_TEMPLATE +#undef RW_SENSOR_TEMPLATE /* Read power domain data */ static void remove_domain_devices(struct acpi_power_meter_resource *resource) @@ -619,49 +626,24 @@ end: } /* Registration and deregistration */ -static int register_ro_attrs(struct acpi_power_meter_resource *resource, - struct ro_sensor_template *ro) +static int register_attrs(struct acpi_power_meter_resource *resource, + struct sensor_template *attrs) { struct device *dev = &resource->acpi_dev->dev; struct sensor_device_attribute *sensors = &resource->sensors[resource->num_sensors]; int res = 0; - while (ro->label) { - sensors->dev_attr.attr.name = ro->label; + while (attrs->label) { + sensors->dev_attr.attr.name = attrs->label; sensors->dev_attr.attr.mode = S_IRUGO; - sensors->dev_attr.show = ro->show; - sensors->index = ro->index; + sensors->dev_attr.show = attrs->show; + sensors->index = attrs->index; - sysfs_attr_init(&sensors->dev_attr.attr); - res = device_create_file(dev, &sensors->dev_attr); - if (res) { - sensors->dev_attr.attr.name = NULL; - goto error; + if (attrs->set) { + sensors->dev_attr.attr.mode |= S_IWUSR; + sensors->dev_attr.store = attrs->set; } - sensors++; - resource->num_sensors++; - ro++; - } - -error: - return res; -} - -static int register_rw_attrs(struct acpi_power_meter_resource *resource, - struct rw_sensor_template *rw) -{ - struct device *dev = &resource->acpi_dev->dev; - struct sensor_device_attribute *sensors = - &resource->sensors[resource->num_sensors]; - int res = 0; - - while (rw->label) { - sensors->dev_attr.attr.name = rw->label; - sensors->dev_attr.attr.mode = S_IRUGO | S_IWUSR; - sensors->dev_attr.show = rw->show; - sensors->dev_attr.store = rw->set; - sensors->index = rw->index; sysfs_attr_init(&sensors->dev_attr.attr); res = device_create_file(dev, &sensors->dev_attr); @@ -671,7 +653,7 @@ static int register_rw_attrs(struct acpi_power_meter_resource *resource, } sensors++; resource->num_sensors++; - rw++; + attrs++; } error: @@ -703,10 +685,7 @@ static int setup_attrs(struct acpi_power_meter_resource *resource) return res; if (resource->caps.flags & POWER_METER_CAN_MEASURE) { - res = register_ro_attrs(resource, meter_ro_attrs); - if (res) - goto error; - res = register_rw_attrs(resource, meter_rw_attrs); + res = register_attrs(resource, meter_attrs); if (res) goto error; } @@ -718,28 +697,27 @@ static int setup_attrs(struct acpi_power_meter_resource *resource) goto skip_unsafe_cap; } - if (resource->caps.configurable_cap) { - res = register_rw_attrs(resource, rw_cap_attrs); - if (res) - goto error; - } else { - res = register_ro_attrs(resource, ro_cap_attrs); - if (res) - goto error; - } - res = register_ro_attrs(resource, misc_cap_attrs); + if (resource->caps.configurable_cap) + res = register_attrs(resource, rw_cap_attrs); + else + res = register_attrs(resource, ro_cap_attrs); + + if (res) + goto error; + + res = register_attrs(resource, misc_cap_attrs); if (res) goto error; } -skip_unsafe_cap: +skip_unsafe_cap: if (resource->caps.flags & POWER_METER_CAN_TRIP) { - res = register_rw_attrs(resource, trip_attrs); + res = register_attrs(resource, trip_attrs); if (res) goto error; } - res = register_ro_attrs(resource, misc_attrs); + res = register_attrs(resource, misc_attrs); if (res) goto error; diff --git a/drivers/hwmon/ad7314.c b/drivers/hwmon/ad7314.c index f85ce70d9677..cfec802cf9ca 100644 --- a/drivers/hwmon/ad7314.c +++ b/drivers/hwmon/ad7314.c @@ -18,21 +18,14 @@ #include <linux/hwmon-sysfs.h> /* - * AD7314 power mode - */ -#define AD7314_PD 0x2000 - -/* * AD7314 temperature masks */ -#define AD7314_TEMP_SIGN 0x200 #define AD7314_TEMP_MASK 0x7FE0 -#define AD7314_TEMP_OFFSET 5 +#define AD7314_TEMP_SHIFT 5 /* * ADT7301 and ADT7302 temperature masks */ -#define ADT7301_TEMP_SIGN 0x2000 #define ADT7301_TEMP_MASK 0x3FFF enum ad7314_variant { @@ -73,7 +66,7 @@ static ssize_t ad7314_show_temperature(struct device *dev, return ret; switch (spi_get_device_id(chip->spi_dev)->driver_data) { case ad7314: - data = (ret & AD7314_TEMP_MASK) >> AD7314_TEMP_OFFSET; + data = (ret & AD7314_TEMP_MASK) >> AD7314_TEMP_SHIFT; data = (data << 6) >> 6; return sprintf(buf, "%d\n", 250 * data); diff --git a/drivers/hwmon/fam15h_power.c b/drivers/hwmon/fam15h_power.c index e8e18cab1fb8..6b13f1a4dc27 100644 --- a/drivers/hwmon/fam15h_power.c +++ b/drivers/hwmon/fam15h_power.c @@ -257,15 +257,4 @@ static struct pci_driver fam15h_power_driver = { .remove = __devexit_p(fam15h_power_remove), }; -static int __init fam15h_power_init(void) -{ - return pci_register_driver(&fam15h_power_driver); -} - -static void __exit fam15h_power_exit(void) -{ - pci_unregister_driver(&fam15h_power_driver); -} - -module_init(fam15h_power_init) -module_exit(fam15h_power_exit) +module_pci_driver(fam15h_power_driver); diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c new file mode 100644 index 000000000000..7f3f4a385729 --- /dev/null +++ b/drivers/hwmon/ina2xx.c @@ -0,0 +1,368 @@ +/* + * Driver for Texas Instruments INA219, INA226 power monitor chips + * + * INA219: + * Zero Drift Bi-Directional Current/Power Monitor with I2C Interface + * Datasheet: http://www.ti.com/product/ina219 + * + * INA226: + * Bi-Directional Current/Power Monitor with I2C Interface + * Datasheet: http://www.ti.com/product/ina226 + * + * Copyright (C) 2012 Lothar Felten <[email protected]> + * Thanks to Jan Volkering + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> + +#include <linux/platform_data/ina2xx.h> + +/* common register definitions */ +#define INA2XX_CONFIG 0x00 +#define INA2XX_SHUNT_VOLTAGE 0x01 /* readonly */ +#define INA2XX_BUS_VOLTAGE 0x02 /* readonly */ +#define INA2XX_POWER 0x03 /* readonly */ +#define INA2XX_CURRENT 0x04 /* readonly */ +#define INA2XX_CALIBRATION 0x05 + +/* INA226 register definitions */ +#define INA226_MASK_ENABLE 0x06 +#define INA226_ALERT_LIMIT 0x07 +#define INA226_DIE_ID 0xFF + + +/* register count */ +#define INA219_REGISTERS 6 +#define INA226_REGISTERS 8 + +#define INA2XX_MAX_REGISTERS 8 + +/* settings - depend on use case */ +#define INA219_CONFIG_DEFAULT 0x399F /* PGA=8 */ +#define INA226_CONFIG_DEFAULT 0x4527 /* averages=16 */ + +/* worst case is 68.10 ms (~14.6Hz, ina219) */ +#define INA2XX_CONVERSION_RATE 15 + +enum ina2xx_ids { ina219, ina226 }; + +struct ina2xx_data { + struct device *hwmon_dev; + + struct mutex update_lock; + bool valid; + unsigned long last_updated; + + int kind; + int registers; + u16 regs[INA2XX_MAX_REGISTERS]; +}; + +int ina2xx_read_word(struct i2c_client *client, int reg) +{ + int val = i2c_smbus_read_word_data(client, reg); + if (unlikely(val < 0)) { + dev_dbg(&client->dev, + "Failed to read register: %d\n", reg); + return val; + } + return be16_to_cpu(val); +} + +void ina2xx_write_word(struct i2c_client *client, int reg, int data) +{ + i2c_smbus_write_word_data(client, reg, cpu_to_be16(data)); +} + +static struct ina2xx_data *ina2xx_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct ina2xx_data *data = i2c_get_clientdata(client); + struct ina2xx_data *ret = data; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + + HZ / INA2XX_CONVERSION_RATE) || !data->valid) { + + int i; + + dev_dbg(&client->dev, "Starting ina2xx update\n"); + + /* Read all registers */ + for (i = 0; i < data->registers; i++) { + int rv = ina2xx_read_word(client, i); + if (rv < 0) { + ret = ERR_PTR(rv); + goto abort; + } + data->regs[i] = rv; + } + data->last_updated = jiffies; + data->valid = 1; + } +abort: + mutex_unlock(&data->update_lock); + return ret; +} + +static int ina219_get_value(struct ina2xx_data *data, u8 reg) +{ + /* + * calculate exact value for the given register + * we assume default power-on reset settings: + * bus voltage range 32V + * gain = /8 + * adc 1 & 2 -> conversion time 532uS + * mode is continuous shunt and bus + * calibration value is INA219_CALIBRATION_VALUE + */ + int val = data->regs[reg]; + + switch (reg) { + case INA2XX_SHUNT_VOLTAGE: + /* LSB=10uV. Convert to mV. */ + val = DIV_ROUND_CLOSEST(val, 100); + break; + case INA2XX_BUS_VOLTAGE: + /* LSB=4mV. Register is not right aligned, convert to mV. */ + val = (val >> 3) * 4; + break; + case INA2XX_POWER: + /* LSB=20mW. Convert to uW */ + val = val * 20 * 1000; + break; + case INA2XX_CURRENT: + /* LSB=1mA (selected). Is in mA */ + break; + default: + /* programmer goofed */ + WARN_ON_ONCE(1); + val = 0; + break; + } + + return val; +} + +static int ina226_get_value(struct ina2xx_data *data, u8 reg) +{ + /* + * calculate exact value for the given register + * we assume default power-on reset settings: + * bus voltage range 32V + * gain = /8 + * adc 1 & 2 -> conversion time 532uS + * mode is continuous shunt and bus + * calibration value is INA226_CALIBRATION_VALUE + */ + int val = data->regs[reg]; + + switch (reg) { + case INA2XX_SHUNT_VOLTAGE: + /* LSB=2.5uV. Convert to mV. */ + val = DIV_ROUND_CLOSEST(val, 400); + break; + case INA2XX_BUS_VOLTAGE: + /* LSB=1.25mV. Convert to mV. */ + val = val + DIV_ROUND_CLOSEST(val, 4); + break; + case INA2XX_POWER: + /* LSB=25mW. Convert to uW */ + val = val * 25 * 1000; + break; + case INA2XX_CURRENT: + /* LSB=1mA (selected). Is in mA */ + break; + default: + /* programmer goofed */ + WARN_ON_ONCE(1); + val = 0; + break; + } + + return val; +} + +static ssize_t ina2xx_show_value(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct ina2xx_data *data = ina2xx_update_device(dev); + int value = 0; + + if (IS_ERR(data)) + return PTR_ERR(data); + + switch (data->kind) { + case ina219: + value = ina219_get_value(data, attr->index); + break; + case ina226: + value = ina226_get_value(data, attr->index); + break; + default: + WARN_ON_ONCE(1); + break; + } + return snprintf(buf, PAGE_SIZE, "%d\n", value); +} + +/* shunt voltage */ +static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, \ + ina2xx_show_value, NULL, INA2XX_SHUNT_VOLTAGE); + +/* bus voltage */ +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, \ + ina2xx_show_value, NULL, INA2XX_BUS_VOLTAGE); + +/* calculated current */ +static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, \ + ina2xx_show_value, NULL, INA2XX_CURRENT); + +/* calculated power */ +static SENSOR_DEVICE_ATTR(power1_input, S_IRUGO, \ + ina2xx_show_value, NULL, INA2XX_POWER); + +/* pointers to created device attributes */ +static struct attribute *ina2xx_attributes[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_curr1_input.dev_attr.attr, + &sensor_dev_attr_power1_input.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ina2xx_group = { + .attrs = ina2xx_attributes, +}; + +static int ina2xx_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct ina2xx_data *data; + struct ina2xx_platform_data *pdata; + int ret = 0; + long shunt = 10000; /* default shunt value 10mOhms */ + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + if (client->dev.platform_data) { + pdata = + (struct ina2xx_platform_data *)client->dev.platform_data; + shunt = pdata->shunt_uohms; + } + + if (shunt <= 0) + return -ENODEV; + + /* set the device type */ + data->kind = id->driver_data; + + switch (data->kind) { + case ina219: + /* device configuration */ + ina2xx_write_word(client, INA2XX_CONFIG, INA219_CONFIG_DEFAULT); + + /* set current LSB to 1mA, shunt is in uOhms */ + /* (equation 13 in datasheet) */ + ina2xx_write_word(client, INA2XX_CALIBRATION, 40960000 / shunt); + dev_info(&client->dev, + "power monitor INA219 (Rshunt = %li uOhm)\n", shunt); + data->registers = INA219_REGISTERS; + break; + case ina226: + /* device configuration */ + ina2xx_write_word(client, INA2XX_CONFIG, INA226_CONFIG_DEFAULT); + + /* set current LSB to 1mA, shunt is in uOhms */ + /* (equation 1 in datasheet)*/ + ina2xx_write_word(client, INA2XX_CALIBRATION, 5120000 / shunt); + dev_info(&client->dev, + "power monitor INA226 (Rshunt = %li uOhm)\n", shunt); + data->registers = INA226_REGISTERS; + break; + default: + /* unknown device id */ + return -ENODEV; + } + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + ret = sysfs_create_group(&client->dev.kobj, &ina2xx_group); + if (ret) + return ret; + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + ret = PTR_ERR(data->hwmon_dev); + goto out_err_hwmon; + } + + return 0; + +out_err_hwmon: + sysfs_remove_group(&client->dev.kobj, &ina2xx_group); + return ret; +} + +static int ina2xx_remove(struct i2c_client *client) +{ + struct ina2xx_data *data = i2c_get_clientdata(client); + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &ina2xx_group); + + return 0; +} + +static const struct i2c_device_id ina2xx_id[] = { + { "ina219", ina219 }, + { "ina226", ina226 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ina2xx_id); + +static struct i2c_driver ina2xx_driver = { + .driver = { + .name = "ina2xx", + }, + .probe = ina2xx_probe, + .remove = ina2xx_remove, + .id_table = ina2xx_id, +}; + +static int __init ina2xx_init(void) +{ + return i2c_add_driver(&ina2xx_driver); +} + +static void __exit ina2xx_exit(void) +{ + i2c_del_driver(&ina2xx_driver); +} + +MODULE_AUTHOR("Lothar Felten <[email protected]>"); +MODULE_DESCRIPTION("ina2xx driver"); +MODULE_LICENSE("GPL"); + +module_init(ina2xx_init); +module_exit(ina2xx_exit); diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c index 0b204e4cf51c..e7701d99f8e8 100644 --- a/drivers/hwmon/it87.c +++ b/drivers/hwmon/it87.c @@ -19,6 +19,8 @@ * IT8726F Super I/O chip w/LPC interface * IT8728F Super I/O chip w/LPC interface * IT8758E Super I/O chip w/LPC interface + * IT8782F Super I/O chip w/LPC interface + * IT8783E/F Super I/O chip w/LPC interface * Sis950 A clone of the IT8705F * * Copyright (C) 2001 Chris Gauthron @@ -59,7 +61,8 @@ #define DRVNAME "it87" -enum chips { it87, it8712, it8716, it8718, it8720, it8721, it8728 }; +enum chips { it87, it8712, it8716, it8718, it8720, it8721, it8728, it8782, + it8783 }; static unsigned short force_id; module_param(force_id, ushort, 0); @@ -137,13 +140,18 @@ static inline void superio_exit(void) #define IT8721F_DEVID 0x8721 #define IT8726F_DEVID 0x8726 #define IT8728F_DEVID 0x8728 +#define IT8782F_DEVID 0x8782 +#define IT8783E_DEVID 0x8783 #define IT87_ACT_REG 0x30 #define IT87_BASE_REG 0x60 /* Logical device 7 registers (IT8712F and later) */ +#define IT87_SIO_GPIO1_REG 0x25 #define IT87_SIO_GPIO3_REG 0x27 #define IT87_SIO_GPIO5_REG 0x29 +#define IT87_SIO_PINX1_REG 0x2a /* Pin selection */ #define IT87_SIO_PINX2_REG 0x2c /* Pin selection */ +#define IT87_SIO_SPI_REG 0xef /* SPI function pin select */ #define IT87_SIO_VID_REG 0xfc /* VID value */ #define IT87_SIO_BEEP_PIN_REG 0xf6 /* Beep pin mapping */ @@ -210,6 +218,7 @@ static const u8 IT87_REG_FANX_MIN[] = { 0x1b, 0x1c, 0x1d, 0x85, 0x87 }; #define IT87_REG_VIN_ENABLE 0x50 #define IT87_REG_TEMP_ENABLE 0x51 +#define IT87_REG_TEMP_EXTRA 0x55 #define IT87_REG_BEEP_ENABLE 0x5c #define IT87_REG_CHIPID 0x58 @@ -226,9 +235,11 @@ struct it87_sio_data { u8 beep_pin; u8 internal; /* Internal sensors can be labeled */ /* Features skipped based on config or DMI */ + u16 skip_in; u8 skip_vid; u8 skip_fan; u8 skip_pwm; + u8 skip_temp; }; /* @@ -253,6 +264,7 @@ struct it87_data { u8 has_fan; /* Bitfield, fans enabled */ u16 fan[5]; /* Register values, possibly combined */ u16 fan_min[5]; /* Register values, possibly combined */ + u8 has_temp; /* Bitfield, temp sensors enabled */ s8 temp[3]; /* Register value */ s8 temp_high[3]; /* Register value */ s8 temp_low[3]; /* Register value */ @@ -304,31 +316,23 @@ static inline int has_newer_autopwm(const struct it87_data *data) || data->type == it8728; } -static u8 in_to_reg(const struct it87_data *data, int nr, long val) +static int adc_lsb(const struct it87_data *data, int nr) { - long lsb; - - if (has_12mv_adc(data)) { - if (data->in_scaled & (1 << nr)) - lsb = 24; - else - lsb = 12; - } else - lsb = 16; + int lsb = has_12mv_adc(data) ? 12 : 16; + if (data->in_scaled & (1 << nr)) + lsb <<= 1; + return lsb; +} - val = DIV_ROUND_CLOSEST(val, lsb); +static u8 in_to_reg(const struct it87_data *data, int nr, long val) +{ + val = DIV_ROUND_CLOSEST(val, adc_lsb(data, nr)); return SENSORS_LIMIT(val, 0, 255); } static int in_from_reg(const struct it87_data *data, int nr, int val) { - if (has_12mv_adc(data)) { - if (data->in_scaled & (1 << nr)) - return val * 24; - else - return val * 12; - } else - return val * 16; + return val * adc_lsb(data, nr); } static inline u8 FAN_TO_REG(long rpm, int div) @@ -407,7 +411,9 @@ static inline int has_16bit_fans(const struct it87_data *data) || data->type == it8718 || data->type == it8720 || data->type == it8721 - || data->type == it8728; + || data->type == it8728 + || data->type == it8782 + || data->type == it8783; } static inline int has_old_autopwm(const struct it87_data *data) @@ -1369,57 +1375,103 @@ static ssize_t show_name(struct device *dev, struct device_attribute } static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); -static struct attribute *it87_attributes[] = { +static struct attribute *it87_attributes_in[9][5] = { +{ &sensor_dev_attr_in0_input.dev_attr.attr, - &sensor_dev_attr_in1_input.dev_attr.attr, - &sensor_dev_attr_in2_input.dev_attr.attr, - &sensor_dev_attr_in3_input.dev_attr.attr, - &sensor_dev_attr_in4_input.dev_attr.attr, - &sensor_dev_attr_in5_input.dev_attr.attr, - &sensor_dev_attr_in6_input.dev_attr.attr, - &sensor_dev_attr_in7_input.dev_attr.attr, - &sensor_dev_attr_in8_input.dev_attr.attr, &sensor_dev_attr_in0_min.dev_attr.attr, - &sensor_dev_attr_in1_min.dev_attr.attr, - &sensor_dev_attr_in2_min.dev_attr.attr, - &sensor_dev_attr_in3_min.dev_attr.attr, - &sensor_dev_attr_in4_min.dev_attr.attr, - &sensor_dev_attr_in5_min.dev_attr.attr, - &sensor_dev_attr_in6_min.dev_attr.attr, - &sensor_dev_attr_in7_min.dev_attr.attr, &sensor_dev_attr_in0_max.dev_attr.attr, - &sensor_dev_attr_in1_max.dev_attr.attr, - &sensor_dev_attr_in2_max.dev_attr.attr, - &sensor_dev_attr_in3_max.dev_attr.attr, - &sensor_dev_attr_in4_max.dev_attr.attr, - &sensor_dev_attr_in5_max.dev_attr.attr, - &sensor_dev_attr_in6_max.dev_attr.attr, - &sensor_dev_attr_in7_max.dev_attr.attr, &sensor_dev_attr_in0_alarm.dev_attr.attr, + NULL +}, { + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, &sensor_dev_attr_in1_alarm.dev_attr.attr, + NULL +}, { + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, &sensor_dev_attr_in2_alarm.dev_attr.attr, + NULL +}, { + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, &sensor_dev_attr_in3_alarm.dev_attr.attr, + NULL +}, { + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, &sensor_dev_attr_in4_alarm.dev_attr.attr, + NULL +}, { + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in5_min.dev_attr.attr, + &sensor_dev_attr_in5_max.dev_attr.attr, &sensor_dev_attr_in5_alarm.dev_attr.attr, + NULL +}, { + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in6_min.dev_attr.attr, + &sensor_dev_attr_in6_max.dev_attr.attr, &sensor_dev_attr_in6_alarm.dev_attr.attr, + NULL +}, { + &sensor_dev_attr_in7_input.dev_attr.attr, + &sensor_dev_attr_in7_min.dev_attr.attr, + &sensor_dev_attr_in7_max.dev_attr.attr, &sensor_dev_attr_in7_alarm.dev_attr.attr, + NULL +}, { + &sensor_dev_attr_in8_input.dev_attr.attr, + NULL +} }; +static const struct attribute_group it87_group_in[9] = { + { .attrs = it87_attributes_in[0] }, + { .attrs = it87_attributes_in[1] }, + { .attrs = it87_attributes_in[2] }, + { .attrs = it87_attributes_in[3] }, + { .attrs = it87_attributes_in[4] }, + { .attrs = it87_attributes_in[5] }, + { .attrs = it87_attributes_in[6] }, + { .attrs = it87_attributes_in[7] }, + { .attrs = it87_attributes_in[8] }, +}; + +static struct attribute *it87_attributes_temp[3][6] = { +{ &sensor_dev_attr_temp1_input.dev_attr.attr, - &sensor_dev_attr_temp2_input.dev_attr.attr, - &sensor_dev_attr_temp3_input.dev_attr.attr, &sensor_dev_attr_temp1_max.dev_attr.attr, - &sensor_dev_attr_temp2_max.dev_attr.attr, - &sensor_dev_attr_temp3_max.dev_attr.attr, &sensor_dev_attr_temp1_min.dev_attr.attr, - &sensor_dev_attr_temp2_min.dev_attr.attr, - &sensor_dev_attr_temp3_min.dev_attr.attr, &sensor_dev_attr_temp1_type.dev_attr.attr, - &sensor_dev_attr_temp2_type.dev_attr.attr, - &sensor_dev_attr_temp3_type.dev_attr.attr, &sensor_dev_attr_temp1_alarm.dev_attr.attr, + NULL +} , { + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp2_type.dev_attr.attr, &sensor_dev_attr_temp2_alarm.dev_attr.attr, + NULL +} , { + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp3_min.dev_attr.attr, + &sensor_dev_attr_temp3_type.dev_attr.attr, &sensor_dev_attr_temp3_alarm.dev_attr.attr, + NULL +} }; + +static const struct attribute_group it87_group_temp[3] = { + { .attrs = it87_attributes_temp[0] }, + { .attrs = it87_attributes_temp[1] }, + { .attrs = it87_attributes_temp[2] }, +}; +static struct attribute *it87_attributes[] = { &dev_attr_alarms.attr, &sensor_dev_attr_intrusion0_alarm.dev_attr.attr, &dev_attr_name.attr, @@ -1430,7 +1482,7 @@ static const struct attribute_group it87_group = { .attrs = it87_attributes, }; -static struct attribute *it87_attributes_beep[] = { +static struct attribute *it87_attributes_in_beep[] = { &sensor_dev_attr_in0_beep.dev_attr.attr, &sensor_dev_attr_in1_beep.dev_attr.attr, &sensor_dev_attr_in2_beep.dev_attr.attr, @@ -1439,15 +1491,13 @@ static struct attribute *it87_attributes_beep[] = { &sensor_dev_attr_in5_beep.dev_attr.attr, &sensor_dev_attr_in6_beep.dev_attr.attr, &sensor_dev_attr_in7_beep.dev_attr.attr, + NULL +}; +static struct attribute *it87_attributes_temp_beep[] = { &sensor_dev_attr_temp1_beep.dev_attr.attr, &sensor_dev_attr_temp2_beep.dev_attr.attr, &sensor_dev_attr_temp3_beep.dev_attr.attr, - NULL -}; - -static const struct attribute_group it87_group_beep = { - .attrs = it87_attributes_beep, }; static struct attribute *it87_attributes_fan16[5][3+1] = { { @@ -1651,6 +1701,12 @@ static int __init it87_find(unsigned short *address, case IT8728F_DEVID: sio_data->type = it8728; break; + case IT8782F_DEVID: + sio_data->type = it8782; + break; + case IT8783E_DEVID: + sio_data->type = it8783; + break; case 0xffff: /* No device at all */ goto exit; default: @@ -1686,16 +1742,86 @@ static int __init it87_find(unsigned short *address, /* The IT8705F has a different LD number for GPIO */ superio_select(5); sio_data->beep_pin = superio_inb(IT87_SIO_BEEP_PIN_REG) & 0x3f; + } else if (sio_data->type == it8783) { + int reg25, reg27, reg2A, reg2C, regEF; + + sio_data->skip_vid = 1; /* No VID */ + + superio_select(GPIO); + + reg25 = superio_inb(IT87_SIO_GPIO1_REG); + reg27 = superio_inb(IT87_SIO_GPIO3_REG); + reg2A = superio_inb(IT87_SIO_PINX1_REG); + reg2C = superio_inb(IT87_SIO_PINX2_REG); + regEF = superio_inb(IT87_SIO_SPI_REG); + + /* Check if fan3 is there or not */ + if ((reg27 & (1 << 0)) || !(reg2C & (1 << 2))) + sio_data->skip_fan |= (1 << 2); + if ((reg25 & (1 << 4)) + || (!(reg2A & (1 << 1)) && (regEF & (1 << 0)))) + sio_data->skip_pwm |= (1 << 2); + + /* Check if fan2 is there or not */ + if (reg27 & (1 << 7)) + sio_data->skip_fan |= (1 << 1); + if (reg27 & (1 << 3)) + sio_data->skip_pwm |= (1 << 1); + + /* VIN5 */ + if ((reg27 & (1 << 0)) || (reg2C & (1 << 2))) + sio_data->skip_in |= (1 << 5); /* No VIN5 */ + + /* VIN6 */ + if (reg27 & (1 << 1)) + sio_data->skip_in |= (1 << 6); /* No VIN6 */ + + /* + * VIN7 + * Does not depend on bit 2 of Reg2C, contrary to datasheet. + */ + if (reg27 & (1 << 2)) { + /* + * The data sheet is a bit unclear regarding the + * internal voltage divider for VCCH5V. It says + * "This bit enables and switches VIN7 (pin 91) to the + * internal voltage divider for VCCH5V". + * This is different to other chips, where the internal + * voltage divider would connect VIN7 to an internal + * voltage source. Maybe that is the case here as well. + * + * Since we don't know for sure, re-route it if that is + * not the case, and ask the user to report if the + * resulting voltage is sane. + */ + if (!(reg2C & (1 << 1))) { + reg2C |= (1 << 1); + superio_outb(IT87_SIO_PINX2_REG, reg2C); + pr_notice("Routing internal VCCH5V to in7.\n"); + } + pr_notice("in7 routed to internal voltage divider, with external pin disabled.\n"); + pr_notice("Please report if it displays a reasonable voltage.\n"); + } + + if (reg2C & (1 << 0)) + sio_data->internal |= (1 << 0); + if (reg2C & (1 << 1)) + sio_data->internal |= (1 << 1); + + sio_data->beep_pin = superio_inb(IT87_SIO_BEEP_PIN_REG) & 0x3f; + } else { int reg; + bool uart6; superio_select(GPIO); reg = superio_inb(IT87_SIO_GPIO3_REG); - if (sio_data->type == it8721 || sio_data->type == it8728) { + if (sio_data->type == it8721 || sio_data->type == it8728 || + sio_data->type == it8782) { /* - * The IT8721F/IT8758E doesn't have VID pins at all, - * not sure about the IT8728F. + * IT8721F/IT8758E, and IT8782F don't have VID pins + * at all, not sure about the IT8728F. */ sio_data->skip_vid = 1; } else { @@ -1724,6 +1850,9 @@ static int __init it87_find(unsigned short *address, sio_data->vid_value = superio_inb(IT87_SIO_VID_REG); reg = superio_inb(IT87_SIO_PINX2_REG); + + uart6 = sio_data->type == it8782 && (reg & (1 << 2)); + /* * The IT8720F has no VIN7 pin, so VCCH should always be * routed internally to VIN7 with an internal divider. @@ -1733,8 +1862,12 @@ static int __init it87_find(unsigned short *address, * configured, even though the IT8720F datasheet claims * that the internal routing of VCCH to VIN7 is the default * setting. So we force the internal routing in this case. + * + * On IT8782F, VIN7 is multiplexed with one of the UART6 pins. + * If UART6 is enabled, re-route VIN7 to the internal divider + * if that is not already the case. */ - if (sio_data->type == it8720 && !(reg & (1 << 1))) { + if ((sio_data->type == it8720 || uart6) && !(reg & (1 << 1))) { reg |= (1 << 1); superio_outb(IT87_SIO_PINX2_REG, reg); pr_notice("Routing internal VCCH to in7\n"); @@ -1745,6 +1878,20 @@ static int __init it87_find(unsigned short *address, sio_data->type == it8728) sio_data->internal |= (1 << 1); + /* + * On IT8782F, UART6 pins overlap with VIN5, VIN6, and VIN7. + * While VIN7 can be routed to the internal voltage divider, + * VIN5 and VIN6 are not available if UART6 is enabled. + * + * Also, temp3 is not available if UART6 is enabled and TEMPIN3 + * is the temperature source. Since we can not read the + * temperature source here, skip_temp is preliminary. + */ + if (uart6) { + sio_data->skip_in |= (1 << 5) | (1 << 6); + sio_data->skip_temp |= (1 << 2); + } + sio_data->beep_pin = superio_inb(IT87_SIO_BEEP_PIN_REG) & 0x3f; } if (sio_data->beep_pin) @@ -1782,8 +1929,22 @@ static void it87_remove_files(struct device *dev) int i; sysfs_remove_group(&dev->kobj, &it87_group); - if (sio_data->beep_pin) - sysfs_remove_group(&dev->kobj, &it87_group_beep); + for (i = 0; i < 9; i++) { + if (sio_data->skip_in & (1 << i)) + continue; + sysfs_remove_group(&dev->kobj, &it87_group_in[i]); + if (it87_attributes_in_beep[i]) + sysfs_remove_file(&dev->kobj, + it87_attributes_in_beep[i]); + } + for (i = 0; i < 3; i++) { + if (!(data->has_temp & (1 << i))) + continue; + sysfs_remove_group(&dev->kobj, &it87_group_temp[i]); + if (sio_data->beep_pin) + sysfs_remove_file(&dev->kobj, + it87_attributes_temp_beep[i]); + } for (i = 0; i < 5; i++) { if (!(data->has_fan & (1 << i))) continue; @@ -1823,22 +1984,22 @@ static int __devinit it87_probe(struct platform_device *pdev) "it8720", "it8721", "it8728", + "it8782", + "it8783", }; res = platform_get_resource(pdev, IORESOURCE_IO, 0); - if (!request_region(res->start, IT87_EC_EXTENT, DRVNAME)) { + if (!devm_request_region(&pdev->dev, res->start, IT87_EC_EXTENT, + DRVNAME)) { dev_err(dev, "Failed to request region 0x%lx-0x%lx\n", (unsigned long)res->start, (unsigned long)(res->start + IT87_EC_EXTENT - 1)); - err = -EBUSY; - goto ERROR0; + return -EBUSY; } - data = kzalloc(sizeof(struct it87_data), GFP_KERNEL); - if (!data) { - err = -ENOMEM; - goto ERROR1; - } + data = devm_kzalloc(&pdev->dev, sizeof(struct it87_data), GFP_KERNEL); + if (!data) + return -ENOMEM; data->addr = res->start; data->type = sio_data->type; @@ -1847,10 +2008,8 @@ static int __devinit it87_probe(struct platform_device *pdev) /* Now, we do the remaining detection. */ if ((it87_read_value(data, IT87_REG_CONFIG) & 0x80) - || it87_read_value(data, IT87_REG_CHIPID) != 0x90) { - err = -ENODEV; - goto ERROR2; - } + || it87_read_value(data, IT87_REG_CHIPID) != 0x90) + return -ENODEV; platform_set_drvdata(pdev, data); @@ -1867,6 +2026,18 @@ static int __devinit it87_probe(struct platform_device *pdev) data->in_scaled |= (1 << 7); /* in7 is VSB */ if (sio_data->internal & (1 << 2)) data->in_scaled |= (1 << 8); /* in8 is Vbat */ + } else if (sio_data->type == it8782 || sio_data->type == it8783) { + if (sio_data->internal & (1 << 0)) + data->in_scaled |= (1 << 3); /* in3 is VCC5V */ + if (sio_data->internal & (1 << 1)) + data->in_scaled |= (1 << 7); /* in7 is VCCH5V */ + } + + data->has_temp = 0x07; + if (sio_data->skip_temp & (1 << 2)) { + if (sio_data->type == it8782 + && !(it87_read_value(data, IT87_REG_TEMP_EXTRA) & 0x80)) + data->has_temp &= ~(1 << 2); } /* Initialize the IT87 chip */ @@ -1875,12 +2046,34 @@ static int __devinit it87_probe(struct platform_device *pdev) /* Register sysfs hooks */ err = sysfs_create_group(&dev->kobj, &it87_group); if (err) - goto ERROR2; + return err; - if (sio_data->beep_pin) { - err = sysfs_create_group(&dev->kobj, &it87_group_beep); + for (i = 0; i < 9; i++) { + if (sio_data->skip_in & (1 << i)) + continue; + err = sysfs_create_group(&dev->kobj, &it87_group_in[i]); if (err) - goto ERROR4; + goto error; + if (sio_data->beep_pin && it87_attributes_in_beep[i]) { + err = sysfs_create_file(&dev->kobj, + it87_attributes_in_beep[i]); + if (err) + goto error; + } + } + + for (i = 0; i < 3; i++) { + if (!(data->has_temp & (1 << i))) + continue; + err = sysfs_create_group(&dev->kobj, &it87_group_temp[i]); + if (err) + goto error; + if (sio_data->beep_pin) { + err = sysfs_create_file(&dev->kobj, + it87_attributes_temp_beep[i]); + if (err) + goto error; + } } /* Do not create fan files for disabled fans */ @@ -1891,13 +2084,13 @@ static int __devinit it87_probe(struct platform_device *pdev) continue; err = sysfs_create_group(&dev->kobj, &fan_group[i]); if (err) - goto ERROR4; + goto error; if (sio_data->beep_pin) { err = sysfs_create_file(&dev->kobj, it87_attributes_fan_beep[i]); if (err) - goto ERROR4; + goto error; if (!fan_beep_need_rw) continue; @@ -1922,14 +2115,14 @@ static int __devinit it87_probe(struct platform_device *pdev) err = sysfs_create_group(&dev->kobj, &it87_group_pwm[i]); if (err) - goto ERROR4; + goto error; if (!has_old_autopwm(data)) continue; err = sysfs_create_group(&dev->kobj, &it87_group_autopwm[i]); if (err) - goto ERROR4; + goto error; } } @@ -1939,7 +2132,7 @@ static int __devinit it87_probe(struct platform_device *pdev) data->vid = sio_data->vid_value; err = sysfs_create_group(&dev->kobj, &it87_group_vid); if (err) - goto ERROR4; + goto error; } /* Export labels for internal sensors */ @@ -1949,25 +2142,19 @@ static int __devinit it87_probe(struct platform_device *pdev) err = sysfs_create_file(&dev->kobj, it87_attributes_label[i]); if (err) - goto ERROR4; + goto error; } data->hwmon_dev = hwmon_device_register(dev); if (IS_ERR(data->hwmon_dev)) { err = PTR_ERR(data->hwmon_dev); - goto ERROR4; + goto error; } return 0; -ERROR4: +error: it87_remove_files(dev); -ERROR2: - platform_set_drvdata(pdev, NULL); - kfree(data); -ERROR1: - release_region(res->start, IT87_EC_EXTENT); -ERROR0: return err; } @@ -1978,10 +2165,6 @@ static int __devexit it87_remove(struct platform_device *pdev) hwmon_device_unregister(data->hwmon_dev); it87_remove_files(&pdev->dev); - release_region(data->addr, IT87_EC_EXTENT); - platform_set_drvdata(pdev, NULL); - kfree(data); - return 0; } @@ -2143,8 +2326,9 @@ static void __devinit it87_init_device(struct platform_device *pdev) it87_write_value(data, IT87_REG_FAN_16BIT, tmp | 0x07); } - /* IT8705F only supports three fans. */ - if (data->type != it87) { + /* IT8705F, IT8782F, and IT8783E/F only support three fans. */ + if (data->type != it87 && data->type != it8782 && + data->type != it8783) { if (tmp & (1 << 4)) data->has_fan |= (1 << 3); /* fan4 enabled */ if (tmp & (1 << 5)) @@ -2233,6 +2417,8 @@ static struct it87_data *it87_update_device(struct device *dev) } } for (i = 0; i < 3; i++) { + if (!(data->has_temp & (1 << i))) + continue; data->temp[i] = it87_read_value(data, IT87_REG_TEMP(i)); data->temp_high[i] = diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c index 307bb325dde9..7356b5ec8f67 100644 --- a/drivers/hwmon/k10temp.c +++ b/drivers/hwmon/k10temp.c @@ -225,15 +225,4 @@ static struct pci_driver k10temp_driver = { .remove = __devexit_p(k10temp_remove), }; -static int __init k10temp_init(void) -{ - return pci_register_driver(&k10temp_driver); -} - -static void __exit k10temp_exit(void) -{ - pci_unregister_driver(&k10temp_driver); -} - -module_init(k10temp_init) -module_exit(k10temp_exit) +module_pci_driver(k10temp_driver); diff --git a/drivers/hwmon/k8temp.c b/drivers/hwmon/k8temp.c index 575101988751..35aac82ee8eb 100644 --- a/drivers/hwmon/k8temp.c +++ b/drivers/hwmon/k8temp.c @@ -339,19 +339,8 @@ static struct pci_driver k8temp_driver = { .remove = __devexit_p(k8temp_remove), }; -static int __init k8temp_init(void) -{ - return pci_register_driver(&k8temp_driver); -} - -static void __exit k8temp_exit(void) -{ - pci_unregister_driver(&k8temp_driver); -} +module_pci_driver(k8temp_driver); MODULE_AUTHOR("Rudolf Marek <[email protected]>"); MODULE_DESCRIPTION("AMD K8 core temperature monitor"); MODULE_LICENSE("GPL"); - -module_init(k8temp_init) -module_exit(k8temp_exit) diff --git a/drivers/hwmon/ntc_thermistor.c b/drivers/hwmon/ntc_thermistor.c index 9b382ec2c3bd..6da9696e1827 100644 --- a/drivers/hwmon/ntc_thermistor.c +++ b/drivers/hwmon/ntc_thermistor.c @@ -134,8 +134,7 @@ static inline u64 div64_u64_safe(u64 dividend, u64 divisor) return div64_u64(dividend, divisor); } -static unsigned int get_ohm_of_thermistor(struct ntc_data *data, - unsigned int uV) +static int get_ohm_of_thermistor(struct ntc_data *data, unsigned int uV) { struct ntc_thermistor_platform_data *pdata = data->pdata; u64 mV = uV / 1000; @@ -146,12 +145,12 @@ static unsigned int get_ohm_of_thermistor(struct ntc_data *data, if (mV == 0) { if (pdata->connect == NTC_CONNECTED_POSITIVE) - return UINT_MAX; + return INT_MAX; return 0; } if (mV >= pmV) return (pdata->connect == NTC_CONNECTED_POSITIVE) ? - 0 : UINT_MAX; + 0 : INT_MAX; if (pdata->connect == NTC_CONNECTED_POSITIVE && puO == 0) N = div64_u64_safe(pdO * (pmV - mV), mV); @@ -163,113 +162,109 @@ static unsigned int get_ohm_of_thermistor(struct ntc_data *data, else N = div64_u64_safe(pdO * puO * mV, pdO * (pmV - mV) - puO * mV); - return (unsigned int) N; + if (N > INT_MAX) + N = INT_MAX; + return N; } -static int lookup_comp(struct ntc_data *data, - unsigned int ohm, int *i_low, int *i_high) +static void lookup_comp(struct ntc_data *data, unsigned int ohm, + int *i_low, int *i_high) { - int start, end, mid = -1; + int start, end, mid; + + /* + * Handle special cases: Resistance is higher than or equal to + * resistance in first table entry, or resistance is lower or equal + * to resistance in last table entry. + * In these cases, return i_low == i_high, either pointing to the + * beginning or to the end of the table depending on the condition. + */ + if (ohm >= data->comp[0].ohm) { + *i_low = 0; + *i_high = 0; + return; + } + if (ohm <= data->comp[data->n_comp - 1].ohm) { + *i_low = data->n_comp - 1; + *i_high = data->n_comp - 1; + return; + } /* Do a binary search on compensation table */ start = 0; end = data->n_comp; - - while (end > start) { + while (start < end) { mid = start + (end - start) / 2; - if (data->comp[mid].ohm < ohm) + /* + * start <= mid < end + * data->comp[start].ohm > ohm >= data->comp[end].ohm + * + * We could check for "ohm == data->comp[mid].ohm" here, but + * that is a quite unlikely condition, and we would have to + * check again after updating start. Check it at the end instead + * for simplicity. + */ + if (ohm >= data->comp[mid].ohm) { end = mid; - else if (data->comp[mid].ohm > ohm) - start = mid + 1; - else - break; - } - - if (mid == 0) { - if (data->comp[mid].ohm > ohm) { - *i_high = mid; - *i_low = mid + 1; - return 0; - } else { - *i_low = mid; - *i_high = -1; - return -EINVAL; - } - } - if (mid == (data->n_comp - 1)) { - if (data->comp[mid].ohm <= ohm) { - *i_low = mid; - *i_high = mid - 1; - return 0; } else { - *i_low = -1; - *i_high = mid; - return -EINVAL; + start = mid + 1; + /* + * ohm >= data->comp[start].ohm might be true here, + * since we set start to mid + 1. In that case, we are + * done. We could keep going, but the condition is quite + * likely to occur, so it is worth checking for it. + */ + if (ohm >= data->comp[start].ohm) + end = start; } + /* + * start <= end + * data->comp[start].ohm >= ohm >= data->comp[end].ohm + */ } - - if (data->comp[mid].ohm <= ohm) { - *i_low = mid; - *i_high = mid - 1; - } else { - *i_low = mid + 1; - *i_high = mid; - } - - return 0; + /* + * start == end + * ohm >= data->comp[end].ohm + */ + *i_low = end; + if (ohm == data->comp[end].ohm) + *i_high = end; + else + *i_high = end - 1; } -static int get_temp_mC(struct ntc_data *data, unsigned int ohm, int *temp) +static int get_temp_mC(struct ntc_data *data, unsigned int ohm) { int low, high; - int ret; + int temp; - ret = lookup_comp(data, ohm, &low, &high); - if (ret) { + lookup_comp(data, ohm, &low, &high); + if (low == high) { /* Unable to use linear approximation */ - if (low != -1) - *temp = data->comp[low].temp_C * 1000; - else if (high != -1) - *temp = data->comp[high].temp_C * 1000; - else - return ret; + temp = data->comp[low].temp_C * 1000; } else { - *temp = data->comp[low].temp_C * 1000 + + temp = data->comp[low].temp_C * 1000 + ((data->comp[high].temp_C - data->comp[low].temp_C) * 1000 * ((int)ohm - (int)data->comp[low].ohm)) / ((int)data->comp[high].ohm - (int)data->comp[low].ohm); } - - return 0; + return temp; } -static int ntc_thermistor_read(struct ntc_data *data, int *temp) +static int ntc_thermistor_get_ohm(struct ntc_data *data) { - int ret; - int read_ohm, read_uV; - unsigned int ohm = 0; - - if (data->pdata->read_ohm) { - read_ohm = data->pdata->read_ohm(); - if (read_ohm < 0) - return read_ohm; - ohm = (unsigned int)read_ohm; - } + int read_uV; + + if (data->pdata->read_ohm) + return data->pdata->read_ohm(); if (data->pdata->read_uV) { read_uV = data->pdata->read_uV(); if (read_uV < 0) return read_uV; - ohm = get_ohm_of_thermistor(data, (unsigned int)read_uV); - } - - ret = get_temp_mC(data, ohm, temp); - if (ret) { - dev_dbg(data->dev, "Sensor reading function not available.\n"); - return ret; + return get_ohm_of_thermistor(data, read_uV); } - - return 0; + return -EINVAL; } static ssize_t ntc_show_name(struct device *dev, @@ -290,12 +285,13 @@ static ssize_t ntc_show_temp(struct device *dev, struct device_attribute *attr, char *buf) { struct ntc_data *data = dev_get_drvdata(dev); - int temp, ret; + int ohm; - ret = ntc_thermistor_read(data, &temp); - if (ret) - return ret; - return sprintf(buf, "%d\n", temp); + ohm = ntc_thermistor_get_ohm(data); + if (ohm < 0) + return ohm; + + return sprintf(buf, "%d\n", get_temp_mC(data, ohm)); } static SENSOR_DEVICE_ATTR(temp1_type, S_IRUGO, ntc_show_type, NULL, 0); @@ -326,14 +322,14 @@ static int __devinit ntc_thermistor_probe(struct platform_device *pdev) /* Either one of the two is required. */ if (!pdata->read_uV && !pdata->read_ohm) { - dev_err(&pdev->dev, "Both read_uV and read_ohm missing." - "Need either one of the two.\n"); + dev_err(&pdev->dev, + "Both read_uV and read_ohm missing. Need either one of the two.\n"); return -EINVAL; } if (pdata->read_uV && pdata->read_ohm) { - dev_warn(&pdev->dev, "Only one of read_uV and read_ohm " - "is needed; ignoring read_uV.\n"); + dev_warn(&pdev->dev, + "Only one of read_uV and read_ohm is needed; ignoring read_uV.\n"); pdata->read_uV = NULL; } @@ -344,12 +340,12 @@ static int __devinit ntc_thermistor_probe(struct platform_device *pdev) NTC_CONNECTED_POSITIVE) || (pdata->connect != NTC_CONNECTED_POSITIVE && pdata->connect != NTC_CONNECTED_GROUND))) { - dev_err(&pdev->dev, "Required data to use read_uV not " - "supplied.\n"); + dev_err(&pdev->dev, + "Required data to use read_uV not supplied.\n"); return -EINVAL; } - data = kzalloc(sizeof(struct ntc_data), GFP_KERNEL); + data = devm_kzalloc(&pdev->dev, sizeof(struct ntc_data), GFP_KERNEL); if (!data) return -ENOMEM; @@ -370,8 +366,7 @@ static int __devinit ntc_thermistor_probe(struct platform_device *pdev) dev_err(&pdev->dev, "Unknown device type: %lu(%s)\n", pdev->id_entry->driver_data, pdev->id_entry->name); - ret = -EINVAL; - goto err; + return -EINVAL; } platform_set_drvdata(pdev, data); @@ -379,13 +374,13 @@ static int __devinit ntc_thermistor_probe(struct platform_device *pdev) ret = sysfs_create_group(&data->dev->kobj, &ntc_attr_group); if (ret) { dev_err(data->dev, "unable to create sysfs files\n"); - goto err; + return ret; } data->hwmon_dev = hwmon_device_register(data->dev); - if (IS_ERR_OR_NULL(data->hwmon_dev)) { + if (IS_ERR(data->hwmon_dev)) { dev_err(data->dev, "unable to register as hwmon device.\n"); - ret = -EINVAL; + ret = PTR_ERR(data->hwmon_dev); goto err_after_sysfs; } @@ -395,8 +390,6 @@ static int __devinit ntc_thermistor_probe(struct platform_device *pdev) return 0; err_after_sysfs: sysfs_remove_group(&data->dev->kobj, &ntc_attr_group); -err: - kfree(data); return ret; } @@ -408,8 +401,6 @@ static int __devexit ntc_thermistor_remove(struct platform_device *pdev) sysfs_remove_group(&data->dev->kobj, &ntc_attr_group); platform_set_drvdata(pdev, NULL); - kfree(data); - return 0; } diff --git a/drivers/i2c/busses/i2c-powermac.c b/drivers/i2c/busses/i2c-powermac.c index 7b397c6f607e..31c47e18d83c 100644 --- a/drivers/i2c/busses/i2c-powermac.c +++ b/drivers/i2c/busses/i2c-powermac.c @@ -227,6 +227,72 @@ static int __devexit i2c_powermac_remove(struct platform_device *dev) return 0; } +static void __devinit i2c_powermac_register_devices(struct i2c_adapter *adap, + struct pmac_i2c_bus *bus) +{ + struct i2c_client *newdev; + struct device_node *node; + + for_each_child_of_node(adap->dev.of_node, node) { + struct i2c_board_info info = {}; + struct dev_archdata dev_ad = {}; + const __be32 *reg; + char tmp[16]; + u32 addr; + int len; + + /* Get address & channel */ + reg = of_get_property(node, "reg", &len); + if (!reg || (len < sizeof(int))) { + dev_err(&adap->dev, "i2c-powermac: invalid reg on %s\n", + node->full_name); + continue; + } + addr = be32_to_cpup(reg); + + /* Multibus setup, check channel */ + if (!pmac_i2c_match_adapter(node, adap)) + continue; + + dev_dbg(&adap->dev, "i2c-powermac: register %s\n", + node->full_name); + + /* Make up a modalias. Note: we to _NOT_ want the standard + * i2c drivers to match with any of our powermac stuff + * unless they have been specifically modified to handle + * it on a case by case basis. For example, for thermal + * control, things like lm75 etc... shall match with their + * corresponding windfarm drivers, _NOT_ the generic ones, + * so we force a prefix of AAPL, onto the modalias to + * make that happen + */ + if (of_modalias_node(node, tmp, sizeof(tmp)) < 0) { + dev_err(&adap->dev, "i2c-powermac: modalias failure" + " on %s\n", node->full_name); + continue; + } + snprintf(info.type, sizeof(info.type), "MAC,%s", tmp); + + /* Fill out the rest of the info structure */ + info.addr = (addr & 0xff) >> 1; + info.irq = irq_of_parse_and_map(node, 0); + info.of_node = of_node_get(node); + info.archdata = &dev_ad; + + newdev = i2c_new_device(adap, &info); + if (!newdev) { + dev_err(&adap->dev, "i2c-powermac: Failure to register" + " %s\n", node->full_name); + of_node_put(node); + /* We do not dispose of the interrupt mapping on + * purpose. It's not necessary (interrupt cannot be + * re-used) and somebody else might have grabbed it + * via direct DT lookup so let's not bother + */ + continue; + } + } +} static int __devinit i2c_powermac_probe(struct platform_device *dev) { @@ -272,6 +338,7 @@ static int __devinit i2c_powermac_probe(struct platform_device *dev) adapter->algo = &i2c_powermac_algorithm; i2c_set_adapdata(adapter, bus); adapter->dev.parent = &dev->dev; + adapter->dev.of_node = dev->dev.of_node; rc = i2c_add_adapter(adapter); if (rc) { printk(KERN_ERR "i2c-powermac: Adapter %s registration " @@ -281,33 +348,10 @@ static int __devinit i2c_powermac_probe(struct platform_device *dev) printk(KERN_INFO "PowerMac i2c bus %s registered\n", adapter->name); - if (!strncmp(basename, "uni-n", 5)) { - struct device_node *np; - const u32 *prop; - struct i2c_board_info info; - - /* Instantiate I2C motion sensor if present */ - np = of_find_node_by_name(NULL, "accelerometer"); - if (np && of_device_is_compatible(np, "AAPL,accelerometer_1") && - (prop = of_get_property(np, "reg", NULL))) { - int i2c_bus; - const char *tmp_bus; - - /* look for bus either using "reg" or by path */ - tmp_bus = strstr(np->full_name, "/i2c-bus@"); - if (tmp_bus) - i2c_bus = *(tmp_bus + 9) - '0'; - else - i2c_bus = ((*prop) >> 8) & 0x0f; - - if (pmac_i2c_get_channel(bus) == i2c_bus) { - memset(&info, 0, sizeof(struct i2c_board_info)); - info.addr = ((*prop) & 0xff) >> 1; - strlcpy(info.type, "ams", I2C_NAME_SIZE); - i2c_new_device(adapter, &info); - } - } - } + /* Cannot use of_i2c_register_devices() due to Apple device-tree + * funkyness + */ + i2c_powermac_register_devices(adapter, bus); return rc; } diff --git a/drivers/macintosh/Kconfig b/drivers/macintosh/Kconfig index fa51af11c6f1..a555da64224e 100644 --- a/drivers/macintosh/Kconfig +++ b/drivers/macintosh/Kconfig @@ -204,11 +204,14 @@ config THERM_ADT746X better fan behaviour by default, and some manual control. config THERM_PM72 - tristate "Support for thermal management on PowerMac G5" + tristate "Support for thermal management on PowerMac G5 (AGP)" depends on I2C && I2C_POWERMAC && PPC_PMAC64 + default n help This driver provides thermostat and fan control for the desktop - G5 machines. + G5 machines. + + This is deprecated, use windfarm instead. config WINDFARM tristate "New PowerMac thermal control infrastructure" @@ -221,6 +224,22 @@ config WINDFARM_PM81 help This driver provides thermal control for the iMacG5 +config WINDFARM_PM72 + tristate "Support for thermal management on PowerMac G5 (AGP)" + depends on WINDFARM && I2C && CPU_FREQ_PMAC64 && ADB_PMU + select I2C_POWERMAC + help + This driver provides thermal control for the PowerMac G5 + "AGP" variants (PowerMac 7,2 and 7,3) + +config WINDFARM_RM31 + tristate "Support for thermal management on Xserve G5" + depends on WINDFARM && I2C && CPU_FREQ_PMAC64 && ADB_PMU + select I2C_POWERMAC + help + This driver provides thermal control for the Xserve G5 + (RackMac3,1) + config WINDFARM_PM91 tristate "Support for thermal management on PowerMac9,1" depends on WINDFARM && I2C && CPU_FREQ_PMAC64 && PMAC_SMU diff --git a/drivers/macintosh/Makefile b/drivers/macintosh/Makefile index 6652a6ebb6fa..6753b65f8ede 100644 --- a/drivers/macintosh/Makefile +++ b/drivers/macintosh/Makefile @@ -29,6 +29,20 @@ obj-$(CONFIG_THERM_PM72) += therm_pm72.o obj-$(CONFIG_THERM_WINDTUNNEL) += therm_windtunnel.o obj-$(CONFIG_THERM_ADT746X) += therm_adt746x.o obj-$(CONFIG_WINDFARM) += windfarm_core.o +obj-$(CONFIG_WINDFARM_PM72) += windfarm_fcu_controls.o \ + windfarm_ad7417_sensor.o \ + windfarm_lm75_sensor.o \ + windfarm_max6690_sensor.o \ + windfarm_pid.o \ + windfarm_cpufreq_clamp.o \ + windfarm_pm72.o +obj-$(CONFIG_WINDFARM_RM31) += windfarm_fcu_controls.o \ + windfarm_ad7417_sensor.o \ + windfarm_lm75_sensor.o \ + windfarm_lm87_sensor.o \ + windfarm_pid.o \ + windfarm_cpufreq_clamp.o \ + windfarm_rm31.o obj-$(CONFIG_WINDFARM_PM81) += windfarm_smu_controls.o \ windfarm_smu_sensors.o \ windfarm_lm75_sensor.o windfarm_pid.o \ diff --git a/drivers/macintosh/ams/ams-i2c.c b/drivers/macintosh/ams/ams-i2c.c index abeecd27b484..978eda8d6678 100644 --- a/drivers/macintosh/ams/ams-i2c.c +++ b/drivers/macintosh/ams/ams-i2c.c @@ -65,7 +65,7 @@ static int ams_i2c_probe(struct i2c_client *client, static int ams_i2c_remove(struct i2c_client *client); static const struct i2c_device_id ams_id[] = { - { "ams", 0 }, + { "MAC,accelerometer_1", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, ams_id); diff --git a/drivers/macintosh/therm_adt746x.c b/drivers/macintosh/therm_adt746x.c index fc71723cbc48..f433521a6f9d 100644 --- a/drivers/macintosh/therm_adt746x.c +++ b/drivers/macintosh/therm_adt746x.c @@ -47,7 +47,7 @@ static u8 FAN_SPD_SET[2] = {0x30, 0x31}; static u8 default_limits_local[3] = {70, 50, 70}; /* local, sensor1, sensor2 */ static u8 default_limits_chip[3] = {80, 65, 80}; /* local, sensor1, sensor2 */ -static const char *sensor_location[3]; +static const char *sensor_location[3] = { "?", "?", "?" }; static int limit_adjust; static int fan_speed = -1; @@ -79,18 +79,16 @@ struct thermostat { int last_speed[2]; int last_var[2]; int pwm_inv[2]; + struct task_struct *thread; + struct platform_device *pdev; + enum { + ADT7460, + ADT7467 + } type; }; -static enum {ADT7460, ADT7467} therm_type; -static int therm_bus, therm_address; -static struct platform_device * of_dev; -static struct thermostat* thermostat; -static struct task_struct *thread_therm = NULL; - static void write_both_fan_speed(struct thermostat *th, int speed); static void write_fan_speed(struct thermostat *th, int speed, int fan); -static void thermostat_create_files(void); -static void thermostat_remove_files(void); static int write_reg(struct thermostat* th, int reg, u8 data) @@ -126,66 +124,6 @@ read_reg(struct thermostat* th, int reg) return data; } -static struct i2c_driver thermostat_driver; - -static int -attach_thermostat(struct i2c_adapter *adapter) -{ - unsigned long bus_no; - struct i2c_board_info info; - struct i2c_client *client; - - if (strncmp(adapter->name, "uni-n", 5)) - return -ENODEV; - bus_no = simple_strtoul(adapter->name + 6, NULL, 10); - if (bus_no != therm_bus) - return -ENODEV; - - memset(&info, 0, sizeof(struct i2c_board_info)); - strlcpy(info.type, "therm_adt746x", I2C_NAME_SIZE); - info.addr = therm_address; - client = i2c_new_device(adapter, &info); - if (!client) - return -ENODEV; - - /* - * Let i2c-core delete that device on driver removal. - * This is safe because i2c-core holds the core_lock mutex for us. - */ - list_add_tail(&client->detected, &thermostat_driver.clients); - return 0; -} - -static int -remove_thermostat(struct i2c_client *client) -{ - struct thermostat *th = i2c_get_clientdata(client); - int i; - - thermostat_remove_files(); - - if (thread_therm != NULL) { - kthread_stop(thread_therm); - } - - printk(KERN_INFO "adt746x: Putting max temperatures back from " - "%d, %d, %d to %d, %d, %d\n", - th->limits[0], th->limits[1], th->limits[2], - th->initial_limits[0], th->initial_limits[1], - th->initial_limits[2]); - - for (i = 0; i < 3; i++) - write_reg(th, LIMIT_REG[i], th->initial_limits[i]); - - write_both_fan_speed(th, -1); - - thermostat = NULL; - - kfree(th); - - return 0; -} - static int read_fan_speed(struct thermostat *th, u8 addr) { u8 tmp[2]; @@ -203,7 +141,7 @@ static int read_fan_speed(struct thermostat *th, u8 addr) static void write_both_fan_speed(struct thermostat *th, int speed) { write_fan_speed(th, speed, 0); - if (therm_type == ADT7460) + if (th->type == ADT7460) write_fan_speed(th, speed, 1); } @@ -216,7 +154,7 @@ static void write_fan_speed(struct thermostat *th, int speed, int fan) else if (speed < -1) speed = 0; - if (therm_type == ADT7467 && fan == 1) + if (th->type == ADT7467 && fan == 1) return; if (th->last_speed[fan] != speed) { @@ -239,7 +177,7 @@ static void write_fan_speed(struct thermostat *th, int speed, int fan) write_reg(th, FAN_SPD_SET[fan], speed); } else { /* back to automatic */ - if(therm_type == ADT7460) { + if(th->type == ADT7460) { manual = read_reg(th, MANUAL_MODE[fan]) & (~MANUAL_MASK); manual &= ~INVERT_MASK; @@ -293,7 +231,7 @@ static void update_fans_speed (struct thermostat *th) /* we don't care about local sensor, so we start at sensor 1 */ for (i = 1; i < 3; i++) { int started = 0; - int fan_number = (therm_type == ADT7460 && i == 2); + int fan_number = (th->type == ADT7460 && i == 2); int var = th->temps[i] - th->limits[i]; if (var > -1) { @@ -370,116 +308,22 @@ static int monitor_task(void *arg) static void set_limit(struct thermostat *th, int i) { - /* Set sensor1 limit higher to avoid powerdowns */ - th->limits[i] = default_limits_chip[i] + limit_adjust; - write_reg(th, LIMIT_REG[i], th->limits[i]); + /* Set sensor1 limit higher to avoid powerdowns */ + th->limits[i] = default_limits_chip[i] + limit_adjust; + write_reg(th, LIMIT_REG[i], th->limits[i]); - /* set our limits to normal */ - th->limits[i] = default_limits_local[i] + limit_adjust; + /* set our limits to normal */ + th->limits[i] = default_limits_local[i] + limit_adjust; } -static int probe_thermostat(struct i2c_client *client, - const struct i2c_device_id *id) -{ - struct thermostat* th; - int rc; - int i; - - if (thermostat) - return 0; - - th = kzalloc(sizeof(struct thermostat), GFP_KERNEL); - if (!th) - return -ENOMEM; - - i2c_set_clientdata(client, th); - th->clt = client; - - rc = read_reg(th, CONFIG_REG); - if (rc < 0) { - dev_err(&client->dev, "Thermostat failed to read config!\n"); - kfree(th); - return -ENODEV; - } - - /* force manual control to start the fan quieter */ - if (fan_speed == -1) - fan_speed = 64; - - if(therm_type == ADT7460) { - printk(KERN_INFO "adt746x: ADT7460 initializing\n"); - /* The 7460 needs to be started explicitly */ - write_reg(th, CONFIG_REG, 1); - } else - printk(KERN_INFO "adt746x: ADT7467 initializing\n"); - - for (i = 0; i < 3; i++) { - th->initial_limits[i] = read_reg(th, LIMIT_REG[i]); - set_limit(th, i); - } - - printk(KERN_INFO "adt746x: Lowering max temperatures from %d, %d, %d" - " to %d, %d, %d\n", - th->initial_limits[0], th->initial_limits[1], - th->initial_limits[2], th->limits[0], th->limits[1], - th->limits[2]); - - thermostat = th; - - /* record invert bit status because fw can corrupt it after suspend */ - th->pwm_inv[0] = read_reg(th, MANUAL_MODE[0]) & INVERT_MASK; - th->pwm_inv[1] = read_reg(th, MANUAL_MODE[1]) & INVERT_MASK; - - /* be sure to really write fan speed the first time */ - th->last_speed[0] = -2; - th->last_speed[1] = -2; - th->last_var[0] = -80; - th->last_var[1] = -80; - - if (fan_speed != -1) { - /* manual mode, stop fans */ - write_both_fan_speed(th, 0); - } else { - /* automatic mode */ - write_both_fan_speed(th, -1); - } - - thread_therm = kthread_run(monitor_task, th, "kfand"); - - if (thread_therm == ERR_PTR(-ENOMEM)) { - printk(KERN_INFO "adt746x: Kthread creation failed\n"); - thread_therm = NULL; - return -ENOMEM; - } - - thermostat_create_files(); - - return 0; +#define BUILD_SHOW_FUNC_INT(name, data) \ +static ssize_t show_##name(struct device *dev, struct device_attribute *attr, char *buf) \ +{ \ + struct thermostat *th = dev_get_drvdata(dev); \ + return sprintf(buf, "%d\n", data); \ } -static const struct i2c_device_id therm_adt746x_id[] = { - { "therm_adt746x", 0 }, - { } -}; - -static struct i2c_driver thermostat_driver = { - .driver = { - .name = "therm_adt746x", - }, - .attach_adapter = attach_thermostat, - .probe = probe_thermostat, - .remove = remove_thermostat, - .id_table = therm_adt746x_id, -}; - -/* - * Now, unfortunately, sysfs doesn't give us a nice void * we could - * pass around to the attribute functions, so we don't really have - * choice but implement a bunch of them... - * - * FIXME, it does now... - */ -#define BUILD_SHOW_FUNC_INT(name, data) \ +#define BUILD_SHOW_FUNC_INT_LITE(name, data) \ static ssize_t show_##name(struct device *dev, struct device_attribute *attr, char *buf) \ { \ return sprintf(buf, "%d\n", data); \ @@ -494,22 +338,24 @@ static ssize_t show_##name(struct device *dev, struct device_attribute *attr, ch #define BUILD_SHOW_FUNC_FAN(name, data) \ static ssize_t show_##name(struct device *dev, struct device_attribute *attr, char *buf) \ { \ + struct thermostat *th = dev_get_drvdata(dev); \ return sprintf(buf, "%d (%d rpm)\n", \ - thermostat->last_speed[data], \ - read_fan_speed(thermostat, FAN_SPEED[data]) \ + th->last_speed[data], \ + read_fan_speed(th, FAN_SPEED[data]) \ ); \ } #define BUILD_STORE_FUNC_DEG(name, data) \ static ssize_t store_##name(struct device *dev, struct device_attribute *attr, const char *buf, size_t n) \ { \ + struct thermostat *th = dev_get_drvdata(dev); \ int val; \ int i; \ val = simple_strtol(buf, NULL, 10); \ printk(KERN_INFO "Adjusting limits by %d degrees\n", val); \ limit_adjust = val; \ for (i=0; i < 3; i++) \ - set_limit(thermostat, i); \ + set_limit(th, i); \ return n; \ } @@ -525,20 +371,21 @@ static ssize_t store_##name(struct device *dev, struct device_attribute *attr, c return n; \ } -BUILD_SHOW_FUNC_INT(sensor1_temperature, (read_reg(thermostat, TEMP_REG[1]))) -BUILD_SHOW_FUNC_INT(sensor2_temperature, (read_reg(thermostat, TEMP_REG[2]))) -BUILD_SHOW_FUNC_INT(sensor1_limit, thermostat->limits[1]) -BUILD_SHOW_FUNC_INT(sensor2_limit, thermostat->limits[2]) +BUILD_SHOW_FUNC_INT(sensor1_temperature, (read_reg(th, TEMP_REG[1]))) +BUILD_SHOW_FUNC_INT(sensor2_temperature, (read_reg(th, TEMP_REG[2]))) +BUILD_SHOW_FUNC_INT(sensor1_limit, th->limits[1]) +BUILD_SHOW_FUNC_INT(sensor2_limit, th->limits[2]) BUILD_SHOW_FUNC_STR(sensor1_location, sensor_location[1]) BUILD_SHOW_FUNC_STR(sensor2_location, sensor_location[2]) -BUILD_SHOW_FUNC_INT(specified_fan_speed, fan_speed) +BUILD_SHOW_FUNC_INT_LITE(specified_fan_speed, fan_speed) +BUILD_STORE_FUNC_INT(specified_fan_speed,fan_speed) + BUILD_SHOW_FUNC_FAN(sensor1_fan_speed, 0) BUILD_SHOW_FUNC_FAN(sensor2_fan_speed, 1) -BUILD_STORE_FUNC_INT(specified_fan_speed,fan_speed) -BUILD_SHOW_FUNC_INT(limit_adjust, limit_adjust) -BUILD_STORE_FUNC_DEG(limit_adjust, thermostat) +BUILD_SHOW_FUNC_INT_LITE(limit_adjust, limit_adjust) +BUILD_STORE_FUNC_DEG(limit_adjust, th) static DEVICE_ATTR(sensor1_temperature, S_IRUGO, show_sensor1_temperature,NULL); @@ -564,53 +411,77 @@ static DEVICE_ATTR(sensor2_fan_speed, S_IRUGO, static DEVICE_ATTR(limit_adjust, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, show_limit_adjust, store_limit_adjust); - -static int __init -thermostat_init(void) +static void thermostat_create_files(struct thermostat *th) { - struct device_node* np; - const u32 *prop; - int i = 0, offset = 0; + struct device_node *np = th->clt->dev.of_node; + struct device *dev; + int err; - np = of_find_node_by_name(NULL, "fan"); - if (!np) - return -ENODEV; - if (of_device_is_compatible(np, "adt7460")) - therm_type = ADT7460; - else if (of_device_is_compatible(np, "adt7467")) - therm_type = ADT7467; - else { - of_node_put(np); - return -ENODEV; - } + /* To maintain ABI compatibility with userspace, create + * the old style platform driver and attach the attributes + * to it here + */ + th->pdev = of_platform_device_create(np, "temperatures", NULL); + if (!th->pdev) + return; + dev = &th->pdev->dev; + dev_set_drvdata(dev, th); + err = device_create_file(dev, &dev_attr_sensor1_temperature); + err |= device_create_file(dev, &dev_attr_sensor2_temperature); + err |= device_create_file(dev, &dev_attr_sensor1_limit); + err |= device_create_file(dev, &dev_attr_sensor2_limit); + err |= device_create_file(dev, &dev_attr_sensor1_location); + err |= device_create_file(dev, &dev_attr_sensor2_location); + err |= device_create_file(dev, &dev_attr_limit_adjust); + err |= device_create_file(dev, &dev_attr_specified_fan_speed); + err |= device_create_file(dev, &dev_attr_sensor1_fan_speed); + if(th->type == ADT7460) + err |= device_create_file(dev, &dev_attr_sensor2_fan_speed); + if (err) + printk(KERN_WARNING + "Failed to create temperature attribute file(s).\n"); +} - prop = of_get_property(np, "hwsensor-params-version", NULL); - printk(KERN_INFO "adt746x: version %d (%ssupported)\n", *prop, - (*prop == 1)?"":"un"); - if (*prop != 1) { - of_node_put(np); - return -ENODEV; - } +static void thermostat_remove_files(struct thermostat *th) +{ + struct device *dev; - prop = of_get_property(np, "reg", NULL); - if (!prop) { - of_node_put(np); - return -ENODEV; - } + if (!th->pdev) + return; + dev = &th->pdev->dev; + device_remove_file(dev, &dev_attr_sensor1_temperature); + device_remove_file(dev, &dev_attr_sensor2_temperature); + device_remove_file(dev, &dev_attr_sensor1_limit); + device_remove_file(dev, &dev_attr_sensor2_limit); + device_remove_file(dev, &dev_attr_sensor1_location); + device_remove_file(dev, &dev_attr_sensor2_location); + device_remove_file(dev, &dev_attr_limit_adjust); + device_remove_file(dev, &dev_attr_specified_fan_speed); + device_remove_file(dev, &dev_attr_sensor1_fan_speed); + if (th->type == ADT7460) + device_remove_file(dev, &dev_attr_sensor2_fan_speed); + of_device_unregister(th->pdev); - /* look for bus either by path or using "reg" */ - if (strstr(np->full_name, "/i2c-bus@") != NULL) { - const char *tmp_bus = (strstr(np->full_name, "/i2c-bus@") + 9); - therm_bus = tmp_bus[0]-'0'; - } else { - therm_bus = ((*prop) >> 8) & 0x0f; - } +} - therm_address = ((*prop) & 0xff) >> 1; +static int probe_thermostat(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device_node *np = client->dev.of_node; + struct thermostat* th; + const __be32 *prop; + int i, rc, vers, offset = 0; - printk(KERN_INFO "adt746x: Thermostat bus: %d, address: 0x%02x, " - "limit_adjust: %d, fan_speed: %d\n", - therm_bus, therm_address, limit_adjust, fan_speed); + if (!np) + return -ENXIO; + prop = of_get_property(np, "hwsensor-params-version", NULL); + if (!prop) + return -ENXIO; + vers = be32_to_cpup(prop); + printk(KERN_INFO "adt746x: version %d (%ssupported)\n", + vers, vers == 1 ? "" : "un"); + if (vers != 1) + return -ENXIO; if (of_get_property(np, "hwsensor-location", NULL)) { for (i = 0; i < 3; i++) { @@ -623,72 +494,129 @@ thermostat_init(void) printk(KERN_INFO "sensor %d: %s\n", i, sensor_location[i]); offset += strlen(sensor_location[i]) + 1; } - } else { - sensor_location[0] = "?"; - sensor_location[1] = "?"; - sensor_location[2] = "?"; } - of_dev = of_platform_device_create(np, "temperatures", NULL); - of_node_put(np); + th = kzalloc(sizeof(struct thermostat), GFP_KERNEL); + if (!th) + return -ENOMEM; + + i2c_set_clientdata(client, th); + th->clt = client; + th->type = id->driver_data; - if (of_dev == NULL) { - printk(KERN_ERR "Can't register temperatures device !\n"); + rc = read_reg(th, CONFIG_REG); + if (rc < 0) { + dev_err(&client->dev, "Thermostat failed to read config!\n"); + kfree(th); return -ENODEV; } -#ifndef CONFIG_I2C_POWERMAC - request_module("i2c-powermac"); -#endif + /* force manual control to start the fan quieter */ + if (fan_speed == -1) + fan_speed = 64; + + if (th->type == ADT7460) { + printk(KERN_INFO "adt746x: ADT7460 initializing\n"); + /* The 7460 needs to be started explicitly */ + write_reg(th, CONFIG_REG, 1); + } else + printk(KERN_INFO "adt746x: ADT7467 initializing\n"); - return i2c_add_driver(&thermostat_driver); + for (i = 0; i < 3; i++) { + th->initial_limits[i] = read_reg(th, LIMIT_REG[i]); + set_limit(th, i); + } + + printk(KERN_INFO "adt746x: Lowering max temperatures from %d, %d, %d" + " to %d, %d, %d\n", + th->initial_limits[0], th->initial_limits[1], + th->initial_limits[2], th->limits[0], th->limits[1], + th->limits[2]); + + /* record invert bit status because fw can corrupt it after suspend */ + th->pwm_inv[0] = read_reg(th, MANUAL_MODE[0]) & INVERT_MASK; + th->pwm_inv[1] = read_reg(th, MANUAL_MODE[1]) & INVERT_MASK; + + /* be sure to really write fan speed the first time */ + th->last_speed[0] = -2; + th->last_speed[1] = -2; + th->last_var[0] = -80; + th->last_var[1] = -80; + + if (fan_speed != -1) { + /* manual mode, stop fans */ + write_both_fan_speed(th, 0); + } else { + /* automatic mode */ + write_both_fan_speed(th, -1); + } + + th->thread = kthread_run(monitor_task, th, "kfand"); + if (th->thread == ERR_PTR(-ENOMEM)) { + printk(KERN_INFO "adt746x: Kthread creation failed\n"); + th->thread = NULL; + return -ENOMEM; + } + + thermostat_create_files(th); + + return 0; } -static void thermostat_create_files(void) +static int remove_thermostat(struct i2c_client *client) { - int err; + struct thermostat *th = i2c_get_clientdata(client); + int i; + + thermostat_remove_files(th); - err = device_create_file(&of_dev->dev, &dev_attr_sensor1_temperature); - err |= device_create_file(&of_dev->dev, &dev_attr_sensor2_temperature); - err |= device_create_file(&of_dev->dev, &dev_attr_sensor1_limit); - err |= device_create_file(&of_dev->dev, &dev_attr_sensor2_limit); - err |= device_create_file(&of_dev->dev, &dev_attr_sensor1_location); - err |= device_create_file(&of_dev->dev, &dev_attr_sensor2_location); - err |= device_create_file(&of_dev->dev, &dev_attr_limit_adjust); - err |= device_create_file(&of_dev->dev, &dev_attr_specified_fan_speed); - err |= device_create_file(&of_dev->dev, &dev_attr_sensor1_fan_speed); - if(therm_type == ADT7460) - err |= device_create_file(&of_dev->dev, &dev_attr_sensor2_fan_speed); - if (err) - printk(KERN_WARNING - "Failed to create temperature attribute file(s).\n"); + if (th->thread != NULL) + kthread_stop(th->thread); + + printk(KERN_INFO "adt746x: Putting max temperatures back from " + "%d, %d, %d to %d, %d, %d\n", + th->limits[0], th->limits[1], th->limits[2], + th->initial_limits[0], th->initial_limits[1], + th->initial_limits[2]); + + for (i = 0; i < 3; i++) + write_reg(th, LIMIT_REG[i], th->initial_limits[i]); + + write_both_fan_speed(th, -1); + + kfree(th); + + return 0; } -static void thermostat_remove_files(void) +static const struct i2c_device_id therm_adt746x_id[] = { + { "MAC,adt7460", ADT7460 }, + { "MAC,adt7467", ADT7467 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, therm_adt746x_id); + +static struct i2c_driver thermostat_driver = { + .driver = { + .name = "therm_adt746x", + }, + .probe = probe_thermostat, + .remove = remove_thermostat, + .id_table = therm_adt746x_id, +}; + +static int __init thermostat_init(void) { - if (of_dev) { - device_remove_file(&of_dev->dev, &dev_attr_sensor1_temperature); - device_remove_file(&of_dev->dev, &dev_attr_sensor2_temperature); - device_remove_file(&of_dev->dev, &dev_attr_sensor1_limit); - device_remove_file(&of_dev->dev, &dev_attr_sensor2_limit); - device_remove_file(&of_dev->dev, &dev_attr_sensor1_location); - device_remove_file(&of_dev->dev, &dev_attr_sensor2_location); - device_remove_file(&of_dev->dev, &dev_attr_limit_adjust); - device_remove_file(&of_dev->dev, &dev_attr_specified_fan_speed); - device_remove_file(&of_dev->dev, &dev_attr_sensor1_fan_speed); - - if(therm_type == ADT7460) - device_remove_file(&of_dev->dev, - &dev_attr_sensor2_fan_speed); +#ifndef CONFIG_I2C_POWERMAC + request_module("i2c-powermac"); +#endif - } + return i2c_add_driver(&thermostat_driver); } -static void __exit -thermostat_exit(void) +static void __exit thermostat_exit(void) { i2c_del_driver(&thermostat_driver); - of_device_unregister(of_dev); } module_init(thermostat_init); diff --git a/drivers/macintosh/windfarm.h b/drivers/macintosh/windfarm.h index 7a2482cc26a7..028cdac2d33d 100644 --- a/drivers/macintosh/windfarm.h +++ b/drivers/macintosh/windfarm.h @@ -17,7 +17,7 @@ #include <linux/device.h> /* Display a 16.16 fixed point value */ -#define FIX32TOPRINT(f) ((f) >> 16),((((f) & 0xffff) * 1000) >> 16) +#define FIX32TOPRINT(f) (((s32)(f)) >> 16),(((((s32)(f)) & 0xffff) * 1000) >> 16) /* * Control objects @@ -35,12 +35,13 @@ struct wf_control_ops { }; struct wf_control { - struct list_head link; - struct wf_control_ops *ops; - char *name; - int type; - struct kref ref; - struct device_attribute attr; + struct list_head link; + const struct wf_control_ops *ops; + const char *name; + int type; + struct kref ref; + struct device_attribute attr; + void *priv; }; #define WF_CONTROL_TYPE_GENERIC 0 @@ -72,6 +73,26 @@ static inline int wf_control_set_min(struct wf_control *ct) return ct->ops->set_value(ct, vmin); } +static inline int wf_control_set(struct wf_control *ct, s32 val) +{ + return ct->ops->set_value(ct, val); +} + +static inline int wf_control_get(struct wf_control *ct, s32 *val) +{ + return ct->ops->get_value(ct, val); +} + +static inline s32 wf_control_get_min(struct wf_control *ct) +{ + return ct->ops->get_min(ct); +} + +static inline s32 wf_control_get_max(struct wf_control *ct) +{ + return ct->ops->get_max(ct); +} + /* * Sensor objects */ @@ -85,11 +106,12 @@ struct wf_sensor_ops { }; struct wf_sensor { - struct list_head link; - struct wf_sensor_ops *ops; - char *name; - struct kref ref; - struct device_attribute attr; + struct list_head link; + const struct wf_sensor_ops *ops; + const char *name; + struct kref ref; + struct device_attribute attr; + void *priv; }; /* Same lifetime rules as controls */ @@ -99,6 +121,11 @@ extern struct wf_sensor * wf_find_sensor(const char *name); extern int wf_get_sensor(struct wf_sensor *sr); extern void wf_put_sensor(struct wf_sensor *sr); +static inline int wf_sensor_get(struct wf_sensor *sr, s32 *val) +{ + return sr->ops->get_value(sr, val); +} + /* For use by clients. Note that we are a bit racy here since * notifier_block doesn't have a module owner field. I may fix * it one day ... diff --git a/drivers/macintosh/windfarm_ad7417_sensor.c b/drivers/macintosh/windfarm_ad7417_sensor.c new file mode 100644 index 000000000000..ac3f243b9c5a --- /dev/null +++ b/drivers/macintosh/windfarm_ad7417_sensor.c @@ -0,0 +1,347 @@ +/* + * Windfarm PowerMac thermal control. AD7417 sensors + * + * Copyright 2012 Benjamin Herrenschmidt, IBM Corp. + * + * Released under the term of the GNU GPL v2. + */ + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/wait.h> +#include <linux/i2c.h> +#include <asm/prom.h> +#include <asm/machdep.h> +#include <asm/io.h> +#include <asm/sections.h> + +#include "windfarm.h" +#include "windfarm_mpu.h" + +#define VERSION "1.0" + +struct wf_ad7417_priv { + struct kref ref; + struct i2c_client *i2c; + u8 config; + u8 cpu; + const struct mpu_data *mpu; + struct wf_sensor sensors[5]; + struct mutex lock; +}; + +static int wf_ad7417_temp_get(struct wf_sensor *sr, s32 *value) +{ + struct wf_ad7417_priv *pv = sr->priv; + u8 buf[2]; + s16 raw; + int rc; + + *value = 0; + mutex_lock(&pv->lock); + + /* Read temp register */ + buf[0] = 0; + rc = i2c_master_send(pv->i2c, buf, 1); + if (rc < 0) + goto error; + rc = i2c_master_recv(pv->i2c, buf, 2); + if (rc < 0) + goto error; + + /* Read a a 16-bit signed value */ + raw = be16_to_cpup((__le16 *)buf); + + /* Convert 8.8-bit to 16.16 fixed point */ + *value = ((s32)raw) << 8; + + mutex_unlock(&pv->lock); + return 0; + +error: + mutex_unlock(&pv->lock); + return -1; +} + +/* + * Scaling factors for the AD7417 ADC converters (except + * for the CPU diode which is obtained from the EEPROM). + * Those values are obtained from the property list of + * the darwin driver + */ +#define ADC_12V_CURRENT_SCALE 0x0320 /* _AD2 */ +#define ADC_CPU_VOLTAGE_SCALE 0x00a0 /* _AD3 */ +#define ADC_CPU_CURRENT_SCALE 0x1f40 /* _AD4 */ + +static void wf_ad7417_adc_convert(struct wf_ad7417_priv *pv, + int chan, s32 raw, s32 *value) +{ + switch(chan) { + case 1: /* Diode */ + *value = (raw * (s32)pv->mpu->mdiode + + ((s32)pv->mpu->bdiode << 12)) >> 2; + break; + case 2: /* 12v current */ + *value = raw * ADC_12V_CURRENT_SCALE; + break; + case 3: /* core voltage */ + *value = raw * ADC_CPU_VOLTAGE_SCALE; + break; + case 4: /* core current */ + *value = raw * ADC_CPU_CURRENT_SCALE; + break; + } +} + +static int wf_ad7417_adc_get(struct wf_sensor *sr, s32 *value) +{ + struct wf_ad7417_priv *pv = sr->priv; + int chan = sr - pv->sensors; + int i, rc; + u8 buf[2]; + u16 raw; + + *value = 0; + mutex_lock(&pv->lock); + for (i = 0; i < 10; i++) { + /* Set channel */ + buf[0] = 1; + buf[1] = (pv->config & 0x1f) | (chan << 5); + rc = i2c_master_send(pv->i2c, buf, 2); + if (rc < 0) + goto error; + + /* Wait for conversion */ + msleep(1); + + /* Switch to data register */ + buf[0] = 4; + rc = i2c_master_send(pv->i2c, buf, 1); + if (rc < 0) + goto error; + + /* Read result */ + rc = i2c_master_recv(pv->i2c, buf, 2); + if (rc < 0) + goto error; + + /* Read a a 16-bit signed value */ + raw = be16_to_cpup((__le16 *)buf) >> 6; + wf_ad7417_adc_convert(pv, chan, raw, value); + + dev_vdbg(&pv->i2c->dev, "ADC chan %d [%s]" + " raw value: 0x%x, conv to: 0x%08x\n", + chan, sr->name, raw, *value); + + mutex_unlock(&pv->lock); + return 0; + + error: + dev_dbg(&pv->i2c->dev, + "Error reading ADC, try %d...\n", i); + if (i < 9) + msleep(10); + } + mutex_unlock(&pv->lock); + return -1; +} + +static void wf_ad7417_release(struct kref *ref) +{ + struct wf_ad7417_priv *pv = container_of(ref, + struct wf_ad7417_priv, ref); + kfree(pv); +} + +static void wf_ad7417_sensor_release(struct wf_sensor *sr) +{ + struct wf_ad7417_priv *pv = sr->priv; + + kfree(sr->name); + kref_put(&pv->ref, wf_ad7417_release); +} + +static const struct wf_sensor_ops wf_ad7417_temp_ops = { + .get_value = wf_ad7417_temp_get, + .release = wf_ad7417_sensor_release, + .owner = THIS_MODULE, +}; + +static const struct wf_sensor_ops wf_ad7417_adc_ops = { + .get_value = wf_ad7417_adc_get, + .release = wf_ad7417_sensor_release, + .owner = THIS_MODULE, +}; + +static void __devinit wf_ad7417_add_sensor(struct wf_ad7417_priv *pv, + int index, const char *name, + const struct wf_sensor_ops *ops) +{ + pv->sensors[index].name = kasprintf(GFP_KERNEL, "%s-%d", name, pv->cpu); + pv->sensors[index].priv = pv; + pv->sensors[index].ops = ops; + if (!wf_register_sensor(&pv->sensors[index])) + kref_get(&pv->ref); +} + +static void __devinit wf_ad7417_init_chip(struct wf_ad7417_priv *pv) +{ + int rc; + u8 buf[2]; + u8 config = 0; + + /* + * Read ADC the configuration register and cache it. We + * also make sure Config2 contains proper values, I've seen + * cases where we got stale grabage in there, thus preventing + * proper reading of conv. values + */ + + /* Clear Config2 */ + buf[0] = 5; + buf[1] = 0; + i2c_master_send(pv->i2c, buf, 2); + + /* Read & cache Config1 */ + buf[0] = 1; + rc = i2c_master_send(pv->i2c, buf, 1); + if (rc > 0) { + rc = i2c_master_recv(pv->i2c, buf, 1); + if (rc > 0) { + config = buf[0]; + + dev_dbg(&pv->i2c->dev, "ADC config reg: %02x\n", + config); + + /* Disable shutdown mode */ + config &= 0xfe; + buf[0] = 1; + buf[1] = config; + rc = i2c_master_send(pv->i2c, buf, 2); + } + } + if (rc <= 0) + dev_err(&pv->i2c->dev, "Error reading ADC config\n"); + + pv->config = config; +} + +static int __devinit wf_ad7417_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct wf_ad7417_priv *pv; + const struct mpu_data *mpu; + const char *loc; + int cpu_nr; + + loc = of_get_property(client->dev.of_node, "hwsensor-location", NULL); + if (!loc) { + dev_warn(&client->dev, "Missing hwsensor-location property!\n"); + return -ENXIO; + } + + /* + * Identify which CPU we belong to by looking at the first entry + * in the hwsensor-location list + */ + if (!strncmp(loc, "CPU A", 5)) + cpu_nr = 0; + else if (!strncmp(loc, "CPU B", 5)) + cpu_nr = 1; + else { + pr_err("wf_ad7417: Can't identify location %s\n", loc); + return -ENXIO; + } + mpu = wf_get_mpu(cpu_nr); + if (!mpu) { + dev_err(&client->dev, "Failed to retrieve MPU data\n"); + return -ENXIO; + } + + pv = kzalloc(sizeof(struct wf_ad7417_priv), GFP_KERNEL); + if (pv == NULL) + return -ENODEV; + + kref_init(&pv->ref); + mutex_init(&pv->lock); + pv->i2c = client; + pv->cpu = cpu_nr; + pv->mpu = mpu; + dev_set_drvdata(&client->dev, pv); + + /* Initialize the chip */ + wf_ad7417_init_chip(pv); + + /* + * We cannot rely on Apple device-tree giving us child + * node with the names of the individual sensors so we + * just hard code what we know about them + */ + wf_ad7417_add_sensor(pv, 0, "cpu-amb-temp", &wf_ad7417_temp_ops); + wf_ad7417_add_sensor(pv, 1, "cpu-diode-temp", &wf_ad7417_adc_ops); + wf_ad7417_add_sensor(pv, 2, "cpu-12v-current", &wf_ad7417_adc_ops); + wf_ad7417_add_sensor(pv, 3, "cpu-voltage", &wf_ad7417_adc_ops); + wf_ad7417_add_sensor(pv, 4, "cpu-current", &wf_ad7417_adc_ops); + + return 0; +} + +static int __devexit wf_ad7417_remove(struct i2c_client *client) +{ + struct wf_ad7417_priv *pv = dev_get_drvdata(&client->dev); + int i; + + /* Mark client detached */ + pv->i2c = NULL; + + /* Release sensor */ + for (i = 0; i < 5; i++) + wf_unregister_sensor(&pv->sensors[i]); + + kref_put(&pv->ref, wf_ad7417_release); + + return 0; +} + +static const struct i2c_device_id wf_ad7417_id[] = { + { "MAC,ad7417", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wf_ad7417_id); + +static struct i2c_driver wf_ad7417_driver = { + .driver = { + .name = "wf_ad7417", + }, + .probe = wf_ad7417_probe, + .remove = wf_ad7417_remove, + .id_table = wf_ad7417_id, +}; + +static int __devinit wf_ad7417_init(void) +{ + /* This is only supported on these machines */ + if (!of_machine_is_compatible("PowerMac7,2") && + !of_machine_is_compatible("PowerMac7,3") && + !of_machine_is_compatible("RackMac3,1")) + return -ENODEV; + + return i2c_add_driver(&wf_ad7417_driver); +} + +static void __devexit wf_ad7417_exit(void) +{ + i2c_del_driver(&wf_ad7417_driver); +} + +module_init(wf_ad7417_init); +module_exit(wf_ad7417_exit); + +MODULE_AUTHOR("Benjamin Herrenschmidt <[email protected]>"); +MODULE_DESCRIPTION("ad7417 sensor driver for PowerMacs"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/macintosh/windfarm_core.c b/drivers/macintosh/windfarm_core.c index ce8897933a84..3ee198b65843 100644 --- a/drivers/macintosh/windfarm_core.c +++ b/drivers/macintosh/windfarm_core.c @@ -164,13 +164,27 @@ static ssize_t wf_show_control(struct device *dev, struct device_attribute *attr, char *buf) { struct wf_control *ctrl = container_of(attr, struct wf_control, attr); + const char *typestr; s32 val = 0; int err; err = ctrl->ops->get_value(ctrl, &val); - if (err < 0) + if (err < 0) { + if (err == -EFAULT) + return sprintf(buf, "<HW FAULT>\n"); return err; - return sprintf(buf, "%d\n", val); + } + switch(ctrl->type) { + case WF_CONTROL_RPM_FAN: + typestr = " RPM"; + break; + case WF_CONTROL_PWM_FAN: + typestr = " %"; + break; + default: + typestr = ""; + } + return sprintf(buf, "%d%s\n", val, typestr); } /* This is really only for debugging... */ @@ -470,11 +484,6 @@ static int __init windfarm_core_init(void) { DBG("wf: core loaded\n"); - /* Don't register on old machines that use therm_pm72 for now */ - if (of_machine_is_compatible("PowerMac7,2") || - of_machine_is_compatible("PowerMac7,3") || - of_machine_is_compatible("RackMac3,1")) - return -ENODEV; platform_device_register(&wf_platform_device); return 0; } diff --git a/drivers/macintosh/windfarm_cpufreq_clamp.c b/drivers/macintosh/windfarm_cpufreq_clamp.c index 1a77a7c97d0e..72d1fdfe02a5 100644 --- a/drivers/macintosh/windfarm_cpufreq_clamp.c +++ b/drivers/macintosh/windfarm_cpufreq_clamp.c @@ -75,12 +75,6 @@ static int __init wf_cpufreq_clamp_init(void) { struct wf_control *clamp; - /* Don't register on old machines that use therm_pm72 for now */ - if (of_machine_is_compatible("PowerMac7,2") || - of_machine_is_compatible("PowerMac7,3") || - of_machine_is_compatible("RackMac3,1")) - return -ENODEV; - clamp = kmalloc(sizeof(struct wf_control), GFP_KERNEL); if (clamp == NULL) return -ENOMEM; diff --git a/drivers/macintosh/windfarm_fcu_controls.c b/drivers/macintosh/windfarm_fcu_controls.c new file mode 100644 index 000000000000..b3411edb324b --- /dev/null +++ b/drivers/macintosh/windfarm_fcu_controls.c @@ -0,0 +1,613 @@ +/* + * Windfarm PowerMac thermal control. FCU fan control + * + * Copyright 2012 Benjamin Herrenschmidt, IBM Corp. + * + * Released under the term of the GNU GPL v2. + */ +#undef DEBUG + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/wait.h> +#include <linux/i2c.h> +#include <asm/prom.h> +#include <asm/machdep.h> +#include <asm/io.h> +#include <asm/sections.h> + +#include "windfarm.h" +#include "windfarm_mpu.h" + +#define VERSION "1.0" + +#ifdef DEBUG +#define DBG(args...) printk(args) +#else +#define DBG(args...) do { } while(0) +#endif + +/* + * This option is "weird" :) Basically, if you define this to 1 + * the control loop for the RPMs fans (not PWMs) will apply the + * correction factor obtained from the PID to the actual RPM + * speed read from the FCU. + * + * If you define the below constant to 0, then it will be + * applied to the setpoint RPM speed, that is basically the + * speed we proviously "asked" for. + * + * I'm using 0 for now which is what therm_pm72 used to do and + * what Darwin -apparently- does based on observed behaviour. + */ +#define RPM_PID_USE_ACTUAL_SPEED 0 + +/* Default min/max for pumps */ +#define CPU_PUMP_OUTPUT_MAX 3200 +#define CPU_PUMP_OUTPUT_MIN 1250 + +#define FCU_FAN_RPM 0 +#define FCU_FAN_PWM 1 + +struct wf_fcu_priv { + struct kref ref; + struct i2c_client *i2c; + struct mutex lock; + struct list_head fan_list; + int rpm_shift; +}; + +struct wf_fcu_fan { + struct list_head link; + int id; + s32 min, max, target; + struct wf_fcu_priv *fcu_priv; + struct wf_control ctrl; +}; + +static void wf_fcu_release(struct kref *ref) +{ + struct wf_fcu_priv *pv = container_of(ref, struct wf_fcu_priv, ref); + + kfree(pv); +} + +static void wf_fcu_fan_release(struct wf_control *ct) +{ + struct wf_fcu_fan *fan = ct->priv; + + kref_put(&fan->fcu_priv->ref, wf_fcu_release); + kfree(fan); +} + +static int wf_fcu_read_reg(struct wf_fcu_priv *pv, int reg, + unsigned char *buf, int nb) +{ + int tries, nr, nw; + + mutex_lock(&pv->lock); + + buf[0] = reg; + tries = 0; + for (;;) { + nw = i2c_master_send(pv->i2c, buf, 1); + if (nw > 0 || (nw < 0 && nw != -EIO) || tries >= 100) + break; + msleep(10); + ++tries; + } + if (nw <= 0) { + pr_err("Failure writing address to FCU: %d", nw); + nr = nw; + goto bail; + } + tries = 0; + for (;;) { + nr = i2c_master_recv(pv->i2c, buf, nb); + if (nr > 0 || (nr < 0 && nr != -ENODEV) || tries >= 100) + break; + msleep(10); + ++tries; + } + if (nr <= 0) + pr_err("wf_fcu: Failure reading data from FCU: %d", nw); + bail: + mutex_unlock(&pv->lock); + return nr; +} + +static int wf_fcu_write_reg(struct wf_fcu_priv *pv, int reg, + const unsigned char *ptr, int nb) +{ + int tries, nw; + unsigned char buf[16]; + + buf[0] = reg; + memcpy(buf+1, ptr, nb); + ++nb; + tries = 0; + for (;;) { + nw = i2c_master_send(pv->i2c, buf, nb); + if (nw > 0 || (nw < 0 && nw != -EIO) || tries >= 100) + break; + msleep(10); + ++tries; + } + if (nw < 0) + pr_err("wf_fcu: Failure writing to FCU: %d", nw); + return nw; +} + +static int wf_fcu_fan_set_rpm(struct wf_control *ct, s32 value) +{ + struct wf_fcu_fan *fan = ct->priv; + struct wf_fcu_priv *pv = fan->fcu_priv; + int rc, shift = pv->rpm_shift; + unsigned char buf[2]; + + if (value < fan->min) + value = fan->min; + if (value > fan->max) + value = fan->max; + + fan->target = value; + + buf[0] = value >> (8 - shift); + buf[1] = value << shift; + rc = wf_fcu_write_reg(pv, 0x10 + (fan->id * 2), buf, 2); + if (rc < 0) + return -EIO; + return 0; +} + +static int wf_fcu_fan_get_rpm(struct wf_control *ct, s32 *value) +{ + struct wf_fcu_fan *fan = ct->priv; + struct wf_fcu_priv *pv = fan->fcu_priv; + int rc, reg_base, shift = pv->rpm_shift; + unsigned char failure; + unsigned char active; + unsigned char buf[2]; + + rc = wf_fcu_read_reg(pv, 0xb, &failure, 1); + if (rc != 1) + return -EIO; + if ((failure & (1 << fan->id)) != 0) + return -EFAULT; + rc = wf_fcu_read_reg(pv, 0xd, &active, 1); + if (rc != 1) + return -EIO; + if ((active & (1 << fan->id)) == 0) + return -ENXIO; + + /* Programmed value or real current speed */ +#if RPM_PID_USE_ACTUAL_SPEED + reg_base = 0x11; +#else + reg_base = 0x10; +#endif + rc = wf_fcu_read_reg(pv, reg_base + (fan->id * 2), buf, 2); + if (rc != 2) + return -EIO; + + *value = (buf[0] << (8 - shift)) | buf[1] >> shift; + + return 0; +} + +static int wf_fcu_fan_set_pwm(struct wf_control *ct, s32 value) +{ + struct wf_fcu_fan *fan = ct->priv; + struct wf_fcu_priv *pv = fan->fcu_priv; + unsigned char buf[2]; + int rc; + + if (value < fan->min) + value = fan->min; + if (value > fan->max) + value = fan->max; + + fan->target = value; + + value = (value * 2559) / 1000; + buf[0] = value; + rc = wf_fcu_write_reg(pv, 0x30 + (fan->id * 2), buf, 1); + if (rc < 0) + return -EIO; + return 0; +} + +static int wf_fcu_fan_get_pwm(struct wf_control *ct, s32 *value) +{ + struct wf_fcu_fan *fan = ct->priv; + struct wf_fcu_priv *pv = fan->fcu_priv; + unsigned char failure; + unsigned char active; + unsigned char buf[2]; + int rc; + + rc = wf_fcu_read_reg(pv, 0x2b, &failure, 1); + if (rc != 1) + return -EIO; + if ((failure & (1 << fan->id)) != 0) + return -EFAULT; + rc = wf_fcu_read_reg(pv, 0x2d, &active, 1); + if (rc != 1) + return -EIO; + if ((active & (1 << fan->id)) == 0) + return -ENXIO; + + rc = wf_fcu_read_reg(pv, 0x30 + (fan->id * 2), buf, 1); + if (rc != 1) + return -EIO; + + *value = (((s32)buf[0]) * 1000) / 2559; + + return 0; +} + +static s32 wf_fcu_fan_min(struct wf_control *ct) +{ + struct wf_fcu_fan *fan = ct->priv; + + return fan->min; +} + +static s32 wf_fcu_fan_max(struct wf_control *ct) +{ + struct wf_fcu_fan *fan = ct->priv; + + return fan->max; +} + +static const struct wf_control_ops wf_fcu_fan_rpm_ops = { + .set_value = wf_fcu_fan_set_rpm, + .get_value = wf_fcu_fan_get_rpm, + .get_min = wf_fcu_fan_min, + .get_max = wf_fcu_fan_max, + .release = wf_fcu_fan_release, + .owner = THIS_MODULE, +}; + +static const struct wf_control_ops wf_fcu_fan_pwm_ops = { + .set_value = wf_fcu_fan_set_pwm, + .get_value = wf_fcu_fan_get_pwm, + .get_min = wf_fcu_fan_min, + .get_max = wf_fcu_fan_max, + .release = wf_fcu_fan_release, + .owner = THIS_MODULE, +}; + +static void __devinit wf_fcu_get_pump_minmax(struct wf_fcu_fan *fan) +{ + const struct mpu_data *mpu = wf_get_mpu(0); + u16 pump_min = 0, pump_max = 0xffff; + u16 tmp[4]; + + /* Try to fetch pumps min/max infos from eeprom */ + if (mpu) { + memcpy(&tmp, mpu->processor_part_num, 8); + if (tmp[0] != 0xffff && tmp[1] != 0xffff) { + pump_min = max(pump_min, tmp[0]); + pump_max = min(pump_max, tmp[1]); + } + if (tmp[2] != 0xffff && tmp[3] != 0xffff) { + pump_min = max(pump_min, tmp[2]); + pump_max = min(pump_max, tmp[3]); + } + } + + /* Double check the values, this _IS_ needed as the EEPROM on + * some dual 2.5Ghz G5s seem, at least, to have both min & max + * same to the same value ... (grrrr) + */ + if (pump_min == pump_max || pump_min == 0 || pump_max == 0xffff) { + pump_min = CPU_PUMP_OUTPUT_MIN; + pump_max = CPU_PUMP_OUTPUT_MAX; + } + + fan->min = pump_min; + fan->max = pump_max; + + DBG("wf_fcu: pump min/max for %s set to: [%d..%d] RPM\n", + fan->ctrl.name, pump_min, pump_max); +} + +static void __devinit wf_fcu_get_rpmfan_minmax(struct wf_fcu_fan *fan) +{ + struct wf_fcu_priv *pv = fan->fcu_priv; + const struct mpu_data *mpu0 = wf_get_mpu(0); + const struct mpu_data *mpu1 = wf_get_mpu(1); + + /* Default */ + fan->min = 2400 >> pv->rpm_shift; + fan->max = 56000 >> pv->rpm_shift; + + /* CPU fans have min/max in MPU */ + if (mpu0 && !strcmp(fan->ctrl.name, "cpu-front-fan-0")) { + fan->min = max(fan->min, (s32)mpu0->rminn_intake_fan); + fan->max = min(fan->max, (s32)mpu0->rmaxn_intake_fan); + goto bail; + } + if (mpu1 && !strcmp(fan->ctrl.name, "cpu-front-fan-1")) { + fan->min = max(fan->min, (s32)mpu1->rminn_intake_fan); + fan->max = min(fan->max, (s32)mpu1->rmaxn_intake_fan); + goto bail; + } + if (mpu0 && !strcmp(fan->ctrl.name, "cpu-rear-fan-0")) { + fan->min = max(fan->min, (s32)mpu0->rminn_exhaust_fan); + fan->max = min(fan->max, (s32)mpu0->rmaxn_exhaust_fan); + goto bail; + } + if (mpu1 && !strcmp(fan->ctrl.name, "cpu-rear-fan-1")) { + fan->min = max(fan->min, (s32)mpu1->rminn_exhaust_fan); + fan->max = min(fan->max, (s32)mpu1->rmaxn_exhaust_fan); + goto bail; + } + /* Rackmac variants, we just use mpu0 intake */ + if (!strncmp(fan->ctrl.name, "cpu-fan", 7)) { + fan->min = max(fan->min, (s32)mpu0->rminn_intake_fan); + fan->max = min(fan->max, (s32)mpu0->rmaxn_intake_fan); + goto bail; + } + bail: + DBG("wf_fcu: fan min/max for %s set to: [%d..%d] RPM\n", + fan->ctrl.name, fan->min, fan->max); +} + +static void __devinit wf_fcu_add_fan(struct wf_fcu_priv *pv, + const char *name, + int type, int id) +{ + struct wf_fcu_fan *fan; + + fan = kzalloc(sizeof(*fan), GFP_KERNEL); + if (!fan) + return; + fan->fcu_priv = pv; + fan->id = id; + fan->ctrl.name = name; + fan->ctrl.priv = fan; + + /* min/max is oddball but the code comes from + * therm_pm72 which seems to work so ... + */ + if (type == FCU_FAN_RPM) { + if (!strncmp(name, "cpu-pump", strlen("cpu-pump"))) + wf_fcu_get_pump_minmax(fan); + else + wf_fcu_get_rpmfan_minmax(fan); + fan->ctrl.type = WF_CONTROL_RPM_FAN; + fan->ctrl.ops = &wf_fcu_fan_rpm_ops; + } else { + fan->min = 10; + fan->max = 100; + fan->ctrl.type = WF_CONTROL_PWM_FAN; + fan->ctrl.ops = &wf_fcu_fan_pwm_ops; + } + + if (wf_register_control(&fan->ctrl)) { + pr_err("wf_fcu: Failed to register fan %s\n", name); + kfree(fan); + return; + } + list_add(&fan->link, &pv->fan_list); + kref_get(&pv->ref); +} + +static void __devinit wf_fcu_lookup_fans(struct wf_fcu_priv *pv) +{ + /* Translation of device-tree location properties to + * windfarm fan names + */ + static const struct { + const char *dt_name; /* Device-tree name */ + const char *ct_name; /* Control name */ + } loc_trans[] = { + { "BACKSIDE", "backside-fan", }, + { "SYS CTRLR FAN", "backside-fan", }, + { "DRIVE BAY", "drive-bay-fan", }, + { "SLOT", "slots-fan", }, + { "PCI FAN", "slots-fan", }, + { "CPU A INTAKE", "cpu-front-fan-0", }, + { "CPU A EXHAUST", "cpu-rear-fan-0", }, + { "CPU B INTAKE", "cpu-front-fan-1", }, + { "CPU B EXHAUST", "cpu-rear-fan-1", }, + { "CPU A PUMP", "cpu-pump-0", }, + { "CPU B PUMP", "cpu-pump-1", }, + { "CPU A 1", "cpu-fan-a-0", }, + { "CPU A 2", "cpu-fan-b-0", }, + { "CPU A 3", "cpu-fan-c-0", }, + { "CPU B 1", "cpu-fan-a-1", }, + { "CPU B 2", "cpu-fan-b-1", }, + { "CPU B 3", "cpu-fan-c-1", }, + }; + struct device_node *np = NULL, *fcu = pv->i2c->dev.of_node; + int i; + + DBG("Looking up FCU controls in device-tree...\n"); + + while ((np = of_get_next_child(fcu, np)) != NULL) { + int id, type = -1; + const char *loc; + const char *name; + const u32 *reg; + + DBG(" control: %s, type: %s\n", np->name, np->type); + + /* Detect control type */ + if (!strcmp(np->type, "fan-rpm-control") || + !strcmp(np->type, "fan-rpm")) + type = FCU_FAN_RPM; + if (!strcmp(np->type, "fan-pwm-control") || + !strcmp(np->type, "fan-pwm")) + type = FCU_FAN_PWM; + /* Only care about fans for now */ + if (type == -1) + continue; + + /* Lookup for a matching location */ + loc = of_get_property(np, "location", NULL); + reg = of_get_property(np, "reg", NULL); + if (loc == NULL || reg == NULL) + continue; + DBG(" matching location: %s, reg: 0x%08x\n", loc, *reg); + + for (i = 0; i < ARRAY_SIZE(loc_trans); i++) { + if (strncmp(loc, loc_trans[i].dt_name, + strlen(loc_trans[i].dt_name))) + continue; + name = loc_trans[i].ct_name; + + DBG(" location match, name: %s\n", name); + + if (type == FCU_FAN_RPM) + id = ((*reg) - 0x10) / 2; + else + id = ((*reg) - 0x30) / 2; + if (id > 7) { + pr_warning("wf_fcu: Can't parse " + "fan ID in device-tree for %s\n", + np->full_name); + break; + } + wf_fcu_add_fan(pv, name, type, id); + break; + } + } +} + +static void __devinit wf_fcu_default_fans(struct wf_fcu_priv *pv) +{ + /* We only support the default fans for PowerMac7,2 */ + if (!of_machine_is_compatible("PowerMac7,2")) + return; + + wf_fcu_add_fan(pv, "backside-fan", FCU_FAN_PWM, 1); + wf_fcu_add_fan(pv, "drive-bay-fan", FCU_FAN_RPM, 2); + wf_fcu_add_fan(pv, "slots-fan", FCU_FAN_PWM, 2); + wf_fcu_add_fan(pv, "cpu-front-fan-0", FCU_FAN_RPM, 3); + wf_fcu_add_fan(pv, "cpu-rear-fan-0", FCU_FAN_RPM, 4); + wf_fcu_add_fan(pv, "cpu-front-fan-1", FCU_FAN_RPM, 5); + wf_fcu_add_fan(pv, "cpu-rear-fan-1", FCU_FAN_RPM, 6); +} + +static int __devinit wf_fcu_init_chip(struct wf_fcu_priv *pv) +{ + unsigned char buf = 0xff; + int rc; + + rc = wf_fcu_write_reg(pv, 0xe, &buf, 1); + if (rc < 0) + return -EIO; + rc = wf_fcu_write_reg(pv, 0x2e, &buf, 1); + if (rc < 0) + return -EIO; + rc = wf_fcu_read_reg(pv, 0, &buf, 1); + if (rc < 0) + return -EIO; + pv->rpm_shift = (buf == 1) ? 2 : 3; + + pr_debug("wf_fcu: FCU Initialized, RPM fan shift is %d\n", + pv->rpm_shift); + + return 0; +} + +static int __devinit wf_fcu_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct wf_fcu_priv *pv; + + pv = kzalloc(sizeof(*pv), GFP_KERNEL); + if (!pv) + return -ENOMEM; + + kref_init(&pv->ref); + mutex_init(&pv->lock); + INIT_LIST_HEAD(&pv->fan_list); + pv->i2c = client; + + /* + * First we must start the FCU which will query the + * shift value to apply to RPMs + */ + if (wf_fcu_init_chip(pv)) { + pr_err("wf_fcu: Initialization failed !\n"); + kfree(pv); + return -ENXIO; + } + + /* First lookup fans in the device-tree */ + wf_fcu_lookup_fans(pv); + + /* + * Older machines don't have the device-tree entries + * we are looking for, just hard code the list + */ + if (list_empty(&pv->fan_list)) + wf_fcu_default_fans(pv); + + /* Still no fans ? FAIL */ + if (list_empty(&pv->fan_list)) { + pr_err("wf_fcu: Failed to find fans for your machine\n"); + kfree(pv); + return -ENODEV; + } + + dev_set_drvdata(&client->dev, pv); + + return 0; +} + +static int __devexit wf_fcu_remove(struct i2c_client *client) +{ + struct wf_fcu_priv *pv = dev_get_drvdata(&client->dev); + struct wf_fcu_fan *fan; + + while (!list_empty(&pv->fan_list)) { + fan = list_first_entry(&pv->fan_list, struct wf_fcu_fan, link); + list_del(&fan->link); + wf_unregister_control(&fan->ctrl); + } + kref_put(&pv->ref, wf_fcu_release); + return 0; +} + +static const struct i2c_device_id wf_fcu_id[] = { + { "MAC,fcu", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wf_fcu_id); + +static struct i2c_driver wf_fcu_driver = { + .driver = { + .name = "wf_fcu", + }, + .probe = wf_fcu_probe, + .remove = wf_fcu_remove, + .id_table = wf_fcu_id, +}; + +static int __init wf_fcu_init(void) +{ + return i2c_add_driver(&wf_fcu_driver); +} + +static void __exit wf_fcu_exit(void) +{ + i2c_del_driver(&wf_fcu_driver); +} + + +module_init(wf_fcu_init); +module_exit(wf_fcu_exit); + +MODULE_AUTHOR("Benjamin Herrenschmidt <[email protected]>"); +MODULE_DESCRIPTION("FCU control objects for PowerMacs thermal control"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/macintosh/windfarm_lm75_sensor.c b/drivers/macintosh/windfarm_lm75_sensor.c index 4d6a90a1372b..b0c2d3695b34 100644 --- a/drivers/macintosh/windfarm_lm75_sensor.c +++ b/drivers/macintosh/windfarm_lm75_sensor.c @@ -23,7 +23,7 @@ #include "windfarm.h" -#define VERSION "0.2" +#define VERSION "1.0" #undef DEBUG @@ -36,8 +36,8 @@ struct wf_lm75_sensor { int ds1775 : 1; int inited : 1; - struct i2c_client *i2c; - struct wf_sensor sens; + struct i2c_client *i2c; + struct wf_sensor sens; }; #define wf_to_lm75(c) container_of(c, struct wf_lm75_sensor, sens) @@ -90,40 +90,19 @@ static struct wf_sensor_ops wf_lm75_ops = { static int wf_lm75_probe(struct i2c_client *client, const struct i2c_device_id *id) -{ +{ struct wf_lm75_sensor *lm; - int rc; - - lm = kzalloc(sizeof(struct wf_lm75_sensor), GFP_KERNEL); - if (lm == NULL) - return -ENODEV; - - lm->inited = 0; - lm->ds1775 = id->driver_data; - lm->i2c = client; - lm->sens.name = client->dev.platform_data; - lm->sens.ops = &wf_lm75_ops; - i2c_set_clientdata(client, lm); - - rc = wf_register_sensor(&lm->sens); - if (rc) - kfree(lm); - - return rc; -} - -static struct i2c_driver wf_lm75_driver; - -static struct i2c_client *wf_lm75_create(struct i2c_adapter *adapter, - u8 addr, int ds1775, - const char *loc) -{ - struct i2c_board_info info; - struct i2c_client *client; - char *name; + int rc, ds1775 = id->driver_data; + const char *name, *loc; DBG("wf_lm75: creating %s device at address 0x%02x\n", - ds1775 ? "ds1775" : "lm75", addr); + ds1775 ? "ds1775" : "lm75", client->addr); + + loc = of_get_property(client->dev.of_node, "hwsensor-location", NULL); + if (!loc) { + dev_warn(&client->dev, "Missing hwsensor-location property!\n"); + return -ENXIO; + } /* Usual rant about sensor names not beeing very consistent in * the device-tree, oh well ... @@ -137,68 +116,31 @@ static struct i2c_client *wf_lm75_create(struct i2c_adapter *adapter, name = "optical-drive-temp"; else if (!strcmp(loc, "HD Temp")) name = "hard-drive-temp"; + else if (!strcmp(loc, "PCI SLOTS")) + name = "slots-temp"; + else if (!strcmp(loc, "CPU A INLET")) + name = "cpu-inlet-temp-0"; + else if (!strcmp(loc, "CPU B INLET")) + name = "cpu-inlet-temp-1"; else - goto fail; - - memset(&info, 0, sizeof(struct i2c_board_info)); - info.addr = (addr >> 1) & 0x7f; - info.platform_data = name; - strlcpy(info.type, ds1775 ? "wf_ds1775" : "wf_lm75", I2C_NAME_SIZE); - - client = i2c_new_device(adapter, &info); - if (client == NULL) { - printk(KERN_ERR "windfarm: failed to attach %s %s to i2c\n", - ds1775 ? "ds1775" : "lm75", name); - goto fail; - } - - /* - * Let i2c-core delete that device on driver removal. - * This is safe because i2c-core holds the core_lock mutex for us. - */ - list_add_tail(&client->detected, &wf_lm75_driver.clients); - return client; - fail: - return NULL; -} - -static int wf_lm75_attach(struct i2c_adapter *adapter) -{ - struct device_node *busnode, *dev; - struct pmac_i2c_bus *bus; + return -ENXIO; + - DBG("wf_lm75: adapter %s detected\n", adapter->name); - - bus = pmac_i2c_adapter_to_bus(adapter); - if (bus == NULL) + lm = kzalloc(sizeof(struct wf_lm75_sensor), GFP_KERNEL); + if (lm == NULL) return -ENODEV; - busnode = pmac_i2c_get_bus_node(bus); - DBG("wf_lm75: bus found, looking for device...\n"); - - /* Now look for lm75(s) in there */ - for (dev = NULL; - (dev = of_get_next_child(busnode, dev)) != NULL;) { - const char *loc = - of_get_property(dev, "hwsensor-location", NULL); - u8 addr; + lm->inited = 0; + lm->ds1775 = ds1775; + lm->i2c = client; + lm->sens.name = (char *)name; /* XXX fix constness in structure */ + lm->sens.ops = &wf_lm75_ops; + i2c_set_clientdata(client, lm); - /* We must re-match the adapter in order to properly check - * the channel on multibus setups - */ - if (!pmac_i2c_match_adapter(dev, adapter)) - continue; - addr = pmac_i2c_get_dev_addr(dev); - if (loc == NULL || addr == 0) - continue; - /* real lm75 */ - if (of_device_is_compatible(dev, "lm75")) - wf_lm75_create(adapter, addr, 0, loc); - /* ds1775 (compatible, better resolution */ - else if (of_device_is_compatible(dev, "ds1775")) - wf_lm75_create(adapter, addr, 1, loc); - } - return 0; + rc = wf_register_sensor(&lm->sens); + if (rc) + kfree(lm); + return rc; } static int wf_lm75_remove(struct i2c_client *client) @@ -217,16 +159,16 @@ static int wf_lm75_remove(struct i2c_client *client) } static const struct i2c_device_id wf_lm75_id[] = { - { "wf_lm75", 0 }, - { "wf_ds1775", 1 }, + { "MAC,lm75", 0 }, + { "MAC,ds1775", 1 }, { } }; +MODULE_DEVICE_TABLE(i2c, wf_lm75_id); static struct i2c_driver wf_lm75_driver = { .driver = { .name = "wf_lm75", }, - .attach_adapter = wf_lm75_attach, .probe = wf_lm75_probe, .remove = wf_lm75_remove, .id_table = wf_lm75_id, @@ -234,11 +176,6 @@ static struct i2c_driver wf_lm75_driver = { static int __init wf_lm75_sensor_init(void) { - /* Don't register on old machines that use therm_pm72 for now */ - if (of_machine_is_compatible("PowerMac7,2") || - of_machine_is_compatible("PowerMac7,3") || - of_machine_is_compatible("RackMac3,1")) - return -ENODEV; return i2c_add_driver(&wf_lm75_driver); } diff --git a/drivers/macintosh/windfarm_lm87_sensor.c b/drivers/macintosh/windfarm_lm87_sensor.c new file mode 100644 index 000000000000..c071aab79dd1 --- /dev/null +++ b/drivers/macintosh/windfarm_lm87_sensor.c @@ -0,0 +1,201 @@ +/* + * Windfarm PowerMac thermal control. LM87 sensor + * + * Copyright 2012 Benjamin Herrenschmidt, IBM Corp. + * + * Released under the term of the GNU GPL v2. + * + */ + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/wait.h> +#include <linux/i2c.h> +#include <asm/prom.h> +#include <asm/machdep.h> +#include <asm/io.h> +#include <asm/sections.h> +#include <asm/pmac_low_i2c.h> + +#include "windfarm.h" + +#define VERSION "1.0" + +#undef DEBUG + +#ifdef DEBUG +#define DBG(args...) printk(args) +#else +#define DBG(args...) do { } while(0) +#endif + +struct wf_lm87_sensor { + struct i2c_client *i2c; + struct wf_sensor sens; +}; +#define wf_to_lm87(c) container_of(c, struct wf_lm87_sensor, sens) + + +static int wf_lm87_read_reg(struct i2c_client *chip, int reg) +{ + int rc, tries = 0; + u8 buf; + + for (;;) { + /* Set address */ + buf = (u8)reg; + rc = i2c_master_send(chip, &buf, 1); + if (rc <= 0) + goto error; + rc = i2c_master_recv(chip, &buf, 1); + if (rc <= 0) + goto error; + return (int)buf; + error: + DBG("wf_lm87: Error reading LM87, retrying...\n"); + if (++tries > 10) { + printk(KERN_ERR "wf_lm87: Error reading LM87 !\n"); + return -EIO; + } + msleep(10); + } +} + +static int wf_lm87_get(struct wf_sensor *sr, s32 *value) +{ + struct wf_lm87_sensor *lm = sr->priv; + s32 temp; + + if (lm->i2c == NULL) + return -ENODEV; + +#define LM87_INT_TEMP 0x27 + + /* Read temperature register */ + temp = wf_lm87_read_reg(lm->i2c, LM87_INT_TEMP); + if (temp < 0) + return temp; + *value = temp << 16; + + return 0; +} + +static void wf_lm87_release(struct wf_sensor *sr) +{ + struct wf_lm87_sensor *lm = wf_to_lm87(sr); + + kfree(lm); +} + +static struct wf_sensor_ops wf_lm87_ops = { + .get_value = wf_lm87_get, + .release = wf_lm87_release, + .owner = THIS_MODULE, +}; + +static int wf_lm87_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct wf_lm87_sensor *lm; + const char *name = NULL, *loc; + struct device_node *np = NULL; + int rc; + + /* + * The lm87 contains a whole pile of sensors, additionally, + * the Xserve G5 has several lm87's. However, for now we only + * care about the internal temperature sensor + */ + while ((np = of_get_next_child(client->dev.of_node, np)) != NULL) { + if (strcmp(np->name, "int-temp")) + continue; + loc = of_get_property(np, "location", NULL); + if (!loc) + continue; + if (strstr(loc, "DIMM")) + name = "dimms-temp"; + else if (strstr(loc, "Processors")) + name = "between-cpus-temp"; + if (name) { + of_node_put(np); + break; + } + } + if (!name) { + pr_warning("wf_lm87: Unsupported sensor %s\n", + client->dev.of_node->full_name); + return -ENODEV; + } + + lm = kzalloc(sizeof(struct wf_lm87_sensor), GFP_KERNEL); + if (lm == NULL) + return -ENODEV; + + lm->i2c = client; + lm->sens.name = name; + lm->sens.ops = &wf_lm87_ops; + lm->sens.priv = lm; + i2c_set_clientdata(client, lm); + + rc = wf_register_sensor(&lm->sens); + if (rc) + kfree(lm); + return rc; +} + +static int wf_lm87_remove(struct i2c_client *client) +{ + struct wf_lm87_sensor *lm = i2c_get_clientdata(client); + + DBG("wf_lm87: i2c detatch called for %s\n", lm->sens.name); + + /* Mark client detached */ + lm->i2c = NULL; + + /* release sensor */ + wf_unregister_sensor(&lm->sens); + + return 0; +} + +static const struct i2c_device_id wf_lm87_id[] = { + { "MAC,lm87cimt", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wf_lm87_id); + +static struct i2c_driver wf_lm87_driver = { + .driver = { + .name = "wf_lm87", + }, + .probe = wf_lm87_probe, + .remove = wf_lm87_remove, + .id_table = wf_lm87_id, +}; + +static int __init wf_lm87_sensor_init(void) +{ + /* We only support this on the Xserve */ + if (!of_machine_is_compatible("RackMac3,1")) + return -ENODEV; + + return i2c_add_driver(&wf_lm87_driver); +} + +static void __exit wf_lm87_sensor_exit(void) +{ + i2c_del_driver(&wf_lm87_driver); +} + + +module_init(wf_lm87_sensor_init); +module_exit(wf_lm87_sensor_exit); + +MODULE_AUTHOR("Benjamin Herrenschmidt <[email protected]>"); +MODULE_DESCRIPTION("LM87 sensor objects for PowerMacs thermal control"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/macintosh/windfarm_max6690_sensor.c b/drivers/macintosh/windfarm_max6690_sensor.c index 8204113268f4..371b058d2f7d 100644 --- a/drivers/macintosh/windfarm_max6690_sensor.c +++ b/drivers/macintosh/windfarm_max6690_sensor.c @@ -16,7 +16,7 @@ #include "windfarm.h" -#define VERSION "0.2" +#define VERSION "1.0" /* This currently only exports the external temperature sensor, since that's all the control loops need. */ @@ -64,9 +64,29 @@ static struct wf_sensor_ops wf_max6690_ops = { static int wf_max6690_probe(struct i2c_client *client, const struct i2c_device_id *id) { + const char *name, *loc; struct wf_6690_sensor *max; int rc; + loc = of_get_property(client->dev.of_node, "hwsensor-location", NULL); + if (!loc) { + dev_warn(&client->dev, "Missing hwsensor-location property!\n"); + return -ENXIO; + } + + /* + * We only expose the external temperature register for + * now as this is all we need for our control loops + */ + if (!strcmp(loc, "BACKSIDE") || !strcmp(loc, "SYS CTRLR AMBIENT")) + name = "backside-temp"; + else if (!strcmp(loc, "NB Ambient")) + name = "north-bridge-temp"; + else if (!strcmp(loc, "GPU Ambient")) + name = "gpu-temp"; + else + return -ENXIO; + max = kzalloc(sizeof(struct wf_6690_sensor), GFP_KERNEL); if (max == NULL) { printk(KERN_ERR "windfarm: Couldn't create MAX6690 sensor: " @@ -75,90 +95,16 @@ static int wf_max6690_probe(struct i2c_client *client, } max->i2c = client; - max->sens.name = client->dev.platform_data; + max->sens.name = (char *)name; /* XXX fix constness in structure */ max->sens.ops = &wf_max6690_ops; i2c_set_clientdata(client, max); rc = wf_register_sensor(&max->sens); - if (rc) { + if (rc) kfree(max); - } - return rc; } -static struct i2c_driver wf_max6690_driver; - -static struct i2c_client *wf_max6690_create(struct i2c_adapter *adapter, - u8 addr, const char *loc) -{ - struct i2c_board_info info; - struct i2c_client *client; - char *name; - - if (!strcmp(loc, "BACKSIDE")) - name = "backside-temp"; - else if (!strcmp(loc, "NB Ambient")) - name = "north-bridge-temp"; - else if (!strcmp(loc, "GPU Ambient")) - name = "gpu-temp"; - else - goto fail; - - memset(&info, 0, sizeof(struct i2c_board_info)); - info.addr = addr >> 1; - info.platform_data = name; - strlcpy(info.type, "wf_max6690", I2C_NAME_SIZE); - - client = i2c_new_device(adapter, &info); - if (client == NULL) { - printk(KERN_ERR "windfarm: failed to attach MAX6690 sensor\n"); - goto fail; - } - - /* - * Let i2c-core delete that device on driver removal. - * This is safe because i2c-core holds the core_lock mutex for us. - */ - list_add_tail(&client->detected, &wf_max6690_driver.clients); - return client; - - fail: - return NULL; -} - -static int wf_max6690_attach(struct i2c_adapter *adapter) -{ - struct device_node *busnode, *dev = NULL; - struct pmac_i2c_bus *bus; - const char *loc; - - bus = pmac_i2c_adapter_to_bus(adapter); - if (bus == NULL) - return -ENODEV; - busnode = pmac_i2c_get_bus_node(bus); - - while ((dev = of_get_next_child(busnode, dev)) != NULL) { - u8 addr; - - /* We must re-match the adapter in order to properly check - * the channel on multibus setups - */ - if (!pmac_i2c_match_adapter(dev, adapter)) - continue; - if (!of_device_is_compatible(dev, "max6690")) - continue; - addr = pmac_i2c_get_dev_addr(dev); - loc = of_get_property(dev, "hwsensor-location", NULL); - if (loc == NULL || addr == 0) - continue; - printk("found max6690, loc=%s addr=0x%02x\n", loc, addr); - wf_max6690_create(adapter, addr, loc); - } - - return 0; -} - static int wf_max6690_remove(struct i2c_client *client) { struct wf_6690_sensor *max = i2c_get_clientdata(client); @@ -170,15 +116,15 @@ static int wf_max6690_remove(struct i2c_client *client) } static const struct i2c_device_id wf_max6690_id[] = { - { "wf_max6690", 0 }, + { "MAC,max6690", 0 }, { } }; +MODULE_DEVICE_TABLE(i2c, wf_max6690_id); static struct i2c_driver wf_max6690_driver = { .driver = { .name = "wf_max6690", }, - .attach_adapter = wf_max6690_attach, .probe = wf_max6690_probe, .remove = wf_max6690_remove, .id_table = wf_max6690_id, @@ -186,11 +132,6 @@ static struct i2c_driver wf_max6690_driver = { static int __init wf_max6690_sensor_init(void) { - /* Don't register on old machines that use therm_pm72 for now */ - if (of_machine_is_compatible("PowerMac7,2") || - of_machine_is_compatible("PowerMac7,3") || - of_machine_is_compatible("RackMac3,1")) - return -ENODEV; return i2c_add_driver(&wf_max6690_driver); } diff --git a/drivers/macintosh/windfarm_mpu.h b/drivers/macintosh/windfarm_mpu.h new file mode 100644 index 000000000000..046edc8c2ec5 --- /dev/null +++ b/drivers/macintosh/windfarm_mpu.h @@ -0,0 +1,105 @@ +/* + * Windfarm PowerMac thermal control + * + * Copyright 2012 Benjamin Herrenschmidt, IBM Corp. + * + * Released under the term of the GNU GPL v2. + */ + +#ifndef __WINDFARM_MPU_H +#define __WINDFARM_MPU_H + +typedef unsigned short fu16; +typedef int fs32; +typedef short fs16; + +/* Definition of the MPU data structure which contains per CPU + * calibration information (among others) for the G5 machines + */ +struct mpu_data +{ + u8 signature; /* 0x00 - EEPROM sig. */ + u8 bytes_used; /* 0x01 - Bytes used in eeprom (160 ?) */ + u8 size; /* 0x02 - EEPROM size (256 ?) */ + u8 version; /* 0x03 - EEPROM version */ + u32 data_revision; /* 0x04 - Dataset revision */ + u8 processor_bin_code[3]; /* 0x08 - Processor BIN code */ + u8 bin_code_expansion; /* 0x0b - ??? (padding ?) */ + u8 processor_num; /* 0x0c - Number of CPUs on this MPU */ + u8 input_mul_bus_div; /* 0x0d - Clock input multiplier/bus divider */ + u8 reserved1[2]; /* 0x0e - */ + u32 input_clk_freq_high; /* 0x10 - Input clock frequency high */ + u8 cpu_nb_target_cycles; /* 0x14 - ??? */ + u8 cpu_statlat; /* 0x15 - ??? */ + u8 cpu_snooplat; /* 0x16 - ??? */ + u8 cpu_snoopacc; /* 0x17 - ??? */ + u8 nb_paamwin; /* 0x18 - ??? */ + u8 nb_statlat; /* 0x19 - ??? */ + u8 nb_snooplat; /* 0x1a - ??? */ + u8 nb_snoopwin; /* 0x1b - ??? */ + u8 api_bus_mode; /* 0x1c - ??? */ + u8 reserved2[3]; /* 0x1d - */ + u32 input_clk_freq_low; /* 0x20 - Input clock frequency low */ + u8 processor_card_slot; /* 0x24 - Processor card slot number */ + u8 reserved3[2]; /* 0x25 - */ + u8 padjmax; /* 0x27 - Max power adjustment (Not in OF!) */ + u8 ttarget; /* 0x28 - Target temperature */ + u8 tmax; /* 0x29 - Max temperature */ + u8 pmaxh; /* 0x2a - Max power */ + u8 tguardband; /* 0x2b - Guardband temp ??? Hist. len in OSX */ + fs32 pid_gp; /* 0x2c - PID proportional gain */ + fs32 pid_gr; /* 0x30 - PID reset gain */ + fs32 pid_gd; /* 0x34 - PID derivative gain */ + fu16 voph; /* 0x38 - Vop High */ + fu16 vopl; /* 0x3a - Vop Low */ + fs16 nactual_die; /* 0x3c - nActual Die */ + fs16 nactual_heatsink; /* 0x3e - nActual Heatsink */ + fs16 nactual_system; /* 0x40 - nActual System */ + u16 calibration_flags; /* 0x42 - Calibration flags */ + fu16 mdiode; /* 0x44 - Diode M value (scaling factor) */ + fs16 bdiode; /* 0x46 - Diode B value (offset) */ + fs32 theta_heat_sink; /* 0x48 - Theta heat sink */ + u16 rminn_intake_fan; /* 0x4c - Intake fan min RPM */ + u16 rmaxn_intake_fan; /* 0x4e - Intake fan max RPM */ + u16 rminn_exhaust_fan; /* 0x50 - Exhaust fan min RPM */ + u16 rmaxn_exhaust_fan; /* 0x52 - Exhaust fan max RPM */ + u8 processor_part_num[8]; /* 0x54 - Processor part number XX pumps min/max */ + u32 processor_lot_num; /* 0x5c - Processor lot number */ + u8 orig_card_sernum[0x10]; /* 0x60 - Card original serial number */ + u8 curr_card_sernum[0x10]; /* 0x70 - Card current serial number */ + u8 mlb_sernum[0x18]; /* 0x80 - MLB serial number */ + u32 checksum1; /* 0x98 - */ + u32 checksum2; /* 0x9c - */ +}; /* Total size = 0xa0 */ + +static inline const struct mpu_data *wf_get_mpu(int cpu) +{ + struct device_node *np; + char nodename[64]; + const void *data; + int len; + + /* + * prom.c routine for finding a node by path is a bit brain dead + * and requires exact @xxx unit numbers. This is a bit ugly but + * will work for these machines + */ + sprintf(nodename, "/u3@0,f8000000/i2c@f8001000/cpuid@a%d", cpu ? 2 : 0); + np = of_find_node_by_path(nodename); + if (!np) + return NULL; + data = of_get_property(np, "cpuid", &len); + of_node_put(np); + if (!data) + return NULL; + + /* + * We are naughty, we have dropped the reference to the device + * node and still return a pointer to the content. We know we + * can do that though as this is only ever called on PowerMac + * which cannot remove those nodes + */ + return data; +} + +#endif /* __WINDFARM_MPU_H */ diff --git a/drivers/macintosh/windfarm_pm72.c b/drivers/macintosh/windfarm_pm72.c new file mode 100644 index 000000000000..84ac913d7e3a --- /dev/null +++ b/drivers/macintosh/windfarm_pm72.c @@ -0,0 +1,847 @@ +/* + * Windfarm PowerMac thermal control. + * Control loops for PowerMac7,2 and 7,3 + * + * Copyright (C) 2012 Benjamin Herrenschmidt, IBM Corp. + * + * Use and redistribute under the terms of the GNU GPL v2. + */ +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/reboot.h> +#include <asm/prom.h> +#include <asm/smu.h> + +#include "windfarm.h" +#include "windfarm_pid.h" +#include "windfarm_mpu.h" + +#define VERSION "1.0" + +#undef DEBUG +#undef LOTSA_DEBUG + +#ifdef DEBUG +#define DBG(args...) printk(args) +#else +#define DBG(args...) do { } while(0) +#endif + +#ifdef LOTSA_DEBUG +#define DBG_LOTS(args...) printk(args) +#else +#define DBG_LOTS(args...) do { } while(0) +#endif + +/* define this to force CPU overtemp to 60 degree, useful for testing + * the overtemp code + */ +#undef HACKED_OVERTEMP + +/* We currently only handle 2 chips */ +#define NR_CHIPS 2 +#define NR_CPU_FANS 3 * NR_CHIPS + +/* Controls and sensors */ +static struct wf_sensor *sens_cpu_temp[NR_CHIPS]; +static struct wf_sensor *sens_cpu_volts[NR_CHIPS]; +static struct wf_sensor *sens_cpu_amps[NR_CHIPS]; +static struct wf_sensor *backside_temp; +static struct wf_sensor *drives_temp; + +static struct wf_control *cpu_front_fans[NR_CHIPS]; +static struct wf_control *cpu_rear_fans[NR_CHIPS]; +static struct wf_control *cpu_pumps[NR_CHIPS]; +static struct wf_control *backside_fan; +static struct wf_control *drives_fan; +static struct wf_control *slots_fan; +static struct wf_control *cpufreq_clamp; + +/* We keep a temperature history for average calculation of 180s */ +#define CPU_TEMP_HIST_SIZE 180 + +/* Fixed speed for slot fan */ +#define SLOTS_FAN_DEFAULT_PWM 40 + +/* Scale value for CPU intake fans */ +#define CPU_INTAKE_SCALE 0x0000f852 + +/* PID loop state */ +static const struct mpu_data *cpu_mpu_data[NR_CHIPS]; +static struct wf_cpu_pid_state cpu_pid[NR_CHIPS]; +static bool cpu_pid_combined; +static u32 cpu_thist[CPU_TEMP_HIST_SIZE]; +static int cpu_thist_pt; +static s64 cpu_thist_total; +static s32 cpu_all_tmax = 100 << 16; +static struct wf_pid_state backside_pid; +static int backside_tick; +static struct wf_pid_state drives_pid; +static int drives_tick; + +static int nr_chips; +static bool have_all_controls; +static bool have_all_sensors; +static bool started; + +static int failure_state; +#define FAILURE_SENSOR 1 +#define FAILURE_FAN 2 +#define FAILURE_PERM 4 +#define FAILURE_LOW_OVERTEMP 8 +#define FAILURE_HIGH_OVERTEMP 16 + +/* Overtemp values */ +#define LOW_OVER_AVERAGE 0 +#define LOW_OVER_IMMEDIATE (10 << 16) +#define LOW_OVER_CLEAR ((-10) << 16) +#define HIGH_OVER_IMMEDIATE (14 << 16) +#define HIGH_OVER_AVERAGE (10 << 16) +#define HIGH_OVER_IMMEDIATE (14 << 16) + + +static void cpu_max_all_fans(void) +{ + int i; + + /* We max all CPU fans in case of a sensor error. We also do the + * cpufreq clamping now, even if it's supposedly done later by the + * generic code anyway, we do it earlier here to react faster + */ + if (cpufreq_clamp) + wf_control_set_max(cpufreq_clamp); + for (i = 0; i < nr_chips; i++) { + if (cpu_front_fans[i]) + wf_control_set_max(cpu_front_fans[i]); + if (cpu_rear_fans[i]) + wf_control_set_max(cpu_rear_fans[i]); + if (cpu_pumps[i]) + wf_control_set_max(cpu_pumps[i]); + } +} + +static int cpu_check_overtemp(s32 temp) +{ + int new_state = 0; + s32 t_avg, t_old; + static bool first = true; + + /* First check for immediate overtemps */ + if (temp >= (cpu_all_tmax + LOW_OVER_IMMEDIATE)) { + new_state |= FAILURE_LOW_OVERTEMP; + if ((failure_state & FAILURE_LOW_OVERTEMP) == 0) + printk(KERN_ERR "windfarm: Overtemp due to immediate CPU" + " temperature !\n"); + } + if (temp >= (cpu_all_tmax + HIGH_OVER_IMMEDIATE)) { + new_state |= FAILURE_HIGH_OVERTEMP; + if ((failure_state & FAILURE_HIGH_OVERTEMP) == 0) + printk(KERN_ERR "windfarm: Critical overtemp due to" + " immediate CPU temperature !\n"); + } + + /* + * The first time around, initialize the array with the first + * temperature reading + */ + if (first) { + int i; + + cpu_thist_total = 0; + for (i = 0; i < CPU_TEMP_HIST_SIZE; i++) { + cpu_thist[i] = temp; + cpu_thist_total += temp; + } + first = false; + } + + /* + * We calculate a history of max temperatures and use that for the + * overtemp management + */ + t_old = cpu_thist[cpu_thist_pt]; + cpu_thist[cpu_thist_pt] = temp; + cpu_thist_pt = (cpu_thist_pt + 1) % CPU_TEMP_HIST_SIZE; + cpu_thist_total -= t_old; + cpu_thist_total += temp; + t_avg = cpu_thist_total / CPU_TEMP_HIST_SIZE; + + DBG_LOTS(" t_avg = %d.%03d (out: %d.%03d, in: %d.%03d)\n", + FIX32TOPRINT(t_avg), FIX32TOPRINT(t_old), FIX32TOPRINT(temp)); + + /* Now check for average overtemps */ + if (t_avg >= (cpu_all_tmax + LOW_OVER_AVERAGE)) { + new_state |= FAILURE_LOW_OVERTEMP; + if ((failure_state & FAILURE_LOW_OVERTEMP) == 0) + printk(KERN_ERR "windfarm: Overtemp due to average CPU" + " temperature !\n"); + } + if (t_avg >= (cpu_all_tmax + HIGH_OVER_AVERAGE)) { + new_state |= FAILURE_HIGH_OVERTEMP; + if ((failure_state & FAILURE_HIGH_OVERTEMP) == 0) + printk(KERN_ERR "windfarm: Critical overtemp due to" + " average CPU temperature !\n"); + } + + /* Now handle overtemp conditions. We don't currently use the windfarm + * overtemp handling core as it's not fully suited to the needs of those + * new machine. This will be fixed later. + */ + if (new_state) { + /* High overtemp -> immediate shutdown */ + if (new_state & FAILURE_HIGH_OVERTEMP) + machine_power_off(); + if ((failure_state & new_state) != new_state) + cpu_max_all_fans(); + failure_state |= new_state; + } else if ((failure_state & FAILURE_LOW_OVERTEMP) && + (temp < (cpu_all_tmax + LOW_OVER_CLEAR))) { + printk(KERN_ERR "windfarm: Overtemp condition cleared !\n"); + failure_state &= ~FAILURE_LOW_OVERTEMP; + } + + return failure_state & (FAILURE_LOW_OVERTEMP | FAILURE_HIGH_OVERTEMP); +} + +static int read_one_cpu_vals(int cpu, s32 *temp, s32 *power) +{ + s32 dtemp, volts, amps; + int rc; + + /* Get diode temperature */ + rc = wf_sensor_get(sens_cpu_temp[cpu], &dtemp); + if (rc) { + DBG(" CPU%d: temp reading error !\n", cpu); + return -EIO; + } + DBG_LOTS(" CPU%d: temp = %d.%03d\n", cpu, FIX32TOPRINT((dtemp))); + *temp = dtemp; + + /* Get voltage */ + rc = wf_sensor_get(sens_cpu_volts[cpu], &volts); + if (rc) { + DBG(" CPU%d, volts reading error !\n", cpu); + return -EIO; + } + DBG_LOTS(" CPU%d: volts = %d.%03d\n", cpu, FIX32TOPRINT((volts))); + + /* Get current */ + rc = wf_sensor_get(sens_cpu_amps[cpu], &s); + if (rc) { + DBG(" CPU%d, current reading error !\n", cpu); + return -EIO; + } + DBG_LOTS(" CPU%d: amps = %d.%03d\n", cpu, FIX32TOPRINT((amps))); + + /* Calculate power */ + + /* Scale voltage and current raw sensor values according to fixed scales + * obtained in Darwin and calculate power from I and V + */ + *power = (((u64)volts) * ((u64)amps)) >> 16; + + DBG_LOTS(" CPU%d: power = %d.%03d\n", cpu, FIX32TOPRINT((*power))); + + return 0; + +} + +static void cpu_fans_tick_split(void) +{ + int err, cpu; + s32 intake, temp, power, t_max = 0; + + DBG_LOTS("* cpu fans_tick_split()\n"); + + for (cpu = 0; cpu < nr_chips; ++cpu) { + struct wf_cpu_pid_state *sp = &cpu_pid[cpu]; + + /* Read current speed */ + wf_control_get(cpu_rear_fans[cpu], &sp->target); + + DBG_LOTS(" CPU%d: cur_target = %d RPM\n", cpu, sp->target); + + err = read_one_cpu_vals(cpu, &temp, &power); + if (err) { + failure_state |= FAILURE_SENSOR; + cpu_max_all_fans(); + return; + } + + /* Keep track of highest temp */ + t_max = max(t_max, temp); + + /* Handle possible overtemps */ + if (cpu_check_overtemp(t_max)) + return; + + /* Run PID */ + wf_cpu_pid_run(sp, power, temp); + + DBG_LOTS(" CPU%d: target = %d RPM\n", cpu, sp->target); + + /* Apply result directly to exhaust fan */ + err = wf_control_set(cpu_rear_fans[cpu], sp->target); + if (err) { + pr_warning("wf_pm72: Fan %s reports error %d\n", + cpu_rear_fans[cpu]->name, err); + failure_state |= FAILURE_FAN; + break; + } + + /* Scale result for intake fan */ + intake = (sp->target * CPU_INTAKE_SCALE) >> 16; + DBG_LOTS(" CPU%d: intake = %d RPM\n", cpu, intake); + err = wf_control_set(cpu_front_fans[cpu], intake); + if (err) { + pr_warning("wf_pm72: Fan %s reports error %d\n", + cpu_front_fans[cpu]->name, err); + failure_state |= FAILURE_FAN; + break; + } + } +} + +static void cpu_fans_tick_combined(void) +{ + s32 temp0, power0, temp1, power1, t_max = 0; + s32 temp, power, intake, pump; + struct wf_control *pump0, *pump1; + struct wf_cpu_pid_state *sp = &cpu_pid[0]; + int err, cpu; + + DBG_LOTS("* cpu fans_tick_combined()\n"); + + /* Read current speed from cpu 0 */ + wf_control_get(cpu_rear_fans[0], &sp->target); + + DBG_LOTS(" CPUs: cur_target = %d RPM\n", sp->target); + + /* Read values for both CPUs */ + err = read_one_cpu_vals(0, &temp0, &power0); + if (err) { + failure_state |= FAILURE_SENSOR; + cpu_max_all_fans(); + return; + } + err = read_one_cpu_vals(1, &temp1, &power1); + if (err) { + failure_state |= FAILURE_SENSOR; + cpu_max_all_fans(); + return; + } + + /* Keep track of highest temp */ + t_max = max(t_max, max(temp0, temp1)); + + /* Handle possible overtemps */ + if (cpu_check_overtemp(t_max)) + return; + + /* Use the max temp & power of both */ + temp = max(temp0, temp1); + power = max(power0, power1); + + /* Run PID */ + wf_cpu_pid_run(sp, power, temp); + + /* Scale result for intake fan */ + intake = (sp->target * CPU_INTAKE_SCALE) >> 16; + + /* Same deal with pump speed */ + pump0 = cpu_pumps[0]; + pump1 = cpu_pumps[1]; + if (!pump0) { + pump0 = pump1; + pump1 = NULL; + } + pump = (sp->target * wf_control_get_max(pump0)) / + cpu_mpu_data[0]->rmaxn_exhaust_fan; + + DBG_LOTS(" CPUs: target = %d RPM\n", sp->target); + DBG_LOTS(" CPUs: intake = %d RPM\n", intake); + DBG_LOTS(" CPUs: pump = %d RPM\n", pump); + + for (cpu = 0; cpu < nr_chips; cpu++) { + err = wf_control_set(cpu_rear_fans[cpu], sp->target); + if (err) { + pr_warning("wf_pm72: Fan %s reports error %d\n", + cpu_rear_fans[cpu]->name, err); + failure_state |= FAILURE_FAN; + } + err = wf_control_set(cpu_front_fans[cpu], intake); + if (err) { + pr_warning("wf_pm72: Fan %s reports error %d\n", + cpu_front_fans[cpu]->name, err); + failure_state |= FAILURE_FAN; + } + err = 0; + if (cpu_pumps[cpu]) + err = wf_control_set(cpu_pumps[cpu], pump); + if (err) { + pr_warning("wf_pm72: Pump %s reports error %d\n", + cpu_pumps[cpu]->name, err); + failure_state |= FAILURE_FAN; + } + } +} + +/* Implementation... */ +static int cpu_setup_pid(int cpu) +{ + struct wf_cpu_pid_param pid; + const struct mpu_data *mpu = cpu_mpu_data[cpu]; + s32 tmax, ttarget, ptarget; + int fmin, fmax, hsize; + + /* Get PID params from the appropriate MPU EEPROM */ + tmax = mpu->tmax << 16; + ttarget = mpu->ttarget << 16; + ptarget = ((s32)(mpu->pmaxh - mpu->padjmax)) << 16; + + DBG("wf_72: CPU%d ttarget = %d.%03d, tmax = %d.%03d\n", + cpu, FIX32TOPRINT(ttarget), FIX32TOPRINT(tmax)); + + /* We keep a global tmax for overtemp calculations */ + if (tmax < cpu_all_tmax) + cpu_all_tmax = tmax; + + /* Set PID min/max by using the rear fan min/max */ + fmin = wf_control_get_min(cpu_rear_fans[cpu]); + fmax = wf_control_get_max(cpu_rear_fans[cpu]); + DBG("wf_72: CPU%d max RPM range = [%d..%d]\n", cpu, fmin, fmax); + + /* History size */ + hsize = min_t(int, mpu->tguardband, WF_PID_MAX_HISTORY); + DBG("wf_72: CPU%d history size = %d\n", cpu, hsize); + + /* Initialize PID loop */ + pid.interval = 1; /* seconds */ + pid.history_len = hsize; + pid.gd = mpu->pid_gd; + pid.gp = mpu->pid_gp; + pid.gr = mpu->pid_gr; + pid.tmax = tmax; + pid.ttarget = ttarget; + pid.pmaxadj = ptarget; + pid.min = fmin; + pid.max = fmax; + + wf_cpu_pid_init(&cpu_pid[cpu], &pid); + cpu_pid[cpu].target = 1000; + + return 0; +} + +/* Backside/U3 fan */ +static struct wf_pid_param backside_u3_param = { + .interval = 5, + .history_len = 2, + .gd = 40 << 20, + .gp = 5 << 20, + .gr = 0, + .itarget = 65 << 16, + .additive = 1, + .min = 20, + .max = 100, +}; + +static struct wf_pid_param backside_u3h_param = { + .interval = 5, + .history_len = 2, + .gd = 20 << 20, + .gp = 5 << 20, + .gr = 0, + .itarget = 75 << 16, + .additive = 1, + .min = 20, + .max = 100, +}; + +static void backside_fan_tick(void) +{ + s32 temp; + int speed; + int err; + + if (!backside_fan || !backside_temp || !backside_tick) + return; + if (--backside_tick > 0) + return; + backside_tick = backside_pid.param.interval; + + DBG_LOTS("* backside fans tick\n"); + + /* Update fan speed from actual fans */ + err = wf_control_get(backside_fan, &speed); + if (!err) + backside_pid.target = speed; + + err = wf_sensor_get(backside_temp, &temp); + if (err) { + printk(KERN_WARNING "windfarm: U4 temp sensor error %d\n", + err); + failure_state |= FAILURE_SENSOR; + wf_control_set_max(backside_fan); + return; + } + speed = wf_pid_run(&backside_pid, temp); + + DBG_LOTS("backside PID temp=%d.%.3d speed=%d\n", + FIX32TOPRINT(temp), speed); + + err = wf_control_set(backside_fan, speed); + if (err) { + printk(KERN_WARNING "windfarm: backside fan error %d\n", err); + failure_state |= FAILURE_FAN; + } +} + +static void backside_setup_pid(void) +{ + /* first time initialize things */ + s32 fmin = wf_control_get_min(backside_fan); + s32 fmax = wf_control_get_max(backside_fan); + struct wf_pid_param param; + struct device_node *u3; + int u3h = 1; /* conservative by default */ + + u3 = of_find_node_by_path("/u3@0,f8000000"); + if (u3 != NULL) { + const u32 *vers = of_get_property(u3, "device-rev", NULL); + if (vers) + if (((*vers) & 0x3f) < 0x34) + u3h = 0; + of_node_put(u3); + } + + param = u3h ? backside_u3h_param : backside_u3_param; + + param.min = max(param.min, fmin); + param.max = min(param.max, fmax); + wf_pid_init(&backside_pid, ¶m); + backside_tick = 1; + + pr_info("wf_pm72: Backside control loop started.\n"); +} + +/* Drive bay fan */ +static const struct wf_pid_param drives_param = { + .interval = 5, + .history_len = 2, + .gd = 30 << 20, + .gp = 5 << 20, + .gr = 0, + .itarget = 40 << 16, + .additive = 1, + .min = 300, + .max = 4000, +}; + +static void drives_fan_tick(void) +{ + s32 temp; + int speed; + int err; + + if (!drives_fan || !drives_temp || !drives_tick) + return; + if (--drives_tick > 0) + return; + drives_tick = drives_pid.param.interval; + + DBG_LOTS("* drives fans tick\n"); + + /* Update fan speed from actual fans */ + err = wf_control_get(drives_fan, &speed); + if (!err) + drives_pid.target = speed; + + err = wf_sensor_get(drives_temp, &temp); + if (err) { + pr_warning("wf_pm72: drive bay temp sensor error %d\n", err); + failure_state |= FAILURE_SENSOR; + wf_control_set_max(drives_fan); + return; + } + speed = wf_pid_run(&drives_pid, temp); + + DBG_LOTS("drives PID temp=%d.%.3d speed=%d\n", + FIX32TOPRINT(temp), speed); + + err = wf_control_set(drives_fan, speed); + if (err) { + printk(KERN_WARNING "windfarm: drive bay fan error %d\n", err); + failure_state |= FAILURE_FAN; + } +} + +static void drives_setup_pid(void) +{ + /* first time initialize things */ + s32 fmin = wf_control_get_min(drives_fan); + s32 fmax = wf_control_get_max(drives_fan); + struct wf_pid_param param = drives_param; + + param.min = max(param.min, fmin); + param.max = min(param.max, fmax); + wf_pid_init(&drives_pid, ¶m); + drives_tick = 1; + + pr_info("wf_pm72: Drive bay control loop started.\n"); +} + +static void set_fail_state(void) +{ + cpu_max_all_fans(); + + if (backside_fan) + wf_control_set_max(backside_fan); + if (slots_fan) + wf_control_set_max(slots_fan); + if (drives_fan) + wf_control_set_max(drives_fan); +} + +static void pm72_tick(void) +{ + int i, last_failure; + + if (!started) { + started = 1; + printk(KERN_INFO "windfarm: CPUs control loops started.\n"); + for (i = 0; i < nr_chips; ++i) { + if (cpu_setup_pid(i) < 0) { + failure_state = FAILURE_PERM; + set_fail_state(); + break; + } + } + DBG_LOTS("cpu_all_tmax=%d.%03d\n", FIX32TOPRINT(cpu_all_tmax)); + + backside_setup_pid(); + drives_setup_pid(); + + /* + * We don't have the right stuff to drive the PCI fan + * so we fix it to a default value + */ + wf_control_set(slots_fan, SLOTS_FAN_DEFAULT_PWM); + +#ifdef HACKED_OVERTEMP + cpu_all_tmax = 60 << 16; +#endif + } + + /* Permanent failure, bail out */ + if (failure_state & FAILURE_PERM) + return; + + /* + * Clear all failure bits except low overtemp which will be eventually + * cleared by the control loop itself + */ + last_failure = failure_state; + failure_state &= FAILURE_LOW_OVERTEMP; + if (cpu_pid_combined) + cpu_fans_tick_combined(); + else + cpu_fans_tick_split(); + backside_fan_tick(); + drives_fan_tick(); + + DBG_LOTS(" last_failure: 0x%x, failure_state: %x\n", + last_failure, failure_state); + + /* Check for failures. Any failure causes cpufreq clamping */ + if (failure_state && last_failure == 0 && cpufreq_clamp) + wf_control_set_max(cpufreq_clamp); + if (failure_state == 0 && last_failure && cpufreq_clamp) + wf_control_set_min(cpufreq_clamp); + + /* That's it for now, we might want to deal with other failures + * differently in the future though + */ +} + +static void pm72_new_control(struct wf_control *ct) +{ + bool all_controls; + bool had_pump = cpu_pumps[0] || cpu_pumps[1]; + + if (!strcmp(ct->name, "cpu-front-fan-0")) + cpu_front_fans[0] = ct; + else if (!strcmp(ct->name, "cpu-front-fan-1")) + cpu_front_fans[1] = ct; + else if (!strcmp(ct->name, "cpu-rear-fan-0")) + cpu_rear_fans[0] = ct; + else if (!strcmp(ct->name, "cpu-rear-fan-1")) + cpu_rear_fans[1] = ct; + else if (!strcmp(ct->name, "cpu-pump-0")) + cpu_pumps[0] = ct; + else if (!strcmp(ct->name, "cpu-pump-1")) + cpu_pumps[1] = ct; + else if (!strcmp(ct->name, "backside-fan")) + backside_fan = ct; + else if (!strcmp(ct->name, "slots-fan")) + slots_fan = ct; + else if (!strcmp(ct->name, "drive-bay-fan")) + drives_fan = ct; + else if (!strcmp(ct->name, "cpufreq-clamp")) + cpufreq_clamp = ct; + + all_controls = + cpu_front_fans[0] && + cpu_rear_fans[0] && + backside_fan && + slots_fan && + drives_fan; + if (nr_chips > 1) + all_controls &= + cpu_front_fans[1] && + cpu_rear_fans[1]; + have_all_controls = all_controls; + + if ((cpu_pumps[0] || cpu_pumps[1]) && !had_pump) { + pr_info("wf_pm72: Liquid cooling pump(s) detected," + " using new algorithm !\n"); + cpu_pid_combined = true; + } +} + + +static void pm72_new_sensor(struct wf_sensor *sr) +{ + bool all_sensors; + + if (!strcmp(sr->name, "cpu-diode-temp-0")) + sens_cpu_temp[0] = sr; + else if (!strcmp(sr->name, "cpu-diode-temp-1")) + sens_cpu_temp[1] = sr; + else if (!strcmp(sr->name, "cpu-voltage-0")) + sens_cpu_volts[0] = sr; + else if (!strcmp(sr->name, "cpu-voltage-1")) + sens_cpu_volts[1] = sr; + else if (!strcmp(sr->name, "cpu-current-0")) + sens_cpu_amps[0] = sr; + else if (!strcmp(sr->name, "cpu-current-1")) + sens_cpu_amps[1] = sr; + else if (!strcmp(sr->name, "backside-temp")) + backside_temp = sr; + else if (!strcmp(sr->name, "hd-temp")) + drives_temp = sr; + + all_sensors = + sens_cpu_temp[0] && + sens_cpu_volts[0] && + sens_cpu_amps[0] && + backside_temp && + drives_temp; + if (nr_chips > 1) + all_sensors &= + sens_cpu_temp[1] && + sens_cpu_volts[1] && + sens_cpu_amps[1]; + + have_all_sensors = all_sensors; +} + +static int pm72_wf_notify(struct notifier_block *self, + unsigned long event, void *data) +{ + switch (event) { + case WF_EVENT_NEW_SENSOR: + pm72_new_sensor(data); + break; + case WF_EVENT_NEW_CONTROL: + pm72_new_control(data); + break; + case WF_EVENT_TICK: + if (have_all_controls && have_all_sensors) + pm72_tick(); + } + return 0; +} + +static struct notifier_block pm72_events = { + .notifier_call = pm72_wf_notify, +}; + +static int wf_pm72_probe(struct platform_device *dev) +{ + wf_register_client(&pm72_events); + return 0; +} + +static int __devexit wf_pm72_remove(struct platform_device *dev) +{ + wf_unregister_client(&pm72_events); + + /* should release all sensors and controls */ + return 0; +} + +static struct platform_driver wf_pm72_driver = { + .probe = wf_pm72_probe, + .remove = wf_pm72_remove, + .driver = { + .name = "windfarm", + .owner = THIS_MODULE, + }, +}; + +static int __init wf_pm72_init(void) +{ + struct device_node *cpu; + int i; + + if (!of_machine_is_compatible("PowerMac7,2") && + !of_machine_is_compatible("PowerMac7,3")) + return -ENODEV; + + /* Count the number of CPU cores */ + nr_chips = 0; + for (cpu = NULL; (cpu = of_find_node_by_type(cpu, "cpu")) != NULL; ) + ++nr_chips; + if (nr_chips > NR_CHIPS) + nr_chips = NR_CHIPS; + + pr_info("windfarm: Initializing for desktop G5 with %d chips\n", + nr_chips); + + /* Get MPU data for each CPU */ + for (i = 0; i < nr_chips; i++) { + cpu_mpu_data[i] = wf_get_mpu(i); + if (!cpu_mpu_data[i]) { + pr_err("wf_pm72: Failed to find MPU data for CPU %d\n", i); + return -ENXIO; + } + } + +#ifdef MODULE + request_module("windfarm_fcu_controls"); + request_module("windfarm_lm75_sensor"); + request_module("windfarm_ad7417_sensor"); + request_module("windfarm_max6690_sensor"); + request_module("windfarm_cpufreq_clamp"); +#endif /* MODULE */ + + platform_driver_register(&wf_pm72_driver); + return 0; +} + +static void __exit wf_pm72_exit(void) +{ + platform_driver_unregister(&wf_pm72_driver); +} + +module_init(wf_pm72_init); +module_exit(wf_pm72_exit); + +MODULE_AUTHOR("Benjamin Herrenschmidt <[email protected]>"); +MODULE_DESCRIPTION("Thermal control for AGP PowerMac G5s"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:windfarm"); diff --git a/drivers/macintosh/windfarm_pm81.c b/drivers/macintosh/windfarm_pm81.c index fc13d0f2663b..990c87606be9 100644 --- a/drivers/macintosh/windfarm_pm81.c +++ b/drivers/macintosh/windfarm_pm81.c @@ -302,13 +302,13 @@ static void wf_smu_create_sys_fans(void) pid_param.interval = WF_SMU_SYS_FANS_INTERVAL; pid_param.history_len = WF_SMU_SYS_FANS_HISTORY_SIZE; pid_param.itarget = param->itarget; - pid_param.min = fan_system->ops->get_min(fan_system); - pid_param.max = fan_system->ops->get_max(fan_system); + pid_param.min = wf_control_get_min(fan_system); + pid_param.max = wf_control_get_max(fan_system); if (fan_hd) { pid_param.min = - max(pid_param.min,fan_hd->ops->get_min(fan_hd)); + max(pid_param.min, wf_control_get_min(fan_hd)); pid_param.max = - min(pid_param.max,fan_hd->ops->get_max(fan_hd)); + min(pid_param.max, wf_control_get_max(fan_hd)); } wf_pid_init(&wf_smu_sys_fans->pid, &pid_param); @@ -337,7 +337,7 @@ static void wf_smu_sys_fans_tick(struct wf_smu_sys_fans_state *st) } st->ticks = WF_SMU_SYS_FANS_INTERVAL; - rc = sensor_hd_temp->ops->get_value(sensor_hd_temp, &temp); + rc = wf_sensor_get(sensor_hd_temp, &temp); if (rc) { printk(KERN_WARNING "windfarm: HD temp sensor error %d\n", rc); @@ -373,7 +373,7 @@ static void wf_smu_sys_fans_tick(struct wf_smu_sys_fans_state *st) st->hd_setpoint = new_setpoint; readjust: if (fan_system && wf_smu_failure_state == 0) { - rc = fan_system->ops->set_value(fan_system, st->sys_setpoint); + rc = wf_control_set(fan_system, st->sys_setpoint); if (rc) { printk(KERN_WARNING "windfarm: Sys fan error %d\n", rc); @@ -381,7 +381,7 @@ static void wf_smu_sys_fans_tick(struct wf_smu_sys_fans_state *st) } } if (fan_hd && wf_smu_failure_state == 0) { - rc = fan_hd->ops->set_value(fan_hd, st->hd_setpoint); + rc = wf_control_set(fan_hd, st->hd_setpoint); if (rc) { printk(KERN_WARNING "windfarm: HD fan error %d\n", rc); @@ -447,8 +447,8 @@ static void wf_smu_create_cpu_fans(void) pid_param.ttarget = tmax - tdelta; pid_param.pmaxadj = maxpow - powadj; - pid_param.min = fan_cpu_main->ops->get_min(fan_cpu_main); - pid_param.max = fan_cpu_main->ops->get_max(fan_cpu_main); + pid_param.min = wf_control_get_min(fan_cpu_main); + pid_param.max = wf_control_get_max(fan_cpu_main); wf_cpu_pid_init(&wf_smu_cpu_fans->pid, &pid_param); @@ -481,7 +481,7 @@ static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st) } st->ticks = WF_SMU_CPU_FANS_INTERVAL; - rc = sensor_cpu_temp->ops->get_value(sensor_cpu_temp, &temp); + rc = wf_sensor_get(sensor_cpu_temp, &temp); if (rc) { printk(KERN_WARNING "windfarm: CPU temp sensor error %d\n", rc); @@ -489,7 +489,7 @@ static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st) return; } - rc = sensor_cpu_power->ops->get_value(sensor_cpu_power, &power); + rc = wf_sensor_get(sensor_cpu_power, &power); if (rc) { printk(KERN_WARNING "windfarm: CPU power sensor error %d\n", rc); @@ -525,8 +525,7 @@ static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st) st->cpu_setpoint = new_setpoint; readjust: if (fan_cpu_main && wf_smu_failure_state == 0) { - rc = fan_cpu_main->ops->set_value(fan_cpu_main, - st->cpu_setpoint); + rc = wf_control_set(fan_cpu_main, st->cpu_setpoint); if (rc) { printk(KERN_WARNING "windfarm: CPU main fan" " error %d\n", rc); diff --git a/drivers/macintosh/windfarm_pm91.c b/drivers/macintosh/windfarm_pm91.c index a9430ed4f36c..7653603cb00e 100644 --- a/drivers/macintosh/windfarm_pm91.c +++ b/drivers/macintosh/windfarm_pm91.c @@ -192,8 +192,8 @@ static void wf_smu_create_cpu_fans(void) pid_param.ttarget = tmax - tdelta; pid_param.pmaxadj = maxpow - powadj; - pid_param.min = fan_cpu_main->ops->get_min(fan_cpu_main); - pid_param.max = fan_cpu_main->ops->get_max(fan_cpu_main); + pid_param.min = wf_control_get_min(fan_cpu_main); + pid_param.max = wf_control_get_max(fan_cpu_main); wf_cpu_pid_init(&wf_smu_cpu_fans->pid, &pid_param); @@ -226,7 +226,7 @@ static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st) } st->ticks = WF_SMU_CPU_FANS_INTERVAL; - rc = sensor_cpu_temp->ops->get_value(sensor_cpu_temp, &temp); + rc = wf_sensor_get(sensor_cpu_temp, &temp); if (rc) { printk(KERN_WARNING "windfarm: CPU temp sensor error %d\n", rc); @@ -234,7 +234,7 @@ static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st) return; } - rc = sensor_cpu_power->ops->get_value(sensor_cpu_power, &power); + rc = wf_sensor_get(sensor_cpu_power, &power); if (rc) { printk(KERN_WARNING "windfarm: CPU power sensor error %d\n", rc); @@ -261,8 +261,7 @@ static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st) st->cpu_setpoint = new_setpoint; readjust: if (fan_cpu_main && wf_smu_failure_state == 0) { - rc = fan_cpu_main->ops->set_value(fan_cpu_main, - st->cpu_setpoint); + rc = wf_control_set(fan_cpu_main, st->cpu_setpoint); if (rc) { printk(KERN_WARNING "windfarm: CPU main fan" " error %d\n", rc); @@ -270,8 +269,7 @@ static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st) } } if (fan_cpu_second && wf_smu_failure_state == 0) { - rc = fan_cpu_second->ops->set_value(fan_cpu_second, - st->cpu_setpoint); + rc = wf_control_set(fan_cpu_second, st->cpu_setpoint); if (rc) { printk(KERN_WARNING "windfarm: CPU second fan" " error %d\n", rc); @@ -279,8 +277,7 @@ static void wf_smu_cpu_fans_tick(struct wf_smu_cpu_fans_state *st) } } if (fan_cpu_third && wf_smu_failure_state == 0) { - rc = fan_cpu_main->ops->set_value(fan_cpu_third, - st->cpu_setpoint); + rc = wf_control_set(fan_cpu_third, st->cpu_setpoint); if (rc) { printk(KERN_WARNING "windfarm: CPU third fan" " error %d\n", rc); @@ -312,8 +309,8 @@ static void wf_smu_create_drive_fans(void) /* Fill PID params */ param.additive = (fan_hd->type == WF_CONTROL_RPM_FAN); - param.min = fan_hd->ops->get_min(fan_hd); - param.max = fan_hd->ops->get_max(fan_hd); + param.min = wf_control_get_min(fan_hd); + param.max = wf_control_get_max(fan_hd); wf_pid_init(&wf_smu_drive_fans->pid, ¶m); DBG("wf: Drive Fan control initialized.\n"); @@ -338,7 +335,7 @@ static void wf_smu_drive_fans_tick(struct wf_smu_drive_fans_state *st) } st->ticks = st->pid.param.interval; - rc = sensor_hd_temp->ops->get_value(sensor_hd_temp, &temp); + rc = wf_sensor_get(sensor_hd_temp, &temp); if (rc) { printk(KERN_WARNING "windfarm: HD temp sensor error %d\n", rc); @@ -361,7 +358,7 @@ static void wf_smu_drive_fans_tick(struct wf_smu_drive_fans_state *st) st->setpoint = new_setpoint; readjust: if (fan_hd && wf_smu_failure_state == 0) { - rc = fan_hd->ops->set_value(fan_hd, st->setpoint); + rc = wf_control_set(fan_hd, st->setpoint); if (rc) { printk(KERN_WARNING "windfarm: HD fan error %d\n", rc); @@ -393,8 +390,8 @@ static void wf_smu_create_slots_fans(void) /* Fill PID params */ param.additive = (fan_slots->type == WF_CONTROL_RPM_FAN); - param.min = fan_slots->ops->get_min(fan_slots); - param.max = fan_slots->ops->get_max(fan_slots); + param.min = wf_control_get_min(fan_slots); + param.max = wf_control_get_max(fan_slots); wf_pid_init(&wf_smu_slots_fans->pid, ¶m); DBG("wf: Slots Fan control initialized.\n"); @@ -419,7 +416,7 @@ static void wf_smu_slots_fans_tick(struct wf_smu_slots_fans_state *st) } st->ticks = st->pid.param.interval; - rc = sensor_slots_power->ops->get_value(sensor_slots_power, &power); + rc = wf_sensor_get(sensor_slots_power, &power); if (rc) { printk(KERN_WARNING "windfarm: Slots power sensor error %d\n", rc); @@ -444,7 +441,7 @@ static void wf_smu_slots_fans_tick(struct wf_smu_slots_fans_state *st) st->setpoint = new_setpoint; readjust: if (fan_slots && wf_smu_failure_state == 0) { - rc = fan_slots->ops->set_value(fan_slots, st->setpoint); + rc = wf_control_set(fan_slots, st->setpoint); if (rc) { printk(KERN_WARNING "windfarm: Slots fan error %d\n", rc); diff --git a/drivers/macintosh/windfarm_rm31.c b/drivers/macintosh/windfarm_rm31.c new file mode 100644 index 000000000000..3eca6d4b52fc --- /dev/null +++ b/drivers/macintosh/windfarm_rm31.c @@ -0,0 +1,740 @@ +/* + * Windfarm PowerMac thermal control. + * Control loops for RackMack3,1 (Xserve G5) + * + * Copyright (C) 2012 Benjamin Herrenschmidt, IBM Corp. + * + * Use and redistribute under the terms of the GNU GPL v2. + */ +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/reboot.h> +#include <asm/prom.h> +#include <asm/smu.h> + +#include "windfarm.h" +#include "windfarm_pid.h" +#include "windfarm_mpu.h" + +#define VERSION "1.0" + +#undef DEBUG +#undef LOTSA_DEBUG + +#ifdef DEBUG +#define DBG(args...) printk(args) +#else +#define DBG(args...) do { } while(0) +#endif + +#ifdef LOTSA_DEBUG +#define DBG_LOTS(args...) printk(args) +#else +#define DBG_LOTS(args...) do { } while(0) +#endif + +/* define this to force CPU overtemp to 60 degree, useful for testing + * the overtemp code + */ +#undef HACKED_OVERTEMP + +/* We currently only handle 2 chips */ +#define NR_CHIPS 2 +#define NR_CPU_FANS 3 * NR_CHIPS + +/* Controls and sensors */ +static struct wf_sensor *sens_cpu_temp[NR_CHIPS]; +static struct wf_sensor *sens_cpu_volts[NR_CHIPS]; +static struct wf_sensor *sens_cpu_amps[NR_CHIPS]; +static struct wf_sensor *backside_temp; +static struct wf_sensor *slots_temp; +static struct wf_sensor *dimms_temp; + +static struct wf_control *cpu_fans[NR_CHIPS][3]; +static struct wf_control *backside_fan; +static struct wf_control *slots_fan; +static struct wf_control *cpufreq_clamp; + +/* We keep a temperature history for average calculation of 180s */ +#define CPU_TEMP_HIST_SIZE 180 + +/* PID loop state */ +static const struct mpu_data *cpu_mpu_data[NR_CHIPS]; +static struct wf_cpu_pid_state cpu_pid[NR_CHIPS]; +static u32 cpu_thist[CPU_TEMP_HIST_SIZE]; +static int cpu_thist_pt; +static s64 cpu_thist_total; +static s32 cpu_all_tmax = 100 << 16; +static struct wf_pid_state backside_pid; +static int backside_tick; +static struct wf_pid_state slots_pid; +static int slots_tick; +static int slots_speed; +static struct wf_pid_state dimms_pid; +static int dimms_output_clamp; + +static int nr_chips; +static bool have_all_controls; +static bool have_all_sensors; +static bool started; + +static int failure_state; +#define FAILURE_SENSOR 1 +#define FAILURE_FAN 2 +#define FAILURE_PERM 4 +#define FAILURE_LOW_OVERTEMP 8 +#define FAILURE_HIGH_OVERTEMP 16 + +/* Overtemp values */ +#define LOW_OVER_AVERAGE 0 +#define LOW_OVER_IMMEDIATE (10 << 16) +#define LOW_OVER_CLEAR ((-10) << 16) +#define HIGH_OVER_IMMEDIATE (14 << 16) +#define HIGH_OVER_AVERAGE (10 << 16) +#define HIGH_OVER_IMMEDIATE (14 << 16) + + +static void cpu_max_all_fans(void) +{ + int i; + + /* We max all CPU fans in case of a sensor error. We also do the + * cpufreq clamping now, even if it's supposedly done later by the + * generic code anyway, we do it earlier here to react faster + */ + if (cpufreq_clamp) + wf_control_set_max(cpufreq_clamp); + for (i = 0; i < nr_chips; i++) { + if (cpu_fans[i][0]) + wf_control_set_max(cpu_fans[i][0]); + if (cpu_fans[i][1]) + wf_control_set_max(cpu_fans[i][1]); + if (cpu_fans[i][2]) + wf_control_set_max(cpu_fans[i][2]); + } +} + +static int cpu_check_overtemp(s32 temp) +{ + int new_state = 0; + s32 t_avg, t_old; + static bool first = true; + + /* First check for immediate overtemps */ + if (temp >= (cpu_all_tmax + LOW_OVER_IMMEDIATE)) { + new_state |= FAILURE_LOW_OVERTEMP; + if ((failure_state & FAILURE_LOW_OVERTEMP) == 0) + printk(KERN_ERR "windfarm: Overtemp due to immediate CPU" + " temperature !\n"); + } + if (temp >= (cpu_all_tmax + HIGH_OVER_IMMEDIATE)) { + new_state |= FAILURE_HIGH_OVERTEMP; + if ((failure_state & FAILURE_HIGH_OVERTEMP) == 0) + printk(KERN_ERR "windfarm: Critical overtemp due to" + " immediate CPU temperature !\n"); + } + + /* + * The first time around, initialize the array with the first + * temperature reading + */ + if (first) { + int i; + + cpu_thist_total = 0; + for (i = 0; i < CPU_TEMP_HIST_SIZE; i++) { + cpu_thist[i] = temp; + cpu_thist_total += temp; + } + first = false; + } + + /* + * We calculate a history of max temperatures and use that for the + * overtemp management + */ + t_old = cpu_thist[cpu_thist_pt]; + cpu_thist[cpu_thist_pt] = temp; + cpu_thist_pt = (cpu_thist_pt + 1) % CPU_TEMP_HIST_SIZE; + cpu_thist_total -= t_old; + cpu_thist_total += temp; + t_avg = cpu_thist_total / CPU_TEMP_HIST_SIZE; + + DBG_LOTS(" t_avg = %d.%03d (out: %d.%03d, in: %d.%03d)\n", + FIX32TOPRINT(t_avg), FIX32TOPRINT(t_old), FIX32TOPRINT(temp)); + + /* Now check for average overtemps */ + if (t_avg >= (cpu_all_tmax + LOW_OVER_AVERAGE)) { + new_state |= FAILURE_LOW_OVERTEMP; + if ((failure_state & FAILURE_LOW_OVERTEMP) == 0) + printk(KERN_ERR "windfarm: Overtemp due to average CPU" + " temperature !\n"); + } + if (t_avg >= (cpu_all_tmax + HIGH_OVER_AVERAGE)) { + new_state |= FAILURE_HIGH_OVERTEMP; + if ((failure_state & FAILURE_HIGH_OVERTEMP) == 0) + printk(KERN_ERR "windfarm: Critical overtemp due to" + " average CPU temperature !\n"); + } + + /* Now handle overtemp conditions. We don't currently use the windfarm + * overtemp handling core as it's not fully suited to the needs of those + * new machine. This will be fixed later. + */ + if (new_state) { + /* High overtemp -> immediate shutdown */ + if (new_state & FAILURE_HIGH_OVERTEMP) + machine_power_off(); + if ((failure_state & new_state) != new_state) + cpu_max_all_fans(); + failure_state |= new_state; + } else if ((failure_state & FAILURE_LOW_OVERTEMP) && + (temp < (cpu_all_tmax + LOW_OVER_CLEAR))) { + printk(KERN_ERR "windfarm: Overtemp condition cleared !\n"); + failure_state &= ~FAILURE_LOW_OVERTEMP; + } + + return failure_state & (FAILURE_LOW_OVERTEMP | FAILURE_HIGH_OVERTEMP); +} + +static int read_one_cpu_vals(int cpu, s32 *temp, s32 *power) +{ + s32 dtemp, volts, amps; + int rc; + + /* Get diode temperature */ + rc = wf_sensor_get(sens_cpu_temp[cpu], &dtemp); + if (rc) { + DBG(" CPU%d: temp reading error !\n", cpu); + return -EIO; + } + DBG_LOTS(" CPU%d: temp = %d.%03d\n", cpu, FIX32TOPRINT((dtemp))); + *temp = dtemp; + + /* Get voltage */ + rc = wf_sensor_get(sens_cpu_volts[cpu], &volts); + if (rc) { + DBG(" CPU%d, volts reading error !\n", cpu); + return -EIO; + } + DBG_LOTS(" CPU%d: volts = %d.%03d\n", cpu, FIX32TOPRINT((volts))); + + /* Get current */ + rc = wf_sensor_get(sens_cpu_amps[cpu], &s); + if (rc) { + DBG(" CPU%d, current reading error !\n", cpu); + return -EIO; + } + DBG_LOTS(" CPU%d: amps = %d.%03d\n", cpu, FIX32TOPRINT((amps))); + + /* Calculate power */ + + /* Scale voltage and current raw sensor values according to fixed scales + * obtained in Darwin and calculate power from I and V + */ + *power = (((u64)volts) * ((u64)amps)) >> 16; + + DBG_LOTS(" CPU%d: power = %d.%03d\n", cpu, FIX32TOPRINT((*power))); + + return 0; + +} + +static void cpu_fans_tick(void) +{ + int err, cpu, i; + s32 speed, temp, power, t_max = 0; + + DBG_LOTS("* cpu fans_tick_split()\n"); + + for (cpu = 0; cpu < nr_chips; ++cpu) { + struct wf_cpu_pid_state *sp = &cpu_pid[cpu]; + + /* Read current speed */ + wf_control_get(cpu_fans[cpu][0], &sp->target); + + err = read_one_cpu_vals(cpu, &temp, &power); + if (err) { + failure_state |= FAILURE_SENSOR; + cpu_max_all_fans(); + return; + } + + /* Keep track of highest temp */ + t_max = max(t_max, temp); + + /* Handle possible overtemps */ + if (cpu_check_overtemp(t_max)) + return; + + /* Run PID */ + wf_cpu_pid_run(sp, power, temp); + + DBG_LOTS(" CPU%d: target = %d RPM\n", cpu, sp->target); + + /* Apply DIMMs clamp */ + speed = max(sp->target, dimms_output_clamp); + + /* Apply result to all cpu fans */ + for (i = 0; i < 3; i++) { + err = wf_control_set(cpu_fans[cpu][i], speed); + if (err) { + pr_warning("wf_rm31: Fan %s reports error %d\n", + cpu_fans[cpu][i]->name, err); + failure_state |= FAILURE_FAN; + } + } + } +} + +/* Implementation... */ +static int cpu_setup_pid(int cpu) +{ + struct wf_cpu_pid_param pid; + const struct mpu_data *mpu = cpu_mpu_data[cpu]; + s32 tmax, ttarget, ptarget; + int fmin, fmax, hsize; + + /* Get PID params from the appropriate MPU EEPROM */ + tmax = mpu->tmax << 16; + ttarget = mpu->ttarget << 16; + ptarget = ((s32)(mpu->pmaxh - mpu->padjmax)) << 16; + + DBG("wf_72: CPU%d ttarget = %d.%03d, tmax = %d.%03d\n", + cpu, FIX32TOPRINT(ttarget), FIX32TOPRINT(tmax)); + + /* We keep a global tmax for overtemp calculations */ + if (tmax < cpu_all_tmax) + cpu_all_tmax = tmax; + + /* Set PID min/max by using the rear fan min/max */ + fmin = wf_control_get_min(cpu_fans[cpu][0]); + fmax = wf_control_get_max(cpu_fans[cpu][0]); + DBG("wf_72: CPU%d max RPM range = [%d..%d]\n", cpu, fmin, fmax); + + /* History size */ + hsize = min_t(int, mpu->tguardband, WF_PID_MAX_HISTORY); + DBG("wf_72: CPU%d history size = %d\n", cpu, hsize); + + /* Initialize PID loop */ + pid.interval = 1; /* seconds */ + pid.history_len = hsize; + pid.gd = mpu->pid_gd; + pid.gp = mpu->pid_gp; + pid.gr = mpu->pid_gr; + pid.tmax = tmax; + pid.ttarget = ttarget; + pid.pmaxadj = ptarget; + pid.min = fmin; + pid.max = fmax; + + wf_cpu_pid_init(&cpu_pid[cpu], &pid); + cpu_pid[cpu].target = 4000; + + return 0; +} + +/* Backside/U3 fan */ +static struct wf_pid_param backside_param = { + .interval = 1, + .history_len = 2, + .gd = 0x00500000, + .gp = 0x0004cccc, + .gr = 0, + .itarget = 70 << 16, + .additive = 0, + .min = 20, + .max = 100, +}; + +/* DIMMs temperature (clamp the backside fan) */ +static struct wf_pid_param dimms_param = { + .interval = 1, + .history_len = 20, + .gd = 0, + .gp = 0, + .gr = 0x06553600, + .itarget = 50 << 16, + .additive = 0, + .min = 4000, + .max = 14000, +}; + +static void backside_fan_tick(void) +{ + s32 temp, dtemp; + int speed, dspeed, fan_min; + int err; + + if (!backside_fan || !backside_temp || !dimms_temp || !backside_tick) + return; + if (--backside_tick > 0) + return; + backside_tick = backside_pid.param.interval; + + DBG_LOTS("* backside fans tick\n"); + + /* Update fan speed from actual fans */ + err = wf_control_get(backside_fan, &speed); + if (!err) + backside_pid.target = speed; + + err = wf_sensor_get(backside_temp, &temp); + if (err) { + printk(KERN_WARNING "windfarm: U3 temp sensor error %d\n", + err); + failure_state |= FAILURE_SENSOR; + wf_control_set_max(backside_fan); + return; + } + speed = wf_pid_run(&backside_pid, temp); + + DBG_LOTS("backside PID temp=%d.%.3d speed=%d\n", + FIX32TOPRINT(temp), speed); + + err = wf_sensor_get(dimms_temp, &dtemp); + if (err) { + printk(KERN_WARNING "windfarm: DIMMs temp sensor error %d\n", + err); + failure_state |= FAILURE_SENSOR; + wf_control_set_max(backside_fan); + return; + } + dspeed = wf_pid_run(&dimms_pid, dtemp); + dimms_output_clamp = dspeed; + + fan_min = (dspeed * 100) / 14000; + fan_min = max(fan_min, backside_param.min); + speed = max(speed, fan_min); + + err = wf_control_set(backside_fan, speed); + if (err) { + printk(KERN_WARNING "windfarm: backside fan error %d\n", err); + failure_state |= FAILURE_FAN; + } +} + +static void backside_setup_pid(void) +{ + /* first time initialize things */ + s32 fmin = wf_control_get_min(backside_fan); + s32 fmax = wf_control_get_max(backside_fan); + struct wf_pid_param param; + + param = backside_param; + param.min = max(param.min, fmin); + param.max = min(param.max, fmax); + wf_pid_init(&backside_pid, ¶m); + + param = dimms_param; + wf_pid_init(&dimms_pid, ¶m); + + backside_tick = 1; + + pr_info("wf_rm31: Backside control loop started.\n"); +} + +/* Slots fan */ +static const struct wf_pid_param slots_param = { + .interval = 5, + .history_len = 2, + .gd = 30 << 20, + .gp = 5 << 20, + .gr = 0, + .itarget = 40 << 16, + .additive = 1, + .min = 300, + .max = 4000, +}; + +static void slots_fan_tick(void) +{ + s32 temp; + int speed; + int err; + + if (!slots_fan || !slots_temp || !slots_tick) + return; + if (--slots_tick > 0) + return; + slots_tick = slots_pid.param.interval; + + DBG_LOTS("* slots fans tick\n"); + + err = wf_sensor_get(slots_temp, &temp); + if (err) { + pr_warning("wf_rm31: slots temp sensor error %d\n", err); + failure_state |= FAILURE_SENSOR; + wf_control_set_max(slots_fan); + return; + } + speed = wf_pid_run(&slots_pid, temp); + + DBG_LOTS("slots PID temp=%d.%.3d speed=%d\n", + FIX32TOPRINT(temp), speed); + + slots_speed = speed; + err = wf_control_set(slots_fan, speed); + if (err) { + printk(KERN_WARNING "windfarm: slots bay fan error %d\n", err); + failure_state |= FAILURE_FAN; + } +} + +static void slots_setup_pid(void) +{ + /* first time initialize things */ + s32 fmin = wf_control_get_min(slots_fan); + s32 fmax = wf_control_get_max(slots_fan); + struct wf_pid_param param = slots_param; + + param.min = max(param.min, fmin); + param.max = min(param.max, fmax); + wf_pid_init(&slots_pid, ¶m); + slots_tick = 1; + + pr_info("wf_rm31: Slots control loop started.\n"); +} + +static void set_fail_state(void) +{ + cpu_max_all_fans(); + + if (backside_fan) + wf_control_set_max(backside_fan); + if (slots_fan) + wf_control_set_max(slots_fan); +} + +static void rm31_tick(void) +{ + int i, last_failure; + + if (!started) { + started = 1; + printk(KERN_INFO "windfarm: CPUs control loops started.\n"); + for (i = 0; i < nr_chips; ++i) { + if (cpu_setup_pid(i) < 0) { + failure_state = FAILURE_PERM; + set_fail_state(); + break; + } + } + DBG_LOTS("cpu_all_tmax=%d.%03d\n", FIX32TOPRINT(cpu_all_tmax)); + + backside_setup_pid(); + slots_setup_pid(); + +#ifdef HACKED_OVERTEMP + cpu_all_tmax = 60 << 16; +#endif + } + + /* Permanent failure, bail out */ + if (failure_state & FAILURE_PERM) + return; + + /* + * Clear all failure bits except low overtemp which will be eventually + * cleared by the control loop itself + */ + last_failure = failure_state; + failure_state &= FAILURE_LOW_OVERTEMP; + backside_fan_tick(); + slots_fan_tick(); + + /* We do CPUs last because they can be clamped high by + * DIMM temperature + */ + cpu_fans_tick(); + + DBG_LOTS(" last_failure: 0x%x, failure_state: %x\n", + last_failure, failure_state); + + /* Check for failures. Any failure causes cpufreq clamping */ + if (failure_state && last_failure == 0 && cpufreq_clamp) + wf_control_set_max(cpufreq_clamp); + if (failure_state == 0 && last_failure && cpufreq_clamp) + wf_control_set_min(cpufreq_clamp); + + /* That's it for now, we might want to deal with other failures + * differently in the future though + */ +} + +static void rm31_new_control(struct wf_control *ct) +{ + bool all_controls; + + if (!strcmp(ct->name, "cpu-fan-a-0")) + cpu_fans[0][0] = ct; + else if (!strcmp(ct->name, "cpu-fan-b-0")) + cpu_fans[0][1] = ct; + else if (!strcmp(ct->name, "cpu-fan-c-0")) + cpu_fans[0][2] = ct; + else if (!strcmp(ct->name, "cpu-fan-a-1")) + cpu_fans[1][0] = ct; + else if (!strcmp(ct->name, "cpu-fan-b-1")) + cpu_fans[1][1] = ct; + else if (!strcmp(ct->name, "cpu-fan-c-1")) + cpu_fans[1][2] = ct; + else if (!strcmp(ct->name, "backside-fan")) + backside_fan = ct; + else if (!strcmp(ct->name, "slots-fan")) + slots_fan = ct; + else if (!strcmp(ct->name, "cpufreq-clamp")) + cpufreq_clamp = ct; + + all_controls = + cpu_fans[0][0] && + cpu_fans[0][1] && + cpu_fans[0][2] && + backside_fan && + slots_fan; + if (nr_chips > 1) + all_controls &= + cpu_fans[1][0] && + cpu_fans[1][1] && + cpu_fans[1][2]; + have_all_controls = all_controls; +} + + +static void rm31_new_sensor(struct wf_sensor *sr) +{ + bool all_sensors; + + if (!strcmp(sr->name, "cpu-diode-temp-0")) + sens_cpu_temp[0] = sr; + else if (!strcmp(sr->name, "cpu-diode-temp-1")) + sens_cpu_temp[1] = sr; + else if (!strcmp(sr->name, "cpu-voltage-0")) + sens_cpu_volts[0] = sr; + else if (!strcmp(sr->name, "cpu-voltage-1")) + sens_cpu_volts[1] = sr; + else if (!strcmp(sr->name, "cpu-current-0")) + sens_cpu_amps[0] = sr; + else if (!strcmp(sr->name, "cpu-current-1")) + sens_cpu_amps[1] = sr; + else if (!strcmp(sr->name, "backside-temp")) + backside_temp = sr; + else if (!strcmp(sr->name, "slots-temp")) + slots_temp = sr; + else if (!strcmp(sr->name, "dimms-temp")) + dimms_temp = sr; + + all_sensors = + sens_cpu_temp[0] && + sens_cpu_volts[0] && + sens_cpu_amps[0] && + backside_temp && + slots_temp && + dimms_temp; + if (nr_chips > 1) + all_sensors &= + sens_cpu_temp[1] && + sens_cpu_volts[1] && + sens_cpu_amps[1]; + + have_all_sensors = all_sensors; +} + +static int rm31_wf_notify(struct notifier_block *self, + unsigned long event, void *data) +{ + switch (event) { + case WF_EVENT_NEW_SENSOR: + rm31_new_sensor(data); + break; + case WF_EVENT_NEW_CONTROL: + rm31_new_control(data); + break; + case WF_EVENT_TICK: + if (have_all_controls && have_all_sensors) + rm31_tick(); + } + return 0; +} + +static struct notifier_block rm31_events = { + .notifier_call = rm31_wf_notify, +}; + +static int wf_rm31_probe(struct platform_device *dev) +{ + wf_register_client(&rm31_events); + return 0; +} + +static int __devexit wf_rm31_remove(struct platform_device *dev) +{ + wf_unregister_client(&rm31_events); + + /* should release all sensors and controls */ + return 0; +} + +static struct platform_driver wf_rm31_driver = { + .probe = wf_rm31_probe, + .remove = wf_rm31_remove, + .driver = { + .name = "windfarm", + .owner = THIS_MODULE, + }, +}; + +static int __init wf_rm31_init(void) +{ + struct device_node *cpu; + int i; + + if (!of_machine_is_compatible("RackMac3,1")) + return -ENODEV; + + /* Count the number of CPU cores */ + nr_chips = 0; + for (cpu = NULL; (cpu = of_find_node_by_type(cpu, "cpu")) != NULL; ) + ++nr_chips; + if (nr_chips > NR_CHIPS) + nr_chips = NR_CHIPS; + + pr_info("windfarm: Initializing for desktop G5 with %d chips\n", + nr_chips); + + /* Get MPU data for each CPU */ + for (i = 0; i < nr_chips; i++) { + cpu_mpu_data[i] = wf_get_mpu(i); + if (!cpu_mpu_data[i]) { + pr_err("wf_rm31: Failed to find MPU data for CPU %d\n", i); + return -ENXIO; + } + } + +#ifdef MODULE + request_module("windfarm_fcu_controls"); + request_module("windfarm_lm75_sensor"); + request_module("windfarm_lm87_sensor"); + request_module("windfarm_ad7417_sensor"); + request_module("windfarm_max6690_sensor"); + request_module("windfarm_cpufreq_clamp"); +#endif /* MODULE */ + + platform_driver_register(&wf_rm31_driver); + return 0; +} + +static void __exit wf_rm31_exit(void) +{ + platform_driver_unregister(&wf_rm31_driver); +} + +module_init(wf_rm31_init); +module_exit(wf_rm31_exit); + +MODULE_AUTHOR("Benjamin Herrenschmidt <[email protected]>"); +MODULE_DESCRIPTION("Thermal control for Xserve G5"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:windfarm"); diff --git a/drivers/macintosh/windfarm_smu_controls.c b/drivers/macintosh/windfarm_smu_controls.c index 3c2be5193fd5..c155a54e8638 100644 --- a/drivers/macintosh/windfarm_smu_controls.c +++ b/drivers/macintosh/windfarm_smu_controls.c @@ -172,7 +172,6 @@ static struct smu_fan_control *smu_fan_create(struct device_node *node, fct->fan_type = pwm_fan; fct->ctrl.type = pwm_fan ? WF_CONTROL_PWM_FAN : WF_CONTROL_RPM_FAN; - sysfs_attr_init(&fct->ctrl.attr.attr); /* We use the name & location here the same way we do for SMU sensors, * see the comment in windfarm_smu_sensors.c. The locations are a bit diff --git a/drivers/macintosh/windfarm_smu_sat.c b/drivers/macintosh/windfarm_smu_sat.c index 65a8ff3e1f8e..426e810233d7 100644 --- a/drivers/macintosh/windfarm_smu_sat.c +++ b/drivers/macintosh/windfarm_smu_sat.c @@ -20,7 +20,7 @@ #include "windfarm.h" -#define VERSION "0.2" +#define VERSION "1.0" #define DEBUG @@ -34,11 +34,12 @@ #define MAX_AGE msecs_to_jiffies(800) struct wf_sat { + struct kref ref; int nr; - atomic_t refcnt; struct mutex mutex; unsigned long last_read; /* jiffies when cache last updated */ u8 cache[16]; + struct list_head sensors; struct i2c_client *i2c; struct device_node *node; }; @@ -46,11 +47,12 @@ struct wf_sat { static struct wf_sat *sats[2]; struct wf_sat_sensor { - int index; - int index2; /* used for power sensors */ - int shift; - struct wf_sat *sat; - struct wf_sensor sens; + struct list_head link; + int index; + int index2; /* used for power sensors */ + int shift; + struct wf_sat *sat; + struct wf_sensor sens; }; #define wf_to_sat(c) container_of(c, struct wf_sat_sensor, sens) @@ -142,7 +144,7 @@ static int wf_sat_read_cache(struct wf_sat *sat) return 0; } -static int wf_sat_get(struct wf_sensor *sr, s32 *value) +static int wf_sat_sensor_get(struct wf_sensor *sr, s32 *value) { struct wf_sat_sensor *sens = wf_to_sat(sr); struct wf_sat *sat = sens->sat; @@ -175,62 +177,34 @@ static int wf_sat_get(struct wf_sensor *sr, s32 *value) return err; } -static void wf_sat_release(struct wf_sensor *sr) +static void wf_sat_release(struct kref *ref) +{ + struct wf_sat *sat = container_of(ref, struct wf_sat, ref); + + if (sat->nr >= 0) + sats[sat->nr] = NULL; + kfree(sat); +} + +static void wf_sat_sensor_release(struct wf_sensor *sr) { struct wf_sat_sensor *sens = wf_to_sat(sr); struct wf_sat *sat = sens->sat; - if (atomic_dec_and_test(&sat->refcnt)) { - if (sat->nr >= 0) - sats[sat->nr] = NULL; - kfree(sat); - } kfree(sens); + kref_put(&sat->ref, wf_sat_release); } static struct wf_sensor_ops wf_sat_ops = { - .get_value = wf_sat_get, - .release = wf_sat_release, + .get_value = wf_sat_sensor_get, + .release = wf_sat_sensor_release, .owner = THIS_MODULE, }; -static struct i2c_driver wf_sat_driver; - -static void wf_sat_create(struct i2c_adapter *adapter, struct device_node *dev) -{ - struct i2c_board_info info; - struct i2c_client *client; - const u32 *reg; - u8 addr; - - reg = of_get_property(dev, "reg", NULL); - if (reg == NULL) - return; - addr = *reg; - DBG(KERN_DEBUG "wf_sat: creating sat at address %x\n", addr); - - memset(&info, 0, sizeof(struct i2c_board_info)); - info.addr = (addr >> 1) & 0x7f; - info.platform_data = dev; - strlcpy(info.type, "wf_sat", I2C_NAME_SIZE); - - client = i2c_new_device(adapter, &info); - if (client == NULL) { - printk(KERN_ERR "windfarm: failed to attach smu-sat to i2c\n"); - return; - } - - /* - * Let i2c-core delete that device on driver removal. - * This is safe because i2c-core holds the core_lock mutex for us. - */ - list_add_tail(&client->detected, &wf_sat_driver.clients); -} - static int wf_sat_probe(struct i2c_client *client, const struct i2c_device_id *id) { - struct device_node *dev = client->dev.platform_data; + struct device_node *dev = client->dev.of_node; struct wf_sat *sat; struct wf_sat_sensor *sens; const u32 *reg; @@ -246,9 +220,10 @@ static int wf_sat_probe(struct i2c_client *client, return -ENOMEM; sat->nr = -1; sat->node = of_node_get(dev); - atomic_set(&sat->refcnt, 0); + kref_init(&sat->ref); mutex_init(&sat->mutex); sat->i2c = client; + INIT_LIST_HEAD(&sat->sensors); i2c_set_clientdata(client, sat); vsens[0] = vsens[1] = -1; @@ -310,14 +285,15 @@ static int wf_sat_probe(struct i2c_client *client, sens->index2 = -1; sens->shift = shift; sens->sat = sat; - atomic_inc(&sat->refcnt); sens->sens.ops = &wf_sat_ops; sens->sens.name = (char *) (sens + 1); - snprintf(sens->sens.name, 16, "%s-%d", name, cpu); + snprintf((char *)sens->sens.name, 16, "%s-%d", name, cpu); - if (wf_register_sensor(&sens->sens)) { - atomic_dec(&sat->refcnt); + if (wf_register_sensor(&sens->sens)) kfree(sens); + else { + list_add(&sens->link, &sat->sensors); + kref_get(&sat->ref); } } @@ -336,14 +312,15 @@ static int wf_sat_probe(struct i2c_client *client, sens->index2 = isens[core]; sens->shift = 0; sens->sat = sat; - atomic_inc(&sat->refcnt); sens->sens.ops = &wf_sat_ops; sens->sens.name = (char *) (sens + 1); - snprintf(sens->sens.name, 16, "cpu-power-%d", cpu); + snprintf((char *)sens->sens.name, 16, "cpu-power-%d", cpu); - if (wf_register_sensor(&sens->sens)) { - atomic_dec(&sat->refcnt); + if (wf_register_sensor(&sens->sens)) kfree(sens); + else { + list_add(&sens->link, &sat->sensors); + kref_get(&sat->ref); } } @@ -353,42 +330,35 @@ static int wf_sat_probe(struct i2c_client *client, return 0; } -static int wf_sat_attach(struct i2c_adapter *adapter) -{ - struct device_node *busnode, *dev = NULL; - struct pmac_i2c_bus *bus; - - bus = pmac_i2c_adapter_to_bus(adapter); - if (bus == NULL) - return -ENODEV; - busnode = pmac_i2c_get_bus_node(bus); - - while ((dev = of_get_next_child(busnode, dev)) != NULL) - if (of_device_is_compatible(dev, "smu-sat")) - wf_sat_create(adapter, dev); - return 0; -} - static int wf_sat_remove(struct i2c_client *client) { struct wf_sat *sat = i2c_get_clientdata(client); + struct wf_sat_sensor *sens; - /* XXX TODO */ - + /* release sensors */ + while(!list_empty(&sat->sensors)) { + sens = list_first_entry(&sat->sensors, + struct wf_sat_sensor, link); + list_del(&sens->link); + wf_unregister_sensor(&sens->sens); + } sat->i2c = NULL; + i2c_set_clientdata(client, NULL); + kref_put(&sat->ref, wf_sat_release); + return 0; } static const struct i2c_device_id wf_sat_id[] = { - { "wf_sat", 0 }, + { "MAC,smu-sat", 0 }, { } }; +MODULE_DEVICE_TABLE(i2c, wf_sat_id); static struct i2c_driver wf_sat_driver = { .driver = { .name = "wf_smu_sat", }, - .attach_adapter = wf_sat_attach, .probe = wf_sat_probe, .remove = wf_sat_remove, .id_table = wf_sat_id, @@ -399,15 +369,13 @@ static int __init sat_sensors_init(void) return i2c_add_driver(&wf_sat_driver); } -#if 0 /* uncomment when module_exit() below is uncommented */ static void __exit sat_sensors_exit(void) { i2c_del_driver(&wf_sat_driver); } -#endif module_init(sat_sensors_init); -/*module_exit(sat_sensors_exit); Uncomment when cleanup is implemented */ +module_exit(sat_sensors_exit); MODULE_AUTHOR("Paul Mackerras <[email protected]>"); MODULE_DESCRIPTION("SMU satellite sensors for PowerMac thermal control"); diff --git a/drivers/mca/Kconfig b/drivers/mca/Kconfig deleted file mode 100644 index a7a0220ab4bd..000000000000 --- a/drivers/mca/Kconfig +++ /dev/null @@ -1,14 +0,0 @@ -config MCA_LEGACY - bool "Legacy MCA API Support" - depends on MCA - help - This compiles in support for the old slot based MCA API. If you - have an unconverted MCA driver, you will need to say Y here. It - is safe to say Y anyway. - -config MCA_PROC_FS - bool "Support for the mca entry in /proc" - depends on MCA_LEGACY && PROC_FS - help - If you want the old style /proc/mca directory in addition to the - new style sysfs say Y here. diff --git a/drivers/mca/Makefile b/drivers/mca/Makefile deleted file mode 100644 index 0794b122520e..000000000000 --- a/drivers/mca/Makefile +++ /dev/null @@ -1,7 +0,0 @@ -# Makefile for the Linux MCA bus support - -obj-y := mca-bus.o mca-device.o mca-driver.o - -obj-$(CONFIG_MCA_PROC_FS) += mca-proc.o -obj-$(CONFIG_MCA_LEGACY) += mca-legacy.o - diff --git a/drivers/mca/mca-bus.c b/drivers/mca/mca-bus.c deleted file mode 100644 index ada5ebbaa255..000000000000 --- a/drivers/mca/mca-bus.c +++ /dev/null @@ -1,169 +0,0 @@ -/* -*- mode: c; c-basic-offset: 8 -*- */ - -/* - * MCA bus support functions for sysfs. - * - * (C) 2002 James Bottomley <[email protected]> - * -**----------------------------------------------------------------------------- -** -** This program is free software; you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation; either version 2 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program; if not, write to the Free Software -** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -** -**----------------------------------------------------------------------------- - */ - -#include <linux/kernel.h> -#include <linux/device.h> -#include <linux/mca.h> -#include <linux/module.h> -#include <linux/init.h> -#include <linux/slab.h> - -/* Very few machines have more than one MCA bus. However, there are - * those that do (Voyager 35xx/5xxx), so we do it this way for future - * expansion. None that I know have more than 2 */ -static struct mca_bus *mca_root_busses[MAX_MCA_BUSSES]; - -#define MCA_DEVINFO(i,s) { .pos = i, .name = s } - -struct mca_device_info { - short pos_id; /* the 2 byte pos id for this card */ - char name[50]; -}; - -static int mca_bus_match (struct device *dev, struct device_driver *drv) -{ - struct mca_device *mca_dev = to_mca_device (dev); - struct mca_driver *mca_drv = to_mca_driver (drv); - const unsigned short *mca_ids = mca_drv->id_table; - int i = 0; - - if (mca_ids) { - for(i = 0; mca_ids[i]; i++) { - if (mca_ids[i] == mca_dev->pos_id) { - mca_dev->index = i; - return 1; - } - } - } - /* If the integrated id is present, treat it as though it were an - * additional id in the id_table (it can't be because by definition, - * integrated id's overflow a short */ - if (mca_drv->integrated_id && mca_dev->pos_id == - mca_drv->integrated_id) { - mca_dev->index = i; - return 1; - } - return 0; -} - -struct bus_type mca_bus_type = { - .name = "MCA", - .match = mca_bus_match, -}; -EXPORT_SYMBOL (mca_bus_type); - -static ssize_t mca_show_pos_id(struct device *dev, struct device_attribute *attr, char *buf) -{ - /* four digits, \n and trailing \0 */ - struct mca_device *mca_dev = to_mca_device(dev); - int len; - - if(mca_dev->pos_id < MCA_DUMMY_POS_START) - len = sprintf(buf, "%04x\n", mca_dev->pos_id); - else - len = sprintf(buf, "none\n"); - return len; -} -static ssize_t mca_show_pos(struct device *dev, struct device_attribute *attr, char *buf) -{ - /* enough for 8 two byte hex chars plus space and new line */ - int j, len=0; - struct mca_device *mca_dev = to_mca_device(dev); - - for(j=0; j<8; j++) - len += sprintf(buf+len, "%02x ", mca_dev->pos[j]); - /* change last trailing space to new line */ - buf[len-1] = '\n'; - return len; -} - -static DEVICE_ATTR(id, S_IRUGO, mca_show_pos_id, NULL); -static DEVICE_ATTR(pos, S_IRUGO, mca_show_pos, NULL); - -int __init mca_register_device(int bus, struct mca_device *mca_dev) -{ - struct mca_bus *mca_bus = mca_root_busses[bus]; - int rc; - - mca_dev->dev.parent = &mca_bus->dev; - mca_dev->dev.bus = &mca_bus_type; - dev_set_name(&mca_dev->dev, "%02d:%02X", bus, mca_dev->slot); - mca_dev->dma_mask = mca_bus->default_dma_mask; - mca_dev->dev.dma_mask = &mca_dev->dma_mask; - mca_dev->dev.coherent_dma_mask = mca_dev->dma_mask; - - rc = device_register(&mca_dev->dev); - if (rc) - goto err_out; - - rc = device_create_file(&mca_dev->dev, &dev_attr_id); - if (rc) goto err_out_devreg; - rc = device_create_file(&mca_dev->dev, &dev_attr_pos); - if (rc) goto err_out_id; - - return 1; - -err_out_id: - device_remove_file(&mca_dev->dev, &dev_attr_id); -err_out_devreg: - device_unregister(&mca_dev->dev); -err_out: - return 0; -} - -/* */ -struct mca_bus * __devinit mca_attach_bus(int bus) -{ - struct mca_bus *mca_bus; - - if (unlikely(mca_root_busses[bus] != NULL)) { - /* This should never happen, but just in case */ - printk(KERN_EMERG "MCA tried to add already existing bus %d\n", - bus); - dump_stack(); - return NULL; - } - - mca_bus = kzalloc(sizeof(struct mca_bus), GFP_KERNEL); - if (!mca_bus) - return NULL; - - dev_set_name(&mca_bus->dev, "mca%d", bus); - sprintf(mca_bus->name,"Host %s MCA Bridge", bus ? "Secondary" : "Primary"); - if (device_register(&mca_bus->dev)) { - kfree(mca_bus); - return NULL; - } - - mca_root_busses[bus] = mca_bus; - - return mca_bus; -} - -int __init mca_system_init (void) -{ - return bus_register(&mca_bus_type); -} diff --git a/drivers/mca/mca-device.c b/drivers/mca/mca-device.c deleted file mode 100644 index e7adf89fae41..000000000000 --- a/drivers/mca/mca-device.c +++ /dev/null @@ -1,218 +0,0 @@ -/* -*- mode: c; c-basic-offset: 8 -*- */ - -/* - * MCA device support functions - * - * These functions support the ongoing device access API. - * - * (C) 2002 James Bottomley <[email protected]> - * -**----------------------------------------------------------------------------- -** -** This program is free software; you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation; either version 2 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program; if not, write to the Free Software -** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -** -**----------------------------------------------------------------------------- - */ - -#include <linux/module.h> -#include <linux/device.h> -#include <linux/mca.h> -#include <linux/string.h> - -/** - * mca_device_read_stored_pos - read POS register from stored data - * @mca_dev: device to read from - * @reg: register to read from - * - * Fetch a POS value that was stored at boot time by the kernel - * when it scanned the MCA space. The register value is returned. - * Missing or invalid registers report 0. - */ -unsigned char mca_device_read_stored_pos(struct mca_device *mca_dev, int reg) -{ - if(reg < 0 || reg >= 8) - return 0; - - return mca_dev->pos[reg]; -} -EXPORT_SYMBOL(mca_device_read_stored_pos); - -/** - * mca_device_read_pos - read POS register from card - * @mca_dev: device to read from - * @reg: register to read from - * - * Fetch a POS value directly from the hardware to obtain the - * current value. This is much slower than - * mca_device_read_stored_pos and may not be invoked from - * interrupt context. It handles the deep magic required for - * onboard devices transparently. - */ -unsigned char mca_device_read_pos(struct mca_device *mca_dev, int reg) -{ - struct mca_bus *mca_bus = to_mca_bus(mca_dev->dev.parent); - - return mca_bus->f.mca_read_pos(mca_dev, reg); - - return mca_dev->pos[reg]; -} -EXPORT_SYMBOL(mca_device_read_pos); - - -/** - * mca_device_write_pos - read POS register from card - * @mca_dev: device to write pos register to - * @reg: register to write to - * @byte: byte to write to the POS registers - * - * Store a POS value directly to the hardware. You should not - * normally need to use this function and should have a very good - * knowledge of MCA bus before you do so. Doing this wrongly can - * damage the hardware. - * - * This function may not be used from interrupt context. - * - */ -void mca_device_write_pos(struct mca_device *mca_dev, int reg, - unsigned char byte) -{ - struct mca_bus *mca_bus = to_mca_bus(mca_dev->dev.parent); - - mca_bus->f.mca_write_pos(mca_dev, reg, byte); -} -EXPORT_SYMBOL(mca_device_write_pos); - -/** - * mca_device_transform_irq - transform the ADF obtained IRQ - * @mca_device: device whose irq needs transforming - * @irq: input irq from ADF - * - * MCA Adapter Definition Files (ADF) contain irq, ioport, memory - * etc. definitions. In systems with more than one bus, these need - * to be transformed through bus mapping functions to get the real - * system global quantities. - * - * This function transforms the interrupt number and returns the - * transformed system global interrupt - */ -int mca_device_transform_irq(struct mca_device *mca_dev, int irq) -{ - struct mca_bus *mca_bus = to_mca_bus(mca_dev->dev.parent); - - return mca_bus->f.mca_transform_irq(mca_dev, irq); -} -EXPORT_SYMBOL(mca_device_transform_irq); - -/** - * mca_device_transform_ioport - transform the ADF obtained I/O port - * @mca_device: device whose port needs transforming - * @ioport: input I/O port from ADF - * - * MCA Adapter Definition Files (ADF) contain irq, ioport, memory - * etc. definitions. In systems with more than one bus, these need - * to be transformed through bus mapping functions to get the real - * system global quantities. - * - * This function transforms the I/O port number and returns the - * transformed system global port number. - * - * This transformation can be assumed to be linear for port ranges. - */ -int mca_device_transform_ioport(struct mca_device *mca_dev, int port) -{ - struct mca_bus *mca_bus = to_mca_bus(mca_dev->dev.parent); - - return mca_bus->f.mca_transform_ioport(mca_dev, port); -} -EXPORT_SYMBOL(mca_device_transform_ioport); - -/** - * mca_device_transform_memory - transform the ADF obtained memory - * @mca_device: device whose memory region needs transforming - * @mem: memory region start from ADF - * - * MCA Adapter Definition Files (ADF) contain irq, ioport, memory - * etc. definitions. In systems with more than one bus, these need - * to be transformed through bus mapping functions to get the real - * system global quantities. - * - * This function transforms the memory region start and returns the - * transformed system global memory region (physical). - * - * This transformation can be assumed to be linear for region ranges. - */ -void *mca_device_transform_memory(struct mca_device *mca_dev, void *mem) -{ - struct mca_bus *mca_bus = to_mca_bus(mca_dev->dev.parent); - - return mca_bus->f.mca_transform_memory(mca_dev, mem); -} -EXPORT_SYMBOL(mca_device_transform_memory); - - -/** - * mca_device_claimed - check if claimed by driver - * @mca_dev: device to check - * - * Returns 1 if the slot has been claimed by a driver - */ - -int mca_device_claimed(struct mca_device *mca_dev) -{ - return mca_dev->driver_loaded; -} -EXPORT_SYMBOL(mca_device_claimed); - -/** - * mca_device_set_claim - set the claim value of the driver - * @mca_dev: device to set value for - * @val: claim value to set (1 claimed, 0 unclaimed) - */ -void mca_device_set_claim(struct mca_device *mca_dev, int val) -{ - mca_dev->driver_loaded = val; -} -EXPORT_SYMBOL(mca_device_set_claim); - -/** - * mca_device_status - get the status of the device - * @mca_device: device to get - * - * returns an enumeration of the device status: - * - * MCA_ADAPTER_NORMAL adapter is OK. - * MCA_ADAPTER_NONE no adapter at device (should never happen). - * MCA_ADAPTER_DISABLED adapter is disabled. - * MCA_ADAPTER_ERROR adapter cannot be initialised. - */ -enum MCA_AdapterStatus mca_device_status(struct mca_device *mca_dev) -{ - return mca_dev->status; -} -EXPORT_SYMBOL(mca_device_status); - -/** - * mca_device_set_name - set the name of the device - * @mca_device: device to set the name of - * @name: name to set - */ -void mca_device_set_name(struct mca_device *mca_dev, const char *name) -{ - if(!mca_dev) - return; - - strlcpy(mca_dev->name, name, sizeof(mca_dev->name)); -} -EXPORT_SYMBOL(mca_device_set_name); diff --git a/drivers/mca/mca-driver.c b/drivers/mca/mca-driver.c deleted file mode 100644 index 32cd39bcc715..000000000000 --- a/drivers/mca/mca-driver.c +++ /dev/null @@ -1,63 +0,0 @@ -/* -*- mode: c; c-basic-offset: 8 -*- */ - -/* - * MCA driver support functions for sysfs. - * - * (C) 2002 James Bottomley <[email protected]> - * -**----------------------------------------------------------------------------- -** -** This program is free software; you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation; either version 2 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program; if not, write to the Free Software -** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -** -**----------------------------------------------------------------------------- - */ - -#include <linux/device.h> -#include <linux/mca.h> -#include <linux/module.h> - -int mca_register_driver(struct mca_driver *mca_drv) -{ - int r; - - if (MCA_bus) { - mca_drv->driver.bus = &mca_bus_type; - if ((r = driver_register(&mca_drv->driver)) < 0) - return r; - mca_drv->integrated_id = 0; - } - - return 0; -} -EXPORT_SYMBOL(mca_register_driver); - -int mca_register_driver_integrated(struct mca_driver *mca_driver, - int integrated_id) -{ - int r = mca_register_driver(mca_driver); - - if (!r) - mca_driver->integrated_id = integrated_id; - - return r; -} -EXPORT_SYMBOL(mca_register_driver_integrated); - -void mca_unregister_driver(struct mca_driver *mca_drv) -{ - if (MCA_bus) - driver_unregister(&mca_drv->driver); -} -EXPORT_SYMBOL(mca_unregister_driver); diff --git a/drivers/mca/mca-legacy.c b/drivers/mca/mca-legacy.c deleted file mode 100644 index 494f0c2001f5..000000000000 --- a/drivers/mca/mca-legacy.c +++ /dev/null @@ -1,329 +0,0 @@ -/* -*- mode: c; c-basic-offset: 8 -*- */ - -/* - * MCA bus support functions for legacy (2.4) API. - * - * Legacy API means the API that operates in terms of MCA slot number - * - * (C) 2002 James Bottomley <[email protected]> - * -**----------------------------------------------------------------------------- -** -** This program is free software; you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation; either version 2 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program; if not, write to the Free Software -** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -** -**----------------------------------------------------------------------------- - */ - -#include <linux/module.h> -#include <linux/device.h> -#include <linux/mca-legacy.h> -#include <asm/io.h> - -/* NOTE: This structure is stack allocated */ -struct mca_find_adapter_info { - int id; - int slot; - struct mca_device *mca_dev; -}; - -/* The purpose of this iterator is to loop over all the devices and - * find the one with the smallest slot number that's just greater than - * or equal to the required slot with a matching id */ -static int mca_find_adapter_callback(struct device *dev, void *data) -{ - struct mca_find_adapter_info *info = data; - struct mca_device *mca_dev = to_mca_device(dev); - - if(mca_dev->pos_id != info->id) - return 0; - - if(mca_dev->slot < info->slot) - return 0; - - if(!info->mca_dev || info->mca_dev->slot >= mca_dev->slot) - info->mca_dev = mca_dev; - - return 0; -} - -/** - * mca_find_adapter - scan for adapters - * @id: MCA identification to search for - * @start: starting slot - * - * Search the MCA configuration for adapters matching the 16bit - * ID given. The first time it should be called with start as zero - * and then further calls made passing the return value of the - * previous call until %MCA_NOTFOUND is returned. - * - * Disabled adapters are not reported. - */ - -int mca_find_adapter(int id, int start) -{ - struct mca_find_adapter_info info; - - if(id == 0xffff) - return MCA_NOTFOUND; - - info.slot = start; - info.id = id; - info.mca_dev = NULL; - - for(;;) { - bus_for_each_dev(&mca_bus_type, NULL, &info, mca_find_adapter_callback); - - if(info.mca_dev == NULL) - return MCA_NOTFOUND; - - if(info.mca_dev->status != MCA_ADAPTER_DISABLED) - break; - - /* OK, found adapter but it was disabled. Go around - * again, excluding the slot we just found */ - - info.slot = info.mca_dev->slot + 1; - info.mca_dev = NULL; - } - - return info.mca_dev->slot; -} -EXPORT_SYMBOL(mca_find_adapter); - -/*--------------------------------------------------------------------*/ - -/** - * mca_find_unused_adapter - scan for unused adapters - * @id: MCA identification to search for - * @start: starting slot - * - * Search the MCA configuration for adapters matching the 16bit - * ID given. The first time it should be called with start as zero - * and then further calls made passing the return value of the - * previous call until %MCA_NOTFOUND is returned. - * - * Adapters that have been claimed by drivers and those that - * are disabled are not reported. This function thus allows a driver - * to scan for further cards when some may already be driven. - */ - -int mca_find_unused_adapter(int id, int start) -{ - struct mca_find_adapter_info info = { 0 }; - - if (!MCA_bus || id == 0xffff) - return MCA_NOTFOUND; - - info.slot = start; - info.id = id; - info.mca_dev = NULL; - - for(;;) { - bus_for_each_dev(&mca_bus_type, NULL, &info, mca_find_adapter_callback); - - if(info.mca_dev == NULL) - return MCA_NOTFOUND; - - if(info.mca_dev->status != MCA_ADAPTER_DISABLED - && !info.mca_dev->driver_loaded) - break; - - /* OK, found adapter but it was disabled or already in - * use. Go around again, excluding the slot we just - * found */ - - info.slot = info.mca_dev->slot + 1; - info.mca_dev = NULL; - } - - return info.mca_dev->slot; -} -EXPORT_SYMBOL(mca_find_unused_adapter); - -/* NOTE: stack allocated structure */ -struct mca_find_device_by_slot_info { - int slot; - struct mca_device *mca_dev; -}; - -static int mca_find_device_by_slot_callback(struct device *dev, void *data) -{ - struct mca_find_device_by_slot_info *info = data; - struct mca_device *mca_dev = to_mca_device(dev); - - if(mca_dev->slot == info->slot) - info->mca_dev = mca_dev; - - return 0; -} - -struct mca_device *mca_find_device_by_slot(int slot) -{ - struct mca_find_device_by_slot_info info; - - info.slot = slot; - info.mca_dev = NULL; - - bus_for_each_dev(&mca_bus_type, NULL, &info, mca_find_device_by_slot_callback); - - return info.mca_dev; -} - -/** - * mca_read_stored_pos - read POS register from boot data - * @slot: slot number to read from - * @reg: register to read from - * - * Fetch a POS value that was stored at boot time by the kernel - * when it scanned the MCA space. The register value is returned. - * Missing or invalid registers report 0. - */ -unsigned char mca_read_stored_pos(int slot, int reg) -{ - struct mca_device *mca_dev = mca_find_device_by_slot(slot); - - if(!mca_dev) - return 0; - - return mca_device_read_stored_pos(mca_dev, reg); -} -EXPORT_SYMBOL(mca_read_stored_pos); - - -/** - * mca_read_pos - read POS register from card - * @slot: slot number to read from - * @reg: register to read from - * - * Fetch a POS value directly from the hardware to obtain the - * current value. This is much slower than mca_read_stored_pos and - * may not be invoked from interrupt context. It handles the - * deep magic required for onboard devices transparently. - */ - -unsigned char mca_read_pos(int slot, int reg) -{ - struct mca_device *mca_dev = mca_find_device_by_slot(slot); - - if(!mca_dev) - return 0; - - return mca_device_read_pos(mca_dev, reg); -} -EXPORT_SYMBOL(mca_read_pos); - - -/** - * mca_write_pos - read POS register from card - * @slot: slot number to read from - * @reg: register to read from - * @byte: byte to write to the POS registers - * - * Store a POS value directly from the hardware. You should not - * normally need to use this function and should have a very good - * knowledge of MCA bus before you do so. Doing this wrongly can - * damage the hardware. - * - * This function may not be used from interrupt context. - * - * Note that this a technically a Bad Thing, as IBM tech stuff says - * you should only set POS values through their utilities. - * However, some devices such as the 3c523 recommend that you write - * back some data to make sure the configuration is consistent. - * I'd say that IBM is right, but I like my drivers to work. - * - * This function can't do checks to see if multiple devices end up - * with the same resources, so you might see magic smoke if someone - * screws up. - */ - -void mca_write_pos(int slot, int reg, unsigned char byte) -{ - struct mca_device *mca_dev = mca_find_device_by_slot(slot); - - if(!mca_dev) - return; - - mca_device_write_pos(mca_dev, reg, byte); -} -EXPORT_SYMBOL(mca_write_pos); - -/** - * mca_set_adapter_name - Set the description of the card - * @slot: slot to name - * @name: text string for the namen - * - * This function sets the name reported via /proc for this - * adapter slot. This is for user information only. Setting a - * name deletes any previous name. - */ - -void mca_set_adapter_name(int slot, char* name) -{ - struct mca_device *mca_dev = mca_find_device_by_slot(slot); - - if(!mca_dev) - return; - - mca_device_set_name(mca_dev, name); -} -EXPORT_SYMBOL(mca_set_adapter_name); - -/** - * mca_mark_as_used - claim an MCA device - * @slot: slot to claim - * FIXME: should we make this threadsafe - * - * Claim an MCA slot for a device driver. If the - * slot is already taken the function returns 1, - * if it is not taken it is claimed and 0 is - * returned. - */ - -int mca_mark_as_used(int slot) -{ - struct mca_device *mca_dev = mca_find_device_by_slot(slot); - - if(!mca_dev) - /* FIXME: this is actually a severe error */ - return 1; - - if(mca_device_claimed(mca_dev)) - return 1; - - mca_device_set_claim(mca_dev, 1); - - return 0; -} -EXPORT_SYMBOL(mca_mark_as_used); - -/** - * mca_mark_as_unused - release an MCA device - * @slot: slot to claim - * - * Release the slot for other drives to use. - */ - -void mca_mark_as_unused(int slot) -{ - struct mca_device *mca_dev = mca_find_device_by_slot(slot); - - if(!mca_dev) - return; - - mca_device_set_claim(mca_dev, 0); -} -EXPORT_SYMBOL(mca_mark_as_unused); - diff --git a/drivers/mca/mca-proc.c b/drivers/mca/mca-proc.c deleted file mode 100644 index 81ea0d377bf4..000000000000 --- a/drivers/mca/mca-proc.c +++ /dev/null @@ -1,249 +0,0 @@ -/* -*- mode: c; c-basic-offset: 8 -*- */ - -/* - * MCA bus support functions for the proc fs. - * - * NOTE: this code *requires* the legacy MCA api. - * - * Legacy API means the API that operates in terms of MCA slot number - * - * (C) 2002 James Bottomley <[email protected]> - * -**----------------------------------------------------------------------------- -** -** This program is free software; you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation; either version 2 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program; if not, write to the Free Software -** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -** -**----------------------------------------------------------------------------- - */ -#include <linux/module.h> -#include <linux/init.h> -#include <linux/proc_fs.h> -#include <linux/mca.h> - -static int get_mca_info_helper(struct mca_device *mca_dev, char *page, int len) -{ - int j; - - for(j=0; j<8; j++) - len += sprintf(page+len, "%02x ", - mca_dev ? mca_dev->pos[j] : 0xff); - len += sprintf(page+len, " %s\n", mca_dev ? mca_dev->name : ""); - return len; -} - -static int get_mca_info(char *page, char **start, off_t off, - int count, int *eof, void *data) -{ - int i, len = 0; - - if(MCA_bus) { - struct mca_device *mca_dev; - /* Format POS registers of eight MCA slots */ - - for(i=0; i<MCA_MAX_SLOT_NR; i++) { - mca_dev = mca_find_device_by_slot(i); - - len += sprintf(page+len, "Slot %d: ", i+1); - len = get_mca_info_helper(mca_dev, page, len); - } - - /* Format POS registers of integrated video subsystem */ - - mca_dev = mca_find_device_by_slot(MCA_INTEGVIDEO); - len += sprintf(page+len, "Video : "); - len = get_mca_info_helper(mca_dev, page, len); - - /* Format POS registers of integrated SCSI subsystem */ - - mca_dev = mca_find_device_by_slot(MCA_INTEGSCSI); - len += sprintf(page+len, "SCSI : "); - len = get_mca_info_helper(mca_dev, page, len); - - /* Format POS registers of motherboard */ - - mca_dev = mca_find_device_by_slot(MCA_MOTHERBOARD); - len += sprintf(page+len, "Planar: "); - len = get_mca_info_helper(mca_dev, page, len); - } else { - /* Leave it empty if MCA not detected - this should *never* - * happen! - */ - } - - if (len <= off+count) *eof = 1; - *start = page + off; - len -= off; - if (len>count) len = count; - if (len<0) len = 0; - return len; -} - -/*--------------------------------------------------------------------*/ - -static int mca_default_procfn(char* buf, struct mca_device *mca_dev) -{ - int len = 0, i; - int slot = mca_dev->slot; - - /* Print out the basic information */ - - if(slot < MCA_MAX_SLOT_NR) { - len += sprintf(buf+len, "Slot: %d\n", slot+1); - } else if(slot == MCA_INTEGSCSI) { - len += sprintf(buf+len, "Integrated SCSI Adapter\n"); - } else if(slot == MCA_INTEGVIDEO) { - len += sprintf(buf+len, "Integrated Video Adapter\n"); - } else if(slot == MCA_MOTHERBOARD) { - len += sprintf(buf+len, "Motherboard\n"); - } - if (mca_dev->name[0]) { - - /* Drivers might register a name without /proc handler... */ - - len += sprintf(buf+len, "Adapter Name: %s\n", - mca_dev->name); - } else { - len += sprintf(buf+len, "Adapter Name: Unknown\n"); - } - len += sprintf(buf+len, "Id: %02x%02x\n", - mca_dev->pos[1], mca_dev->pos[0]); - len += sprintf(buf+len, "Enabled: %s\nPOS: ", - mca_device_status(mca_dev) == MCA_ADAPTER_NORMAL ? - "Yes" : "No"); - for(i=0; i<8; i++) { - len += sprintf(buf+len, "%02x ", mca_dev->pos[i]); - } - len += sprintf(buf+len, "\nDriver Installed: %s", - mca_device_claimed(mca_dev) ? "Yes" : "No"); - buf[len++] = '\n'; - buf[len] = 0; - - return len; -} /* mca_default_procfn() */ - -static int get_mca_machine_info(char* page, char **start, off_t off, - int count, int *eof, void *data) -{ - int len = 0; - - len += sprintf(page+len, "Model Id: 0x%x\n", machine_id); - len += sprintf(page+len, "Submodel Id: 0x%x\n", machine_submodel_id); - len += sprintf(page+len, "BIOS Revision: 0x%x\n", BIOS_revision); - - if (len <= off+count) *eof = 1; - *start = page + off; - len -= off; - if (len>count) len = count; - if (len<0) len = 0; - return len; -} - -static int mca_read_proc(char *page, char **start, off_t off, - int count, int *eof, void *data) -{ - struct mca_device *mca_dev = (struct mca_device *)data; - int len = 0; - - /* Get the standard info */ - - len = mca_default_procfn(page, mca_dev); - - /* Do any device-specific processing, if there is any */ - - if(mca_dev->procfn) { - len += mca_dev->procfn(page+len, mca_dev->slot, - mca_dev->proc_dev); - } - if (len <= off+count) *eof = 1; - *start = page + off; - len -= off; - if (len>count) len = count; - if (len<0) len = 0; - return len; -} /* mca_read_proc() */ - -/*--------------------------------------------------------------------*/ - -void __init mca_do_proc_init(void) -{ - int i; - struct proc_dir_entry *proc_mca; - struct proc_dir_entry* node = NULL; - struct mca_device *mca_dev; - - proc_mca = proc_mkdir("mca", NULL); - create_proc_read_entry("pos",0,proc_mca,get_mca_info,NULL); - create_proc_read_entry("machine",0,proc_mca,get_mca_machine_info,NULL); - - /* Initialize /proc/mca entries for existing adapters */ - - for(i = 0; i < MCA_NUMADAPTERS; i++) { - enum MCA_AdapterStatus status; - mca_dev = mca_find_device_by_slot(i); - if(!mca_dev) - continue; - - mca_dev->procfn = NULL; - - if(i < MCA_MAX_SLOT_NR) sprintf(mca_dev->procname,"slot%d", i+1); - else if(i == MCA_INTEGVIDEO) sprintf(mca_dev->procname,"video"); - else if(i == MCA_INTEGSCSI) sprintf(mca_dev->procname,"scsi"); - else if(i == MCA_MOTHERBOARD) sprintf(mca_dev->procname,"planar"); - - status = mca_device_status(mca_dev); - if (status != MCA_ADAPTER_NORMAL && - status != MCA_ADAPTER_DISABLED) - continue; - - node = create_proc_read_entry(mca_dev->procname, 0, proc_mca, - mca_read_proc, (void *)mca_dev); - - if(node == NULL) { - printk("Failed to allocate memory for MCA proc-entries!"); - return; - } - } - -} /* mca_do_proc_init() */ - -/** - * mca_set_adapter_procfn - Set the /proc callback - * @slot: slot to configure - * @procfn: callback function to call for /proc - * @dev: device information passed to the callback - * - * This sets up an information callback for /proc/mca/slot?. The - * function is called with the buffer, slot, and device pointer (or - * some equally informative context information, or nothing, if you - * prefer), and is expected to put useful information into the - * buffer. The adapter name, ID, and POS registers get printed - * before this is called though, so don't do it again. - * - * This should be called with a %NULL @procfn when a module - * unregisters, thus preventing kernel crashes and other such - * nastiness. - */ - -void mca_set_adapter_procfn(int slot, MCA_ProcFn procfn, void* proc_dev) -{ - struct mca_device *mca_dev = mca_find_device_by_slot(slot); - - if(!mca_dev) - return; - - mca_dev->procfn = procfn; - mca_dev->proc_dev = proc_dev; -} -EXPORT_SYMBOL(mca_set_adapter_procfn); diff --git a/drivers/md/bitmap.c b/drivers/md/bitmap.c index 17e2b472e16d..15dbe03117e4 100644 --- a/drivers/md/bitmap.c +++ b/drivers/md/bitmap.c @@ -45,7 +45,7 @@ static inline char *bmname(struct bitmap *bitmap) * if we find our page, we increment the page's refcount so that it stays * allocated while we're using it */ -static int bitmap_checkpage(struct bitmap *bitmap, +static int bitmap_checkpage(struct bitmap_counts *bitmap, unsigned long page, int create) __releases(bitmap->lock) __acquires(bitmap->lock) @@ -76,8 +76,7 @@ __acquires(bitmap->lock) spin_lock_irq(&bitmap->lock); if (mappage == NULL) { - pr_debug("%s: bitmap map page allocation failed, hijacking\n", - bmname(bitmap)); + pr_debug("md/bitmap: map page allocation failed, hijacking\n"); /* failed - set the hijacked flag so that we can use the * pointer as a counter */ if (!bitmap->bp[page].map) @@ -100,7 +99,7 @@ __acquires(bitmap->lock) /* if page is completely empty, put it back on the free list, or dealloc it */ /* if page was hijacked, unmark the flag so it might get alloced next time */ /* Note: lock should be held when calling this */ -static void bitmap_checkfree(struct bitmap *bitmap, unsigned long page) +static void bitmap_checkfree(struct bitmap_counts *bitmap, unsigned long page) { char *ptr; @@ -130,22 +129,14 @@ static void bitmap_checkfree(struct bitmap *bitmap, unsigned long page) */ /* IO operations when bitmap is stored near all superblocks */ -static struct page *read_sb_page(struct mddev *mddev, loff_t offset, - struct page *page, - unsigned long index, int size) +static int read_sb_page(struct mddev *mddev, loff_t offset, + struct page *page, + unsigned long index, int size) { /* choose a good rdev and read the page from there */ struct md_rdev *rdev; sector_t target; - int did_alloc = 0; - - if (!page) { - page = alloc_page(GFP_KERNEL); - if (!page) - return ERR_PTR(-ENOMEM); - did_alloc = 1; - } rdev_for_each(rdev, mddev) { if (! test_bit(In_sync, &rdev->flags) @@ -158,15 +149,10 @@ static struct page *read_sb_page(struct mddev *mddev, loff_t offset, roundup(size, bdev_logical_block_size(rdev->bdev)), page, READ, true)) { page->index = index; - attach_page_buffers(page, NULL); /* so that free_buffer will - * quietly no-op */ - return page; + return 0; } } - if (did_alloc) - put_page(page); - return ERR_PTR(-EIO); - + return -EIO; } static struct md_rdev *next_active_rdev(struct md_rdev *rdev, struct mddev *mddev) @@ -208,6 +194,7 @@ static int write_sb_page(struct bitmap *bitmap, struct page *page, int wait) struct md_rdev *rdev = NULL; struct block_device *bdev; struct mddev *mddev = bitmap->mddev; + struct bitmap_storage *store = &bitmap->storage; while ((rdev = next_active_rdev(rdev, mddev)) != NULL) { int size = PAGE_SIZE; @@ -215,9 +202,13 @@ static int write_sb_page(struct bitmap *bitmap, struct page *page, int wait) bdev = (rdev->meta_bdev) ? rdev->meta_bdev : rdev->bdev; - if (page->index == bitmap->file_pages-1) - size = roundup(bitmap->last_page_size, + if (page->index == store->file_pages-1) { + int last_page_size = store->bytes & (PAGE_SIZE-1); + if (last_page_size == 0) + last_page_size = PAGE_SIZE; + size = roundup(last_page_size, bdev_logical_block_size(bdev)); + } /* Just make sure we aren't corrupting data or * metadata */ @@ -276,10 +267,10 @@ static void write_page(struct bitmap *bitmap, struct page *page, int wait) { struct buffer_head *bh; - if (bitmap->file == NULL) { + if (bitmap->storage.file == NULL) { switch (write_sb_page(bitmap, page, wait)) { case -EINVAL: - bitmap->flags |= BITMAP_WRITE_ERROR; + set_bit(BITMAP_WRITE_ERROR, &bitmap->flags); } } else { @@ -297,20 +288,16 @@ static void write_page(struct bitmap *bitmap, struct page *page, int wait) wait_event(bitmap->write_wait, atomic_read(&bitmap->pending_writes)==0); } - if (bitmap->flags & BITMAP_WRITE_ERROR) + if (test_bit(BITMAP_WRITE_ERROR, &bitmap->flags)) bitmap_file_kick(bitmap); } static void end_bitmap_write(struct buffer_head *bh, int uptodate) { struct bitmap *bitmap = bh->b_private; - unsigned long flags; - if (!uptodate) { - spin_lock_irqsave(&bitmap->lock, flags); - bitmap->flags |= BITMAP_WRITE_ERROR; - spin_unlock_irqrestore(&bitmap->lock, flags); - } + if (!uptodate) + set_bit(BITMAP_WRITE_ERROR, &bitmap->flags); if (atomic_dec_and_test(&bitmap->pending_writes)) wake_up(&bitmap->write_wait); } @@ -325,8 +312,12 @@ __clear_page_buffers(struct page *page) } static void free_buffers(struct page *page) { - struct buffer_head *bh = page_buffers(page); + struct buffer_head *bh; + if (!PagePrivate(page)) + return; + + bh = page_buffers(page); while (bh) { struct buffer_head *next = bh->b_this_page; free_buffer_head(bh); @@ -343,11 +334,12 @@ static void free_buffers(struct page *page) * This usage is similar to how swap files are handled, and allows us * to write to a file with no concerns of memory allocation failing. */ -static struct page *read_page(struct file *file, unsigned long index, - struct bitmap *bitmap, - unsigned long count) +static int read_page(struct file *file, unsigned long index, + struct bitmap *bitmap, + unsigned long count, + struct page *page) { - struct page *page = NULL; + int ret = 0; struct inode *inode = file->f_path.dentry->d_inode; struct buffer_head *bh; sector_t block; @@ -355,16 +347,9 @@ static struct page *read_page(struct file *file, unsigned long index, pr_debug("read bitmap file (%dB @ %llu)\n", (int)PAGE_SIZE, (unsigned long long)index << PAGE_SHIFT); - page = alloc_page(GFP_KERNEL); - if (!page) - page = ERR_PTR(-ENOMEM); - if (IS_ERR(page)) - goto out; - bh = alloc_page_buffers(page, 1<<inode->i_blkbits, 0); if (!bh) { - put_page(page); - page = ERR_PTR(-ENOMEM); + ret = -ENOMEM; goto out; } attach_page_buffers(page, bh); @@ -376,8 +361,7 @@ static struct page *read_page(struct file *file, unsigned long index, bh->b_blocknr = bmap(inode, block); if (bh->b_blocknr == 0) { /* Cannot use this file! */ - free_buffers(page); - page = ERR_PTR(-EINVAL); + ret = -EINVAL; goto out; } bh->b_bdev = inode->i_sb->s_bdev; @@ -400,17 +384,15 @@ static struct page *read_page(struct file *file, unsigned long index, wait_event(bitmap->write_wait, atomic_read(&bitmap->pending_writes)==0); - if (bitmap->flags & BITMAP_WRITE_ERROR) { - free_buffers(page); - page = ERR_PTR(-EIO); - } + if (test_bit(BITMAP_WRITE_ERROR, &bitmap->flags)) + ret = -EIO; out: - if (IS_ERR(page)) - printk(KERN_ALERT "md: bitmap read error: (%dB @ %llu): %ld\n", + if (ret) + printk(KERN_ALERT "md: bitmap read error: (%dB @ %llu): %d\n", (int)PAGE_SIZE, (unsigned long long)index << PAGE_SHIFT, - PTR_ERR(page)); - return page; + ret); + return ret; } /* @@ -426,9 +408,9 @@ void bitmap_update_sb(struct bitmap *bitmap) return; if (bitmap->mddev->bitmap_info.external) return; - if (!bitmap->sb_page) /* no superblock */ + if (!bitmap->storage.sb_page) /* no superblock */ return; - sb = kmap_atomic(bitmap->sb_page); + sb = kmap_atomic(bitmap->storage.sb_page); sb->events = cpu_to_le64(bitmap->mddev->events); if (bitmap->mddev->events < bitmap->events_cleared) /* rocking back to read-only */ @@ -438,8 +420,13 @@ void bitmap_update_sb(struct bitmap *bitmap) /* Just in case these have been changed via sysfs: */ sb->daemon_sleep = cpu_to_le32(bitmap->mddev->bitmap_info.daemon_sleep/HZ); sb->write_behind = cpu_to_le32(bitmap->mddev->bitmap_info.max_write_behind); + /* This might have been changed by a reshape */ + sb->sync_size = cpu_to_le64(bitmap->mddev->resync_max_sectors); + sb->chunksize = cpu_to_le32(bitmap->mddev->bitmap_info.chunksize); + sb->sectors_reserved = cpu_to_le32(bitmap->mddev-> + bitmap_info.space); kunmap_atomic(sb); - write_page(bitmap, bitmap->sb_page, 1); + write_page(bitmap, bitmap->storage.sb_page, 1); } /* print out the bitmap file superblock */ @@ -447,9 +434,9 @@ void bitmap_print_sb(struct bitmap *bitmap) { bitmap_super_t *sb; - if (!bitmap || !bitmap->sb_page) + if (!bitmap || !bitmap->storage.sb_page) return; - sb = kmap_atomic(bitmap->sb_page); + sb = kmap_atomic(bitmap->storage.sb_page); printk(KERN_DEBUG "%s: bitmap file superblock:\n", bmname(bitmap)); printk(KERN_DEBUG " magic: %08x\n", le32_to_cpu(sb->magic)); printk(KERN_DEBUG " version: %d\n", le32_to_cpu(sb->version)); @@ -488,15 +475,15 @@ static int bitmap_new_disk_sb(struct bitmap *bitmap) unsigned long chunksize, daemon_sleep, write_behind; int err = -EINVAL; - bitmap->sb_page = alloc_page(GFP_KERNEL); - if (IS_ERR(bitmap->sb_page)) { - err = PTR_ERR(bitmap->sb_page); - bitmap->sb_page = NULL; + bitmap->storage.sb_page = alloc_page(GFP_KERNEL); + if (IS_ERR(bitmap->storage.sb_page)) { + err = PTR_ERR(bitmap->storage.sb_page); + bitmap->storage.sb_page = NULL; return err; } - bitmap->sb_page->index = 0; + bitmap->storage.sb_page->index = 0; - sb = kmap_atomic(bitmap->sb_page); + sb = kmap_atomic(bitmap->storage.sb_page); sb->magic = cpu_to_le32(BITMAP_MAGIC); sb->version = cpu_to_le32(BITMAP_MAJOR_HI); @@ -534,8 +521,8 @@ static int bitmap_new_disk_sb(struct bitmap *bitmap) memcpy(sb->uuid, bitmap->mddev->uuid, 16); - bitmap->flags |= BITMAP_STALE; - sb->state |= cpu_to_le32(BITMAP_STALE); + set_bit(BITMAP_STALE, &bitmap->flags); + sb->state = cpu_to_le32(bitmap->flags); bitmap->events_cleared = bitmap->mddev->events; sb->events_cleared = cpu_to_le64(bitmap->mddev->events); @@ -551,31 +538,45 @@ static int bitmap_read_sb(struct bitmap *bitmap) bitmap_super_t *sb; unsigned long chunksize, daemon_sleep, write_behind; unsigned long long events; + unsigned long sectors_reserved = 0; int err = -EINVAL; + struct page *sb_page; + if (!bitmap->storage.file && !bitmap->mddev->bitmap_info.offset) { + chunksize = 128 * 1024 * 1024; + daemon_sleep = 5 * HZ; + write_behind = 0; + set_bit(BITMAP_STALE, &bitmap->flags); + err = 0; + goto out_no_sb; + } /* page 0 is the superblock, read it... */ - if (bitmap->file) { - loff_t isize = i_size_read(bitmap->file->f_mapping->host); + sb_page = alloc_page(GFP_KERNEL); + if (!sb_page) + return -ENOMEM; + bitmap->storage.sb_page = sb_page; + + if (bitmap->storage.file) { + loff_t isize = i_size_read(bitmap->storage.file->f_mapping->host); int bytes = isize > PAGE_SIZE ? PAGE_SIZE : isize; - bitmap->sb_page = read_page(bitmap->file, 0, bitmap, bytes); + err = read_page(bitmap->storage.file, 0, + bitmap, bytes, sb_page); } else { - bitmap->sb_page = read_sb_page(bitmap->mddev, - bitmap->mddev->bitmap_info.offset, - NULL, - 0, sizeof(bitmap_super_t)); + err = read_sb_page(bitmap->mddev, + bitmap->mddev->bitmap_info.offset, + sb_page, + 0, sizeof(bitmap_super_t)); } - if (IS_ERR(bitmap->sb_page)) { - err = PTR_ERR(bitmap->sb_page); - bitmap->sb_page = NULL; + if (err) return err; - } - sb = kmap_atomic(bitmap->sb_page); + sb = kmap_atomic(sb_page); chunksize = le32_to_cpu(sb->chunksize); daemon_sleep = le32_to_cpu(sb->daemon_sleep) * HZ; write_behind = le32_to_cpu(sb->write_behind); + sectors_reserved = le32_to_cpu(sb->sectors_reserved); /* verify that the bitmap-specific fields are valid */ if (sb->magic != cpu_to_le32(BITMAP_MAGIC)) @@ -618,60 +619,32 @@ static int bitmap_read_sb(struct bitmap *bitmap) "-- forcing full recovery\n", bmname(bitmap), events, (unsigned long long) bitmap->mddev->events); - sb->state |= cpu_to_le32(BITMAP_STALE); + set_bit(BITMAP_STALE, &bitmap->flags); } } /* assign fields using values from superblock */ - bitmap->mddev->bitmap_info.chunksize = chunksize; - bitmap->mddev->bitmap_info.daemon_sleep = daemon_sleep; - bitmap->mddev->bitmap_info.max_write_behind = write_behind; bitmap->flags |= le32_to_cpu(sb->state); if (le32_to_cpu(sb->version) == BITMAP_MAJOR_HOSTENDIAN) - bitmap->flags |= BITMAP_HOSTENDIAN; + set_bit(BITMAP_HOSTENDIAN, &bitmap->flags); bitmap->events_cleared = le64_to_cpu(sb->events_cleared); - if (bitmap->flags & BITMAP_STALE) - bitmap->events_cleared = bitmap->mddev->events; err = 0; out: kunmap_atomic(sb); +out_no_sb: + if (test_bit(BITMAP_STALE, &bitmap->flags)) + bitmap->events_cleared = bitmap->mddev->events; + bitmap->mddev->bitmap_info.chunksize = chunksize; + bitmap->mddev->bitmap_info.daemon_sleep = daemon_sleep; + bitmap->mddev->bitmap_info.max_write_behind = write_behind; + if (bitmap->mddev->bitmap_info.space == 0 || + bitmap->mddev->bitmap_info.space > sectors_reserved) + bitmap->mddev->bitmap_info.space = sectors_reserved; if (err) bitmap_print_sb(bitmap); return err; } -enum bitmap_mask_op { - MASK_SET, - MASK_UNSET -}; - -/* record the state of the bitmap in the superblock. Return the old value */ -static int bitmap_mask_state(struct bitmap *bitmap, enum bitmap_state bits, - enum bitmap_mask_op op) -{ - bitmap_super_t *sb; - int old; - - if (!bitmap->sb_page) /* can't set the state */ - return 0; - sb = kmap_atomic(bitmap->sb_page); - old = le32_to_cpu(sb->state) & bits; - switch (op) { - case MASK_SET: - sb->state |= cpu_to_le32(bits); - bitmap->flags |= bits; - break; - case MASK_UNSET: - sb->state &= cpu_to_le32(~bits); - bitmap->flags &= ~bits; - break; - default: - BUG(); - } - kunmap_atomic(sb); - return old; -} - /* * general bitmap file operations */ @@ -683,17 +656,19 @@ static int bitmap_mask_state(struct bitmap *bitmap, enum bitmap_state bits, * file a page at a time. There's a superblock at the start of the file. */ /* calculate the index of the page that contains this bit */ -static inline unsigned long file_page_index(struct bitmap *bitmap, unsigned long chunk) +static inline unsigned long file_page_index(struct bitmap_storage *store, + unsigned long chunk) { - if (!bitmap->mddev->bitmap_info.external) + if (store->sb_page) chunk += sizeof(bitmap_super_t) << 3; return chunk >> PAGE_BIT_SHIFT; } /* calculate the (bit) offset of this bit within a page */ -static inline unsigned long file_page_offset(struct bitmap *bitmap, unsigned long chunk) +static inline unsigned long file_page_offset(struct bitmap_storage *store, + unsigned long chunk) { - if (!bitmap->mddev->bitmap_info.external) + if (store->sb_page) chunk += sizeof(bitmap_super_t) << 3; return chunk & (PAGE_BITS - 1); } @@ -705,57 +680,86 @@ static inline unsigned long file_page_offset(struct bitmap *bitmap, unsigned lon * 1 page (e.g., x86) or less than 1 page -- so the bitmap might start on page * 0 or page 1 */ -static inline struct page *filemap_get_page(struct bitmap *bitmap, +static inline struct page *filemap_get_page(struct bitmap_storage *store, unsigned long chunk) { - if (file_page_index(bitmap, chunk) >= bitmap->file_pages) + if (file_page_index(store, chunk) >= store->file_pages) return NULL; - return bitmap->filemap[file_page_index(bitmap, chunk) - - file_page_index(bitmap, 0)]; + return store->filemap[file_page_index(store, chunk) + - file_page_index(store, 0)]; } -static void bitmap_file_unmap(struct bitmap *bitmap) +static int bitmap_storage_alloc(struct bitmap_storage *store, + unsigned long chunks, int with_super) +{ + int pnum; + unsigned long num_pages; + unsigned long bytes; + + bytes = DIV_ROUND_UP(chunks, 8); + if (with_super) + bytes += sizeof(bitmap_super_t); + + num_pages = DIV_ROUND_UP(bytes, PAGE_SIZE); + + store->filemap = kmalloc(sizeof(struct page *) + * num_pages, GFP_KERNEL); + if (!store->filemap) + return -ENOMEM; + + if (with_super && !store->sb_page) { + store->sb_page = alloc_page(GFP_KERNEL|__GFP_ZERO); + if (store->sb_page == NULL) + return -ENOMEM; + store->sb_page->index = 0; + } + pnum = 0; + if (store->sb_page) { + store->filemap[0] = store->sb_page; + pnum = 1; + } + for ( ; pnum < num_pages; pnum++) { + store->filemap[pnum] = alloc_page(GFP_KERNEL|__GFP_ZERO); + if (!store->filemap[pnum]) { + store->file_pages = pnum; + return -ENOMEM; + } + store->filemap[pnum]->index = pnum; + } + store->file_pages = pnum; + + /* We need 4 bits per page, rounded up to a multiple + * of sizeof(unsigned long) */ + store->filemap_attr = kzalloc( + roundup(DIV_ROUND_UP(num_pages*4, 8), sizeof(unsigned long)), + GFP_KERNEL); + if (!store->filemap_attr) + return -ENOMEM; + + store->bytes = bytes; + + return 0; +} + +static void bitmap_file_unmap(struct bitmap_storage *store) { struct page **map, *sb_page; - unsigned long *attr; int pages; - unsigned long flags; + struct file *file; - spin_lock_irqsave(&bitmap->lock, flags); - map = bitmap->filemap; - bitmap->filemap = NULL; - attr = bitmap->filemap_attr; - bitmap->filemap_attr = NULL; - pages = bitmap->file_pages; - bitmap->file_pages = 0; - sb_page = bitmap->sb_page; - bitmap->sb_page = NULL; - spin_unlock_irqrestore(&bitmap->lock, flags); + file = store->file; + map = store->filemap; + pages = store->file_pages; + sb_page = store->sb_page; while (pages--) if (map[pages] != sb_page) /* 0 is sb_page, release it below */ free_buffers(map[pages]); kfree(map); - kfree(attr); + kfree(store->filemap_attr); if (sb_page) free_buffers(sb_page); -} - -static void bitmap_file_put(struct bitmap *bitmap) -{ - struct file *file; - unsigned long flags; - - spin_lock_irqsave(&bitmap->lock, flags); - file = bitmap->file; - bitmap->file = NULL; - spin_unlock_irqrestore(&bitmap->lock, flags); - - if (file) - wait_event(bitmap->write_wait, - atomic_read(&bitmap->pending_writes)==0); - bitmap_file_unmap(bitmap); if (file) { struct inode *inode = file->f_path.dentry->d_inode; @@ -773,14 +777,14 @@ static void bitmap_file_kick(struct bitmap *bitmap) { char *path, *ptr = NULL; - if (bitmap_mask_state(bitmap, BITMAP_STALE, MASK_SET) == 0) { + if (!test_and_set_bit(BITMAP_STALE, &bitmap->flags)) { bitmap_update_sb(bitmap); - if (bitmap->file) { + if (bitmap->storage.file) { path = kmalloc(PAGE_SIZE, GFP_KERNEL); if (path) - ptr = d_path(&bitmap->file->f_path, path, - PAGE_SIZE); + ptr = d_path(&bitmap->storage.file->f_path, + path, PAGE_SIZE); printk(KERN_ALERT "%s: kicking failed bitmap file %s from array!\n", @@ -792,10 +796,6 @@ static void bitmap_file_kick(struct bitmap *bitmap) "%s: disabling internal bitmap due to errors\n", bmname(bitmap)); } - - bitmap_file_put(bitmap); - - return; } enum bitmap_page_attr { @@ -805,24 +805,30 @@ enum bitmap_page_attr { BITMAP_PAGE_NEEDWRITE = 2, /* there are cleared bits that need to be synced */ }; -static inline void set_page_attr(struct bitmap *bitmap, struct page *page, - enum bitmap_page_attr attr) +static inline void set_page_attr(struct bitmap *bitmap, int pnum, + enum bitmap_page_attr attr) { - __set_bit((page->index<<2) + attr, bitmap->filemap_attr); + set_bit((pnum<<2) + attr, bitmap->storage.filemap_attr); } -static inline void clear_page_attr(struct bitmap *bitmap, struct page *page, - enum bitmap_page_attr attr) +static inline void clear_page_attr(struct bitmap *bitmap, int pnum, + enum bitmap_page_attr attr) { - __clear_bit((page->index<<2) + attr, bitmap->filemap_attr); + clear_bit((pnum<<2) + attr, bitmap->storage.filemap_attr); } -static inline unsigned long test_page_attr(struct bitmap *bitmap, struct page *page, - enum bitmap_page_attr attr) +static inline int test_page_attr(struct bitmap *bitmap, int pnum, + enum bitmap_page_attr attr) { - return test_bit((page->index<<2) + attr, bitmap->filemap_attr); + return test_bit((pnum<<2) + attr, bitmap->storage.filemap_attr); } +static inline int test_and_clear_page_attr(struct bitmap *bitmap, int pnum, + enum bitmap_page_attr attr) +{ + return test_and_clear_bit((pnum<<2) + attr, + bitmap->storage.filemap_attr); +} /* * bitmap_file_set_bit -- called before performing a write to the md device * to set (and eventually sync) a particular bit in the bitmap file @@ -835,26 +841,46 @@ static void bitmap_file_set_bit(struct bitmap *bitmap, sector_t block) unsigned long bit; struct page *page; void *kaddr; - unsigned long chunk = block >> bitmap->chunkshift; + unsigned long chunk = block >> bitmap->counts.chunkshift; - if (!bitmap->filemap) - return; - - page = filemap_get_page(bitmap, chunk); + page = filemap_get_page(&bitmap->storage, chunk); if (!page) return; - bit = file_page_offset(bitmap, chunk); + bit = file_page_offset(&bitmap->storage, chunk); /* set the bit */ kaddr = kmap_atomic(page); - if (bitmap->flags & BITMAP_HOSTENDIAN) + if (test_bit(BITMAP_HOSTENDIAN, &bitmap->flags)) set_bit(bit, kaddr); else - __set_bit_le(bit, kaddr); + test_and_set_bit_le(bit, kaddr); kunmap_atomic(kaddr); pr_debug("set file bit %lu page %lu\n", bit, page->index); /* record page number so it gets flushed to disk when unplug occurs */ - set_page_attr(bitmap, page, BITMAP_PAGE_DIRTY); + set_page_attr(bitmap, page->index, BITMAP_PAGE_DIRTY); +} + +static void bitmap_file_clear_bit(struct bitmap *bitmap, sector_t block) +{ + unsigned long bit; + struct page *page; + void *paddr; + unsigned long chunk = block >> bitmap->counts.chunkshift; + + page = filemap_get_page(&bitmap->storage, chunk); + if (!page) + return; + bit = file_page_offset(&bitmap->storage, chunk); + paddr = kmap_atomic(page); + if (test_bit(BITMAP_HOSTENDIAN, &bitmap->flags)) + clear_bit(bit, paddr); + else + test_and_clear_bit_le(bit, paddr); + kunmap_atomic(paddr); + if (!test_page_attr(bitmap, page->index, BITMAP_PAGE_NEEDWRITE)) { + set_page_attr(bitmap, page->index, BITMAP_PAGE_PENDING); + bitmap->allclean = 0; + } } /* this gets called when the md device is ready to unplug its underlying @@ -862,42 +888,37 @@ static void bitmap_file_set_bit(struct bitmap *bitmap, sector_t block) * sync the dirty pages of the bitmap file to disk */ void bitmap_unplug(struct bitmap *bitmap) { - unsigned long i, flags; + unsigned long i; int dirty, need_write; - struct page *page; int wait = 0; - if (!bitmap) + if (!bitmap || !bitmap->storage.filemap || + test_bit(BITMAP_STALE, &bitmap->flags)) return; /* look at each page to see if there are any set bits that need to be * flushed out to disk */ - for (i = 0; i < bitmap->file_pages; i++) { - spin_lock_irqsave(&bitmap->lock, flags); - if (!bitmap->filemap) { - spin_unlock_irqrestore(&bitmap->lock, flags); + for (i = 0; i < bitmap->storage.file_pages; i++) { + if (!bitmap->storage.filemap) return; + dirty = test_and_clear_page_attr(bitmap, i, BITMAP_PAGE_DIRTY); + need_write = test_and_clear_page_attr(bitmap, i, + BITMAP_PAGE_NEEDWRITE); + if (dirty || need_write) { + clear_page_attr(bitmap, i, BITMAP_PAGE_PENDING); + write_page(bitmap, bitmap->storage.filemap[i], 0); } - page = bitmap->filemap[i]; - dirty = test_page_attr(bitmap, page, BITMAP_PAGE_DIRTY); - need_write = test_page_attr(bitmap, page, BITMAP_PAGE_NEEDWRITE); - clear_page_attr(bitmap, page, BITMAP_PAGE_DIRTY); - clear_page_attr(bitmap, page, BITMAP_PAGE_NEEDWRITE); if (dirty) wait = 1; - spin_unlock_irqrestore(&bitmap->lock, flags); - - if (dirty || need_write) - write_page(bitmap, page, 0); } if (wait) { /* if any writes were performed, we need to wait on them */ - if (bitmap->file) + if (bitmap->storage.file) wait_event(bitmap->write_wait, atomic_read(&bitmap->pending_writes)==0); else md_super_wait(bitmap->mddev); } - if (bitmap->flags & BITMAP_WRITE_ERROR) + if (test_bit(BITMAP_WRITE_ERROR, &bitmap->flags)) bitmap_file_kick(bitmap); } EXPORT_SYMBOL(bitmap_unplug); @@ -917,98 +938,77 @@ static void bitmap_set_memory_bits(struct bitmap *bitmap, sector_t offset, int n static int bitmap_init_from_disk(struct bitmap *bitmap, sector_t start) { unsigned long i, chunks, index, oldindex, bit; - struct page *page = NULL, *oldpage = NULL; - unsigned long num_pages, bit_cnt = 0; + struct page *page = NULL; + unsigned long bit_cnt = 0; struct file *file; - unsigned long bytes, offset; + unsigned long offset; int outofdate; int ret = -ENOSPC; void *paddr; + struct bitmap_storage *store = &bitmap->storage; - chunks = bitmap->chunks; - file = bitmap->file; + chunks = bitmap->counts.chunks; + file = store->file; - BUG_ON(!file && !bitmap->mddev->bitmap_info.offset); + if (!file && !bitmap->mddev->bitmap_info.offset) { + /* No permanent bitmap - fill with '1s'. */ + store->filemap = NULL; + store->file_pages = 0; + for (i = 0; i < chunks ; i++) { + /* if the disk bit is set, set the memory bit */ + int needed = ((sector_t)(i+1) << (bitmap->counts.chunkshift) + >= start); + bitmap_set_memory_bits(bitmap, + (sector_t)i << bitmap->counts.chunkshift, + needed); + } + return 0; + } - outofdate = bitmap->flags & BITMAP_STALE; + outofdate = test_bit(BITMAP_STALE, &bitmap->flags); if (outofdate) printk(KERN_INFO "%s: bitmap file is out of date, doing full " "recovery\n", bmname(bitmap)); - bytes = DIV_ROUND_UP(bitmap->chunks, 8); - if (!bitmap->mddev->bitmap_info.external) - bytes += sizeof(bitmap_super_t); - - num_pages = DIV_ROUND_UP(bytes, PAGE_SIZE); - - if (file && i_size_read(file->f_mapping->host) < bytes) { + if (file && i_size_read(file->f_mapping->host) < store->bytes) { printk(KERN_INFO "%s: bitmap file too short %lu < %lu\n", - bmname(bitmap), - (unsigned long) i_size_read(file->f_mapping->host), - bytes); + bmname(bitmap), + (unsigned long) i_size_read(file->f_mapping->host), + store->bytes); goto err; } - ret = -ENOMEM; - - bitmap->filemap = kmalloc(sizeof(struct page *) * num_pages, GFP_KERNEL); - if (!bitmap->filemap) - goto err; - - /* We need 4 bits per page, rounded up to a multiple of sizeof(unsigned long) */ - bitmap->filemap_attr = kzalloc( - roundup(DIV_ROUND_UP(num_pages*4, 8), sizeof(unsigned long)), - GFP_KERNEL); - if (!bitmap->filemap_attr) - goto err; - oldindex = ~0L; + offset = 0; + if (!bitmap->mddev->bitmap_info.external) + offset = sizeof(bitmap_super_t); for (i = 0; i < chunks; i++) { int b; - index = file_page_index(bitmap, i); - bit = file_page_offset(bitmap, i); + index = file_page_index(&bitmap->storage, i); + bit = file_page_offset(&bitmap->storage, i); if (index != oldindex) { /* this is a new page, read it in */ int count; /* unmap the old page, we're done with it */ - if (index == num_pages-1) - count = bytes - index * PAGE_SIZE; + if (index == store->file_pages-1) + count = store->bytes - index * PAGE_SIZE; else count = PAGE_SIZE; - if (index == 0 && bitmap->sb_page) { - /* - * if we're here then the superblock page - * contains some bits (PAGE_SIZE != sizeof sb) - * we've already read it in, so just use it - */ - page = bitmap->sb_page; - offset = sizeof(bitmap_super_t); - if (!file) - page = read_sb_page( - bitmap->mddev, - bitmap->mddev->bitmap_info.offset, - page, - index, count); - } else if (file) { - page = read_page(file, index, bitmap, count); - offset = 0; - } else { - page = read_sb_page(bitmap->mddev, - bitmap->mddev->bitmap_info.offset, - NULL, - index, count); - offset = 0; - } - if (IS_ERR(page)) { /* read error */ - ret = PTR_ERR(page); + page = store->filemap[index]; + if (file) + ret = read_page(file, index, bitmap, + count, page); + else + ret = read_sb_page( + bitmap->mddev, + bitmap->mddev->bitmap_info.offset, + page, + index, count); + + if (ret) goto err; - } oldindex = index; - oldpage = page; - - bitmap->filemap[bitmap->file_pages++] = page; - bitmap->last_page_size = count; if (outofdate) { /* @@ -1022,39 +1022,33 @@ static int bitmap_init_from_disk(struct bitmap *bitmap, sector_t start) write_page(bitmap, page, 1); ret = -EIO; - if (bitmap->flags & BITMAP_WRITE_ERROR) + if (test_bit(BITMAP_WRITE_ERROR, + &bitmap->flags)) goto err; } } paddr = kmap_atomic(page); - if (bitmap->flags & BITMAP_HOSTENDIAN) + if (test_bit(BITMAP_HOSTENDIAN, &bitmap->flags)) b = test_bit(bit, paddr); else b = test_bit_le(bit, paddr); kunmap_atomic(paddr); if (b) { /* if the disk bit is set, set the memory bit */ - int needed = ((sector_t)(i+1) << bitmap->chunkshift + int needed = ((sector_t)(i+1) << bitmap->counts.chunkshift >= start); bitmap_set_memory_bits(bitmap, - (sector_t)i << bitmap->chunkshift, + (sector_t)i << bitmap->counts.chunkshift, needed); bit_cnt++; } - } - - /* everything went OK */ - ret = 0; - bitmap_mask_state(bitmap, BITMAP_STALE, MASK_UNSET); - - if (bit_cnt) { /* Kick recovery if any bits were set */ - set_bit(MD_RECOVERY_NEEDED, &bitmap->mddev->recovery); - md_wakeup_thread(bitmap->mddev->thread); + offset = 0; } printk(KERN_INFO "%s: bitmap initialized from disk: " - "read %lu/%lu pages, set %lu of %lu bits\n", - bmname(bitmap), bitmap->file_pages, num_pages, bit_cnt, chunks); + "read %lu pages, set %lu of %lu bits\n", + bmname(bitmap), store->file_pages, + bit_cnt, chunks); return 0; @@ -1071,22 +1065,38 @@ void bitmap_write_all(struct bitmap *bitmap) */ int i; - spin_lock_irq(&bitmap->lock); - for (i = 0; i < bitmap->file_pages; i++) - set_page_attr(bitmap, bitmap->filemap[i], + if (!bitmap || !bitmap->storage.filemap) + return; + if (bitmap->storage.file) + /* Only one copy, so nothing needed */ + return; + + for (i = 0; i < bitmap->storage.file_pages; i++) + set_page_attr(bitmap, i, BITMAP_PAGE_NEEDWRITE); bitmap->allclean = 0; - spin_unlock_irq(&bitmap->lock); } -static void bitmap_count_page(struct bitmap *bitmap, sector_t offset, int inc) +static void bitmap_count_page(struct bitmap_counts *bitmap, + sector_t offset, int inc) { sector_t chunk = offset >> bitmap->chunkshift; unsigned long page = chunk >> PAGE_COUNTER_SHIFT; bitmap->bp[page].count += inc; bitmap_checkfree(bitmap, page); } -static bitmap_counter_t *bitmap_get_counter(struct bitmap *bitmap, + +static void bitmap_set_pending(struct bitmap_counts *bitmap, sector_t offset) +{ + sector_t chunk = offset >> bitmap->chunkshift; + unsigned long page = chunk >> PAGE_COUNTER_SHIFT; + struct bitmap_page *bp = &bitmap->bp[page]; + + if (!bp->pending) + bp->pending = 1; +} + +static bitmap_counter_t *bitmap_get_counter(struct bitmap_counts *bitmap, sector_t offset, sector_t *blocks, int create); @@ -1099,10 +1109,9 @@ void bitmap_daemon_work(struct mddev *mddev) { struct bitmap *bitmap; unsigned long j; - unsigned long flags; - struct page *page = NULL, *lastpage = NULL; + unsigned long nextpage; sector_t blocks; - void *paddr; + struct bitmap_counts *counts; /* Use a mutex to guard daemon_work against * bitmap_destroy. @@ -1124,112 +1133,90 @@ void bitmap_daemon_work(struct mddev *mddev) } bitmap->allclean = 1; - spin_lock_irqsave(&bitmap->lock, flags); - for (j = 0; j < bitmap->chunks; j++) { + /* Any file-page which is PENDING now needs to be written. + * So set NEEDWRITE now, then after we make any last-minute changes + * we will write it. + */ + for (j = 0; j < bitmap->storage.file_pages; j++) + if (test_and_clear_page_attr(bitmap, j, + BITMAP_PAGE_PENDING)) + set_page_attr(bitmap, j, + BITMAP_PAGE_NEEDWRITE); + + if (bitmap->need_sync && + mddev->bitmap_info.external == 0) { + /* Arrange for superblock update as well as + * other changes */ + bitmap_super_t *sb; + bitmap->need_sync = 0; + if (bitmap->storage.filemap) { + sb = kmap_atomic(bitmap->storage.sb_page); + sb->events_cleared = + cpu_to_le64(bitmap->events_cleared); + kunmap_atomic(sb); + set_page_attr(bitmap, 0, + BITMAP_PAGE_NEEDWRITE); + } + } + /* Now look at the bitmap counters and if any are '2' or '1', + * decrement and handle accordingly. + */ + counts = &bitmap->counts; + spin_lock_irq(&counts->lock); + nextpage = 0; + for (j = 0; j < counts->chunks; j++) { bitmap_counter_t *bmc; - if (!bitmap->filemap) - /* error or shutdown */ - break; + sector_t block = (sector_t)j << counts->chunkshift; - page = filemap_get_page(bitmap, j); - - if (page != lastpage) { - /* skip this page unless it's marked as needing cleaning */ - if (!test_page_attr(bitmap, page, BITMAP_PAGE_PENDING)) { - int need_write = test_page_attr(bitmap, page, - BITMAP_PAGE_NEEDWRITE); - if (need_write) - clear_page_attr(bitmap, page, BITMAP_PAGE_NEEDWRITE); - - spin_unlock_irqrestore(&bitmap->lock, flags); - if (need_write) - write_page(bitmap, page, 0); - spin_lock_irqsave(&bitmap->lock, flags); - j |= (PAGE_BITS - 1); + if (j == nextpage) { + nextpage += PAGE_COUNTER_RATIO; + if (!counts->bp[j >> PAGE_COUNTER_SHIFT].pending) { + j |= PAGE_COUNTER_MASK; continue; } - - /* grab the new page, sync and release the old */ - if (lastpage != NULL) { - if (test_page_attr(bitmap, lastpage, - BITMAP_PAGE_NEEDWRITE)) { - clear_page_attr(bitmap, lastpage, - BITMAP_PAGE_NEEDWRITE); - spin_unlock_irqrestore(&bitmap->lock, flags); - write_page(bitmap, lastpage, 0); - } else { - set_page_attr(bitmap, lastpage, - BITMAP_PAGE_NEEDWRITE); - bitmap->allclean = 0; - spin_unlock_irqrestore(&bitmap->lock, flags); - } - } else - spin_unlock_irqrestore(&bitmap->lock, flags); - lastpage = page; - - /* We are possibly going to clear some bits, so make - * sure that events_cleared is up-to-date. - */ - if (bitmap->need_sync && - mddev->bitmap_info.external == 0) { - bitmap_super_t *sb; - bitmap->need_sync = 0; - sb = kmap_atomic(bitmap->sb_page); - sb->events_cleared = - cpu_to_le64(bitmap->events_cleared); - kunmap_atomic(sb); - write_page(bitmap, bitmap->sb_page, 1); - } - spin_lock_irqsave(&bitmap->lock, flags); - if (!bitmap->need_sync) - clear_page_attr(bitmap, page, BITMAP_PAGE_PENDING); - else - bitmap->allclean = 0; + counts->bp[j >> PAGE_COUNTER_SHIFT].pending = 0; } - bmc = bitmap_get_counter(bitmap, - (sector_t)j << bitmap->chunkshift, + bmc = bitmap_get_counter(counts, + block, &blocks, 0); - if (!bmc) + + if (!bmc) { j |= PAGE_COUNTER_MASK; - else if (*bmc) { - if (*bmc == 1 && !bitmap->need_sync) { - /* we can clear the bit */ - *bmc = 0; - bitmap_count_page(bitmap, - (sector_t)j << bitmap->chunkshift, - -1); - - /* clear the bit */ - paddr = kmap_atomic(page); - if (bitmap->flags & BITMAP_HOSTENDIAN) - clear_bit(file_page_offset(bitmap, j), - paddr); - else - __clear_bit_le( - file_page_offset(bitmap, - j), - paddr); - kunmap_atomic(paddr); - } else if (*bmc <= 2) { - *bmc = 1; /* maybe clear the bit next time */ - set_page_attr(bitmap, page, BITMAP_PAGE_PENDING); - bitmap->allclean = 0; - } + continue; } - } - spin_unlock_irqrestore(&bitmap->lock, flags); - - /* now sync the final page */ - if (lastpage != NULL) { - spin_lock_irqsave(&bitmap->lock, flags); - if (test_page_attr(bitmap, lastpage, BITMAP_PAGE_NEEDWRITE)) { - clear_page_attr(bitmap, lastpage, BITMAP_PAGE_NEEDWRITE); - spin_unlock_irqrestore(&bitmap->lock, flags); - write_page(bitmap, lastpage, 0); - } else { - set_page_attr(bitmap, lastpage, BITMAP_PAGE_NEEDWRITE); + if (*bmc == 1 && !bitmap->need_sync) { + /* We can clear the bit */ + *bmc = 0; + bitmap_count_page(counts, block, -1); + bitmap_file_clear_bit(bitmap, block); + } else if (*bmc && *bmc <= 2) { + *bmc = 1; + bitmap_set_pending(counts, block); bitmap->allclean = 0; - spin_unlock_irqrestore(&bitmap->lock, flags); + } + } + spin_unlock_irq(&counts->lock); + + /* Now start writeout on any page in NEEDWRITE that isn't DIRTY. + * DIRTY pages need to be written by bitmap_unplug so it can wait + * for them. + * If we find any DIRTY page we stop there and let bitmap_unplug + * handle all the rest. This is important in the case where + * the first blocking holds the superblock and it has been updated. + * We mustn't write any other blocks before the superblock. + */ + for (j = 0; + j < bitmap->storage.file_pages + && !test_bit(BITMAP_STALE, &bitmap->flags); + j++) { + + if (test_page_attr(bitmap, j, + BITMAP_PAGE_DIRTY)) + /* bitmap_unplug will handle the rest */ + break; + if (test_and_clear_page_attr(bitmap, j, + BITMAP_PAGE_NEEDWRITE)) { + write_page(bitmap, bitmap->storage.filemap[j], 0); } } @@ -1240,7 +1227,7 @@ void bitmap_daemon_work(struct mddev *mddev) mutex_unlock(&mddev->bitmap_info.mutex); } -static bitmap_counter_t *bitmap_get_counter(struct bitmap *bitmap, +static bitmap_counter_t *bitmap_get_counter(struct bitmap_counts *bitmap, sector_t offset, sector_t *blocks, int create) __releases(bitmap->lock) @@ -1302,10 +1289,10 @@ int bitmap_startwrite(struct bitmap *bitmap, sector_t offset, unsigned long sect sector_t blocks; bitmap_counter_t *bmc; - spin_lock_irq(&bitmap->lock); - bmc = bitmap_get_counter(bitmap, offset, &blocks, 1); + spin_lock_irq(&bitmap->counts.lock); + bmc = bitmap_get_counter(&bitmap->counts, offset, &blocks, 1); if (!bmc) { - spin_unlock_irq(&bitmap->lock); + spin_unlock_irq(&bitmap->counts.lock); return 0; } @@ -1317,7 +1304,7 @@ int bitmap_startwrite(struct bitmap *bitmap, sector_t offset, unsigned long sect */ prepare_to_wait(&bitmap->overflow_wait, &__wait, TASK_UNINTERRUPTIBLE); - spin_unlock_irq(&bitmap->lock); + spin_unlock_irq(&bitmap->counts.lock); io_schedule(); finish_wait(&bitmap->overflow_wait, &__wait); continue; @@ -1326,7 +1313,7 @@ int bitmap_startwrite(struct bitmap *bitmap, sector_t offset, unsigned long sect switch (*bmc) { case 0: bitmap_file_set_bit(bitmap, offset); - bitmap_count_page(bitmap, offset, 1); + bitmap_count_page(&bitmap->counts, offset, 1); /* fall through */ case 1: *bmc = 2; @@ -1334,7 +1321,7 @@ int bitmap_startwrite(struct bitmap *bitmap, sector_t offset, unsigned long sect (*bmc)++; - spin_unlock_irq(&bitmap->lock); + spin_unlock_irq(&bitmap->counts.lock); offset += blocks; if (sectors > blocks) @@ -1364,10 +1351,10 @@ void bitmap_endwrite(struct bitmap *bitmap, sector_t offset, unsigned long secto unsigned long flags; bitmap_counter_t *bmc; - spin_lock_irqsave(&bitmap->lock, flags); - bmc = bitmap_get_counter(bitmap, offset, &blocks, 0); + spin_lock_irqsave(&bitmap->counts.lock, flags); + bmc = bitmap_get_counter(&bitmap->counts, offset, &blocks, 0); if (!bmc) { - spin_unlock_irqrestore(&bitmap->lock, flags); + spin_unlock_irqrestore(&bitmap->counts.lock, flags); return; } @@ -1386,14 +1373,10 @@ void bitmap_endwrite(struct bitmap *bitmap, sector_t offset, unsigned long secto (*bmc)--; if (*bmc <= 2) { - set_page_attr(bitmap, - filemap_get_page( - bitmap, - offset >> bitmap->chunkshift), - BITMAP_PAGE_PENDING); + bitmap_set_pending(&bitmap->counts, offset); bitmap->allclean = 0; } - spin_unlock_irqrestore(&bitmap->lock, flags); + spin_unlock_irqrestore(&bitmap->counts.lock, flags); offset += blocks; if (sectors > blocks) sectors -= blocks; @@ -1412,8 +1395,8 @@ static int __bitmap_start_sync(struct bitmap *bitmap, sector_t offset, sector_t *blocks = 1024; return 1; /* always resync if no bitmap */ } - spin_lock_irq(&bitmap->lock); - bmc = bitmap_get_counter(bitmap, offset, blocks, 0); + spin_lock_irq(&bitmap->counts.lock); + bmc = bitmap_get_counter(&bitmap->counts, offset, blocks, 0); rv = 0; if (bmc) { /* locked */ @@ -1427,7 +1410,7 @@ static int __bitmap_start_sync(struct bitmap *bitmap, sector_t offset, sector_t } } } - spin_unlock_irq(&bitmap->lock); + spin_unlock_irq(&bitmap->counts.lock); return rv; } @@ -1464,8 +1447,8 @@ void bitmap_end_sync(struct bitmap *bitmap, sector_t offset, sector_t *blocks, i *blocks = 1024; return; } - spin_lock_irqsave(&bitmap->lock, flags); - bmc = bitmap_get_counter(bitmap, offset, blocks, 0); + spin_lock_irqsave(&bitmap->counts.lock, flags); + bmc = bitmap_get_counter(&bitmap->counts, offset, blocks, 0); if (bmc == NULL) goto unlock; /* locked */ @@ -1476,15 +1459,13 @@ void bitmap_end_sync(struct bitmap *bitmap, sector_t offset, sector_t *blocks, i *bmc |= NEEDED_MASK; else { if (*bmc <= 2) { - set_page_attr(bitmap, - filemap_get_page(bitmap, offset >> bitmap->chunkshift), - BITMAP_PAGE_PENDING); + bitmap_set_pending(&bitmap->counts, offset); bitmap->allclean = 0; } } } unlock: - spin_unlock_irqrestore(&bitmap->lock, flags); + spin_unlock_irqrestore(&bitmap->counts.lock, flags); } EXPORT_SYMBOL(bitmap_end_sync); @@ -1524,7 +1505,7 @@ void bitmap_cond_end_sync(struct bitmap *bitmap, sector_t sector) bitmap->mddev->curr_resync_completed = sector; set_bit(MD_CHANGE_CLEAN, &bitmap->mddev->flags); - sector &= ~((1ULL << bitmap->chunkshift) - 1); + sector &= ~((1ULL << bitmap->counts.chunkshift) - 1); s = 0; while (s < sector && s < bitmap->mddev->resync_max_sectors) { bitmap_end_sync(bitmap, s, &blocks, 0); @@ -1538,27 +1519,25 @@ EXPORT_SYMBOL(bitmap_cond_end_sync); static void bitmap_set_memory_bits(struct bitmap *bitmap, sector_t offset, int needed) { /* For each chunk covered by any of these sectors, set the - * counter to 1 and set resync_needed. They should all + * counter to 2 and possibly set resync_needed. They should all * be 0 at this point */ sector_t secs; bitmap_counter_t *bmc; - spin_lock_irq(&bitmap->lock); - bmc = bitmap_get_counter(bitmap, offset, &secs, 1); + spin_lock_irq(&bitmap->counts.lock); + bmc = bitmap_get_counter(&bitmap->counts, offset, &secs, 1); if (!bmc) { - spin_unlock_irq(&bitmap->lock); + spin_unlock_irq(&bitmap->counts.lock); return; } if (!*bmc) { - struct page *page; *bmc = 2 | (needed ? NEEDED_MASK : 0); - bitmap_count_page(bitmap, offset, 1); - page = filemap_get_page(bitmap, offset >> bitmap->chunkshift); - set_page_attr(bitmap, page, BITMAP_PAGE_PENDING); + bitmap_count_page(&bitmap->counts, offset, 1); + bitmap_set_pending(&bitmap->counts, offset); bitmap->allclean = 0; } - spin_unlock_irq(&bitmap->lock); + spin_unlock_irq(&bitmap->counts.lock); } /* dirty the memory and file bits for bitmap chunks "s" to "e" */ @@ -1567,11 +1546,9 @@ void bitmap_dirty_bits(struct bitmap *bitmap, unsigned long s, unsigned long e) unsigned long chunk; for (chunk = s; chunk <= e; chunk++) { - sector_t sec = (sector_t)chunk << bitmap->chunkshift; + sector_t sec = (sector_t)chunk << bitmap->counts.chunkshift; bitmap_set_memory_bits(bitmap, sec, 1); - spin_lock_irq(&bitmap->lock); bitmap_file_set_bit(bitmap, sec); - spin_unlock_irq(&bitmap->lock); if (sec < bitmap->mddev->recovery_cp) /* We are asserting that the array is dirty, * so move the recovery_cp address back so @@ -1616,11 +1593,15 @@ static void bitmap_free(struct bitmap *bitmap) if (!bitmap) /* there was no bitmap */ return; - /* release the bitmap file and kill the daemon */ - bitmap_file_put(bitmap); + /* Shouldn't be needed - but just in case.... */ + wait_event(bitmap->write_wait, + atomic_read(&bitmap->pending_writes) == 0); + + /* release the bitmap file */ + bitmap_file_unmap(&bitmap->storage); - bp = bitmap->bp; - pages = bitmap->pages; + bp = bitmap->counts.bp; + pages = bitmap->counts.pages; /* free all allocated memory */ @@ -1659,25 +1640,19 @@ int bitmap_create(struct mddev *mddev) { struct bitmap *bitmap; sector_t blocks = mddev->resync_max_sectors; - unsigned long chunks; - unsigned long pages; struct file *file = mddev->bitmap_info.file; int err; struct sysfs_dirent *bm = NULL; BUILD_BUG_ON(sizeof(bitmap_super_t) != 256); - if (!file - && !mddev->bitmap_info.offset) /* bitmap disabled, nothing to do */ - return 0; - BUG_ON(file && mddev->bitmap_info.offset); bitmap = kzalloc(sizeof(*bitmap), GFP_KERNEL); if (!bitmap) return -ENOMEM; - spin_lock_init(&bitmap->lock); + spin_lock_init(&bitmap->counts.lock); atomic_set(&bitmap->pending_writes, 0); init_waitqueue_head(&bitmap->write_wait); init_waitqueue_head(&bitmap->overflow_wait); @@ -1693,7 +1668,7 @@ int bitmap_create(struct mddev *mddev) } else bitmap->sysfs_can_clear = NULL; - bitmap->file = file; + bitmap->storage.file = file; if (file) { get_file(file); /* As future accesses to this file will use bmap, @@ -1724,32 +1699,15 @@ int bitmap_create(struct mddev *mddev) goto error; bitmap->daemon_lastrun = jiffies; - bitmap->chunkshift = (ffz(~mddev->bitmap_info.chunksize) - - BITMAP_BLOCK_SHIFT); - - chunks = (blocks + (1 << bitmap->chunkshift) - 1) >> - bitmap->chunkshift; - pages = (chunks + PAGE_COUNTER_RATIO - 1) / PAGE_COUNTER_RATIO; - - BUG_ON(!pages); - - bitmap->chunks = chunks; - bitmap->pages = pages; - bitmap->missing_pages = pages; - - bitmap->bp = kzalloc(pages * sizeof(*bitmap->bp), GFP_KERNEL); - - err = -ENOMEM; - if (!bitmap->bp) + err = bitmap_resize(bitmap, blocks, mddev->bitmap_info.chunksize, 1); + if (err) goto error; printk(KERN_INFO "created bitmap (%lu pages) for device %s\n", - pages, bmname(bitmap)); + bitmap->counts.pages, bmname(bitmap)); mddev->bitmap = bitmap; - - - return (bitmap->flags & BITMAP_WRITE_ERROR) ? -EIO : 0; + return test_bit(BITMAP_WRITE_ERROR, &bitmap->flags) ? -EIO : 0; error: bitmap_free(bitmap); @@ -1790,13 +1748,17 @@ int bitmap_load(struct mddev *mddev) if (err) goto out; + clear_bit(BITMAP_STALE, &bitmap->flags); + + /* Kick recovery in case any bits were set */ + set_bit(MD_RECOVERY_NEEDED, &bitmap->mddev->recovery); mddev->thread->timeout = mddev->bitmap_info.daemon_sleep; md_wakeup_thread(mddev->thread); bitmap_update_sb(bitmap); - if (bitmap->flags & BITMAP_WRITE_ERROR) + if (test_bit(BITMAP_WRITE_ERROR, &bitmap->flags)) err = -EIO; out: return err; @@ -1806,30 +1768,194 @@ EXPORT_SYMBOL_GPL(bitmap_load); void bitmap_status(struct seq_file *seq, struct bitmap *bitmap) { unsigned long chunk_kb; - unsigned long flags; + struct bitmap_counts *counts; if (!bitmap) return; - spin_lock_irqsave(&bitmap->lock, flags); + counts = &bitmap->counts; + chunk_kb = bitmap->mddev->bitmap_info.chunksize >> 10; seq_printf(seq, "bitmap: %lu/%lu pages [%luKB], " "%lu%s chunk", - bitmap->pages - bitmap->missing_pages, - bitmap->pages, - (bitmap->pages - bitmap->missing_pages) + counts->pages - counts->missing_pages, + counts->pages, + (counts->pages - counts->missing_pages) << (PAGE_SHIFT - 10), chunk_kb ? chunk_kb : bitmap->mddev->bitmap_info.chunksize, chunk_kb ? "KB" : "B"); - if (bitmap->file) { + if (bitmap->storage.file) { seq_printf(seq, ", file: "); - seq_path(seq, &bitmap->file->f_path, " \t\n"); + seq_path(seq, &bitmap->storage.file->f_path, " \t\n"); } seq_printf(seq, "\n"); - spin_unlock_irqrestore(&bitmap->lock, flags); } +int bitmap_resize(struct bitmap *bitmap, sector_t blocks, + int chunksize, int init) +{ + /* If chunk_size is 0, choose an appropriate chunk size. + * Then possibly allocate new storage space. + * Then quiesce, copy bits, replace bitmap, and re-start + * + * This function is called both to set up the initial bitmap + * and to resize the bitmap while the array is active. + * If this happens as a result of the array being resized, + * chunksize will be zero, and we need to choose a suitable + * chunksize, otherwise we use what we are given. + */ + struct bitmap_storage store; + struct bitmap_counts old_counts; + unsigned long chunks; + sector_t block; + sector_t old_blocks, new_blocks; + int chunkshift; + int ret = 0; + long pages; + struct bitmap_page *new_bp; + + if (chunksize == 0) { + /* If there is enough space, leave the chunk size unchanged, + * else increase by factor of two until there is enough space. + */ + long bytes; + long space = bitmap->mddev->bitmap_info.space; + + if (space == 0) { + /* We don't know how much space there is, so limit + * to current size - in sectors. + */ + bytes = DIV_ROUND_UP(bitmap->counts.chunks, 8); + if (!bitmap->mddev->bitmap_info.external) + bytes += sizeof(bitmap_super_t); + space = DIV_ROUND_UP(bytes, 512); + bitmap->mddev->bitmap_info.space = space; + } + chunkshift = bitmap->counts.chunkshift; + chunkshift--; + do { + /* 'chunkshift' is shift from block size to chunk size */ + chunkshift++; + chunks = DIV_ROUND_UP_SECTOR_T(blocks, 1 << chunkshift); + bytes = DIV_ROUND_UP(chunks, 8); + if (!bitmap->mddev->bitmap_info.external) + bytes += sizeof(bitmap_super_t); + } while (bytes > (space << 9)); + } else + chunkshift = ffz(~chunksize) - BITMAP_BLOCK_SHIFT; + + chunks = DIV_ROUND_UP_SECTOR_T(blocks, 1 << chunkshift); + memset(&store, 0, sizeof(store)); + if (bitmap->mddev->bitmap_info.offset || bitmap->mddev->bitmap_info.file) + ret = bitmap_storage_alloc(&store, chunks, + !bitmap->mddev->bitmap_info.external); + if (ret) + goto err; + + pages = DIV_ROUND_UP(chunks, PAGE_COUNTER_RATIO); + + new_bp = kzalloc(pages * sizeof(*new_bp), GFP_KERNEL); + ret = -ENOMEM; + if (!new_bp) { + bitmap_file_unmap(&store); + goto err; + } + + if (!init) + bitmap->mddev->pers->quiesce(bitmap->mddev, 1); + + store.file = bitmap->storage.file; + bitmap->storage.file = NULL; + + if (store.sb_page && bitmap->storage.sb_page) + memcpy(page_address(store.sb_page), + page_address(bitmap->storage.sb_page), + sizeof(bitmap_super_t)); + bitmap_file_unmap(&bitmap->storage); + bitmap->storage = store; + + old_counts = bitmap->counts; + bitmap->counts.bp = new_bp; + bitmap->counts.pages = pages; + bitmap->counts.missing_pages = pages; + bitmap->counts.chunkshift = chunkshift; + bitmap->counts.chunks = chunks; + bitmap->mddev->bitmap_info.chunksize = 1 << (chunkshift + + BITMAP_BLOCK_SHIFT); + + blocks = min(old_counts.chunks << old_counts.chunkshift, + chunks << chunkshift); + + spin_lock_irq(&bitmap->counts.lock); + for (block = 0; block < blocks; ) { + bitmap_counter_t *bmc_old, *bmc_new; + int set; + + bmc_old = bitmap_get_counter(&old_counts, block, + &old_blocks, 0); + set = bmc_old && NEEDED(*bmc_old); + + if (set) { + bmc_new = bitmap_get_counter(&bitmap->counts, block, + &new_blocks, 1); + if (*bmc_new == 0) { + /* need to set on-disk bits too. */ + sector_t end = block + new_blocks; + sector_t start = block >> chunkshift; + start <<= chunkshift; + while (start < end) { + bitmap_file_set_bit(bitmap, block); + start += 1 << chunkshift; + } + *bmc_new = 2; + bitmap_count_page(&bitmap->counts, + block, 1); + bitmap_set_pending(&bitmap->counts, + block); + } + *bmc_new |= NEEDED_MASK; + if (new_blocks < old_blocks) + old_blocks = new_blocks; + } + block += old_blocks; + } + + if (!init) { + int i; + while (block < (chunks << chunkshift)) { + bitmap_counter_t *bmc; + bmc = bitmap_get_counter(&bitmap->counts, block, + &new_blocks, 1); + if (bmc) { + /* new space. It needs to be resynced, so + * we set NEEDED_MASK. + */ + if (*bmc == 0) { + *bmc = NEEDED_MASK | 2; + bitmap_count_page(&bitmap->counts, + block, 1); + bitmap_set_pending(&bitmap->counts, + block); + } + } + block += new_blocks; + } + for (i = 0; i < bitmap->storage.file_pages; i++) + set_page_attr(bitmap, i, BITMAP_PAGE_DIRTY); + } + spin_unlock_irq(&bitmap->counts.lock); + + if (!init) { + bitmap_unplug(bitmap); + bitmap->mddev->pers->quiesce(bitmap->mddev, 0); + } + ret = 0; +err: + return ret; +} +EXPORT_SYMBOL_GPL(bitmap_resize); + static ssize_t location_show(struct mddev *mddev, char *page) { @@ -1923,6 +2049,43 @@ location_store(struct mddev *mddev, const char *buf, size_t len) static struct md_sysfs_entry bitmap_location = __ATTR(location, S_IRUGO|S_IWUSR, location_show, location_store); +/* 'bitmap/space' is the space available at 'location' for the + * bitmap. This allows the kernel to know when it is safe to + * resize the bitmap to match a resized array. + */ +static ssize_t +space_show(struct mddev *mddev, char *page) +{ + return sprintf(page, "%lu\n", mddev->bitmap_info.space); +} + +static ssize_t +space_store(struct mddev *mddev, const char *buf, size_t len) +{ + unsigned long sectors; + int rv; + + rv = kstrtoul(buf, 10, §ors); + if (rv) + return rv; + + if (sectors == 0) + return -EINVAL; + + if (mddev->bitmap && + sectors < (mddev->bitmap->storage.bytes + 511) >> 9) + return -EFBIG; /* Bitmap is too big for this small space */ + + /* could make sure it isn't too big, but that isn't really + * needed - user-space should be careful. + */ + mddev->bitmap_info.space = sectors; + return len; +} + +static struct md_sysfs_entry bitmap_space = +__ATTR(space, S_IRUGO|S_IWUSR, space_show, space_store); + static ssize_t timeout_show(struct mddev *mddev, char *page) { @@ -2098,6 +2261,7 @@ __ATTR(max_backlog_used, S_IRUGO | S_IWUSR, static struct attribute *md_bitmap_attrs[] = { &bitmap_location.attr, + &bitmap_space.attr, &bitmap_timeout.attr, &bitmap_backlog.attr, &bitmap_chunksize.attr, diff --git a/drivers/md/bitmap.h b/drivers/md/bitmap.h index b44b0aba2d47..df4aeb6ac6f0 100644 --- a/drivers/md/bitmap.h +++ b/drivers/md/bitmap.h @@ -111,9 +111,9 @@ typedef __u16 bitmap_counter_t; /* use these for bitmap->flags and bitmap->sb->state bit-fields */ enum bitmap_state { - BITMAP_STALE = 0x002, /* the bitmap file is out of date or had -EIO */ - BITMAP_WRITE_ERROR = 0x004, /* A write error has occurred */ - BITMAP_HOSTENDIAN = 0x8000, + BITMAP_STALE = 1, /* the bitmap file is out of date or had -EIO */ + BITMAP_WRITE_ERROR = 2, /* A write error has occurred */ + BITMAP_HOSTENDIAN =15, }; /* the superblock at the front of the bitmap file -- little endian */ @@ -128,8 +128,10 @@ typedef struct bitmap_super_s { __le32 chunksize; /* 52 the bitmap chunk size in bytes */ __le32 daemon_sleep; /* 56 seconds between disk flushes */ __le32 write_behind; /* 60 number of outstanding write-behind writes */ + __le32 sectors_reserved; /* 64 number of 512-byte sectors that are + * reserved for the bitmap. */ - __u8 pad[256 - 64]; /* set to zero */ + __u8 pad[256 - 68]; /* set to zero */ } bitmap_super_t; /* notes: @@ -160,35 +162,48 @@ struct bitmap_page { */ unsigned int hijacked:1; /* + * If any counter in this page is '1' or '2' - and so could be + * cleared then that page is marked as 'pending' + */ + unsigned int pending:1; + /* * count of dirty bits on the page */ - unsigned int count:31; + unsigned int count:30; }; /* the main bitmap structure - one per mddev */ struct bitmap { - struct bitmap_page *bp; - unsigned long pages; /* total number of pages in the bitmap */ - unsigned long missing_pages; /* number of pages not yet allocated */ - struct mddev *mddev; /* the md device that the bitmap is for */ + struct bitmap_counts { + spinlock_t lock; + struct bitmap_page *bp; + unsigned long pages; /* total number of pages + * in the bitmap */ + unsigned long missing_pages; /* number of pages + * not yet allocated */ + unsigned long chunkshift; /* chunksize = 2^chunkshift + * (for bitops) */ + unsigned long chunks; /* Total number of data + * chunks for the array */ + } counts; - /* bitmap chunksize -- how much data does each bit represent? */ - unsigned long chunkshift; /* chunksize = 2^(chunkshift+9) (for bitops) */ - unsigned long chunks; /* total number of data chunks for the array */ + struct mddev *mddev; /* the md device that the bitmap is for */ __u64 events_cleared; int need_sync; - /* bitmap spinlock */ - spinlock_t lock; - - struct file *file; /* backing disk file */ - struct page *sb_page; /* cached copy of the bitmap file superblock */ - struct page **filemap; /* list of cache pages for the file */ - unsigned long *filemap_attr; /* attributes associated w/ filemap pages */ - unsigned long file_pages; /* number of pages in the file */ - int last_page_size; /* bytes in the last page */ + struct bitmap_storage { + struct file *file; /* backing disk file */ + struct page *sb_page; /* cached copy of the bitmap + * file superblock */ + struct page **filemap; /* list of cache pages for + * the file */ + unsigned long *filemap_attr; /* attributes associated + * w/ filemap pages */ + unsigned long file_pages; /* number of pages in the file*/ + unsigned long bytes; /* total bytes in the bitmap */ + } storage; unsigned long flags; @@ -242,6 +257,9 @@ void bitmap_cond_end_sync(struct bitmap *bitmap, sector_t sector); void bitmap_unplug(struct bitmap *bitmap); void bitmap_daemon_work(struct mddev *mddev); + +int bitmap_resize(struct bitmap *bitmap, sector_t blocks, + int chunksize, int init); #endif #endif diff --git a/drivers/md/dm-raid.c b/drivers/md/dm-raid.c index 68965e663248..017c34d78d61 100644 --- a/drivers/md/dm-raid.c +++ b/drivers/md/dm-raid.c @@ -155,10 +155,7 @@ static void context_free(struct raid_set *rs) for (i = 0; i < rs->md.raid_disks; i++) { if (rs->dev[i].meta_dev) dm_put_device(rs->ti, rs->dev[i].meta_dev); - if (rs->dev[i].rdev.sb_page) - put_page(rs->dev[i].rdev.sb_page); - rs->dev[i].rdev.sb_page = NULL; - rs->dev[i].rdev.sb_loaded = 0; + md_rdev_clear(&rs->dev[i].rdev); if (rs->dev[i].data_dev) dm_put_device(rs->ti, rs->dev[i].data_dev); } @@ -606,7 +603,7 @@ static int read_disk_sb(struct md_rdev *rdev, int size) if (!sync_page_io(rdev, 0, size, rdev->sb_page, READ, 1)) { DMERR("Failed to read superblock of device at position %d", rdev->raid_disk); - set_bit(Faulty, &rdev->flags); + md_error(rdev->mddev, rdev); return -EINVAL; } @@ -617,16 +614,18 @@ static int read_disk_sb(struct md_rdev *rdev, int size) static void super_sync(struct mddev *mddev, struct md_rdev *rdev) { - struct md_rdev *r; + int i; uint64_t failed_devices; struct dm_raid_superblock *sb; + struct raid_set *rs = container_of(mddev, struct raid_set, md); sb = page_address(rdev->sb_page); failed_devices = le64_to_cpu(sb->failed_devices); - rdev_for_each(r, mddev) - if ((r->raid_disk >= 0) && test_bit(Faulty, &r->flags)) - failed_devices |= (1ULL << r->raid_disk); + for (i = 0; i < mddev->raid_disks; i++) + if (!rs->dev[i].data_dev || + test_bit(Faulty, &(rs->dev[i].rdev.flags))) + failed_devices |= (1ULL << i); memset(sb, 0, sizeof(*sb)); @@ -1252,12 +1251,13 @@ static void raid_resume(struct dm_target *ti) { struct raid_set *rs = ti->private; + set_bit(MD_CHANGE_DEVS, &rs->md.flags); if (!rs->bitmap_loaded) { bitmap_load(&rs->md); rs->bitmap_loaded = 1; - } else - md_wakeup_thread(rs->md.thread); + } + clear_bit(MD_RECOVERY_FROZEN, &rs->md.recovery); mddev_resume(&rs->md); } diff --git a/drivers/md/md.c b/drivers/md/md.c index 01233d855eb2..1c2f9048e1ae 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -402,6 +402,7 @@ void mddev_resume(struct mddev *mddev) wake_up(&mddev->sb_wait); mddev->pers->quiesce(mddev, 0); + set_bit(MD_RECOVERY_NEEDED, &mddev->recovery); md_wakeup_thread(mddev->thread); md_wakeup_thread(mddev->sync_thread); /* possibly kick off a reshape */ } @@ -452,7 +453,7 @@ static void submit_flushes(struct work_struct *ws) atomic_inc(&rdev->nr_pending); atomic_inc(&rdev->nr_pending); rcu_read_unlock(); - bi = bio_alloc_mddev(GFP_KERNEL, 0, mddev); + bi = bio_alloc_mddev(GFP_NOIO, 0, mddev); bi->bi_end_io = md_end_flush; bi->bi_private = rdev; bi->bi_bdev = rdev->bdev; @@ -607,6 +608,7 @@ void mddev_init(struct mddev *mddev) init_waitqueue_head(&mddev->sb_wait); init_waitqueue_head(&mddev->recovery_wait); mddev->reshape_position = MaxSector; + mddev->reshape_backwards = 0; mddev->resync_min = 0; mddev->resync_max = MaxSector; mddev->level = LEVEL_NONE; @@ -802,7 +804,7 @@ static int alloc_disk_sb(struct md_rdev * rdev) return 0; } -static void free_disk_sb(struct md_rdev * rdev) +void md_rdev_clear(struct md_rdev *rdev) { if (rdev->sb_page) { put_page(rdev->sb_page); @@ -815,8 +817,10 @@ static void free_disk_sb(struct md_rdev * rdev) put_page(rdev->bb_page); rdev->bb_page = NULL; } + kfree(rdev->badblocks.page); + rdev->badblocks.page = NULL; } - +EXPORT_SYMBOL_GPL(md_rdev_clear); static void super_written(struct bio *bio, int error) { @@ -887,6 +891,10 @@ int sync_page_io(struct md_rdev *rdev, sector_t sector, int size, rdev->meta_bdev : rdev->bdev; if (metadata_op) bio->bi_sector = sector + rdev->sb_start; + else if (rdev->mddev->reshape_position != MaxSector && + (rdev->mddev->reshape_backwards == + (sector >= rdev->mddev->reshape_position))) + bio->bi_sector = sector + rdev->new_data_offset; else bio->bi_sector = sector + rdev->data_offset; bio_add_page(bio, page, size, 0); @@ -1034,12 +1042,17 @@ static unsigned int calc_sb_csum(mdp_super_t * sb) struct super_type { char *name; struct module *owner; - int (*load_super)(struct md_rdev *rdev, struct md_rdev *refdev, + int (*load_super)(struct md_rdev *rdev, + struct md_rdev *refdev, int minor_version); - int (*validate_super)(struct mddev *mddev, struct md_rdev *rdev); - void (*sync_super)(struct mddev *mddev, struct md_rdev *rdev); + int (*validate_super)(struct mddev *mddev, + struct md_rdev *rdev); + void (*sync_super)(struct mddev *mddev, + struct md_rdev *rdev); unsigned long long (*rdev_size_change)(struct md_rdev *rdev, sector_t num_sectors); + int (*allow_new_offset)(struct md_rdev *rdev, + unsigned long long new_offset); }; /* @@ -1111,6 +1124,7 @@ static int super_90_load(struct md_rdev *rdev, struct md_rdev *refdev, int minor rdev->preferred_minor = sb->md_minor; rdev->data_offset = 0; + rdev->new_data_offset = 0; rdev->sb_size = MD_SB_BYTES; rdev->badblocks.shift = -1; @@ -1184,7 +1198,11 @@ static int super_90_validate(struct mddev *mddev, struct md_rdev *rdev) mddev->dev_sectors = ((sector_t)sb->size) * 2; mddev->events = ev1; mddev->bitmap_info.offset = 0; + mddev->bitmap_info.space = 0; + /* bitmap can use 60 K after the 4K superblocks */ mddev->bitmap_info.default_offset = MD_SB_BYTES >> 9; + mddev->bitmap_info.default_space = 64*2 - (MD_SB_BYTES >> 9); + mddev->reshape_backwards = 0; if (mddev->minor_version >= 91) { mddev->reshape_position = sb->reshape_position; @@ -1192,6 +1210,8 @@ static int super_90_validate(struct mddev *mddev, struct md_rdev *rdev) mddev->new_level = sb->new_level; mddev->new_layout = sb->new_layout; mddev->new_chunk_sectors = sb->new_chunk >> 9; + if (mddev->delta_disks < 0) + mddev->reshape_backwards = 1; } else { mddev->reshape_position = MaxSector; mddev->delta_disks = 0; @@ -1218,9 +1238,12 @@ static int super_90_validate(struct mddev *mddev, struct md_rdev *rdev) mddev->max_disks = MD_SB_DISKS; if (sb->state & (1<<MD_SB_BITMAP_PRESENT) && - mddev->bitmap_info.file == NULL) + mddev->bitmap_info.file == NULL) { mddev->bitmap_info.offset = mddev->bitmap_info.default_offset; + mddev->bitmap_info.space = + mddev->bitmap_info.space; + } } else if (mddev->pers == NULL) { /* Insist on good event counter while assembling, except @@ -1434,6 +1457,12 @@ super_90_rdev_size_change(struct md_rdev *rdev, sector_t num_sectors) return num_sectors; } +static int +super_90_allow_new_offset(struct md_rdev *rdev, unsigned long long new_offset) +{ + /* non-zero offset changes not possible with v0.90 */ + return new_offset == 0; +} /* * version 1 superblock @@ -1469,6 +1498,7 @@ static int super_1_load(struct md_rdev *rdev, struct md_rdev *refdev, int minor_ struct mdp_superblock_1 *sb; int ret; sector_t sb_start; + sector_t sectors; char b[BDEVNAME_SIZE], b2[BDEVNAME_SIZE]; int bmask; @@ -1523,9 +1553,18 @@ static int super_1_load(struct md_rdev *rdev, struct md_rdev *refdev, int minor_ bdevname(rdev->bdev,b)); return -EINVAL; } + if (sb->pad0 || + sb->pad3[0] || + memcmp(sb->pad3, sb->pad3+1, sizeof(sb->pad3) - sizeof(sb->pad3[1]))) + /* Some padding is non-zero, might be a new feature */ + return -EINVAL; rdev->preferred_minor = 0xffff; rdev->data_offset = le64_to_cpu(sb->data_offset); + rdev->new_data_offset = rdev->data_offset; + if ((le32_to_cpu(sb->feature_map) & MD_FEATURE_RESHAPE_ACTIVE) && + (le32_to_cpu(sb->feature_map) & MD_FEATURE_NEW_OFFSET)) + rdev->new_data_offset += (s32)le32_to_cpu(sb->new_offset); atomic_set(&rdev->corrected_errors, le32_to_cpu(sb->cnt_corrected_read)); rdev->sb_size = le32_to_cpu(sb->max_dev) * 2 + 256; @@ -1536,6 +1575,9 @@ static int super_1_load(struct md_rdev *rdev, struct md_rdev *refdev, int minor_ if (minor_version && rdev->data_offset < sb_start + (rdev->sb_size/512)) return -EINVAL; + if (minor_version + && rdev->new_data_offset < sb_start + (rdev->sb_size/512)) + return -EINVAL; if (sb->level == cpu_to_le32(LEVEL_MULTIPATH)) rdev->desc_nr = -1; @@ -1607,16 +1649,14 @@ static int super_1_load(struct md_rdev *rdev, struct md_rdev *refdev, int minor_ else ret = 0; } - if (minor_version) - rdev->sectors = (i_size_read(rdev->bdev->bd_inode) >> 9) - - le64_to_cpu(sb->data_offset); - else - rdev->sectors = rdev->sb_start; - if (rdev->sectors < le64_to_cpu(sb->data_size)) + if (minor_version) { + sectors = (i_size_read(rdev->bdev->bd_inode) >> 9); + sectors -= rdev->data_offset; + } else + sectors = rdev->sb_start; + if (sectors < le64_to_cpu(sb->data_size)) return -EINVAL; rdev->sectors = le64_to_cpu(sb->data_size); - if (le64_to_cpu(sb->size) > rdev->sectors) - return -EINVAL; return ret; } @@ -1644,17 +1684,37 @@ static int super_1_validate(struct mddev *mddev, struct md_rdev *rdev) mddev->dev_sectors = le64_to_cpu(sb->size); mddev->events = ev1; mddev->bitmap_info.offset = 0; + mddev->bitmap_info.space = 0; + /* Default location for bitmap is 1K after superblock + * using 3K - total of 4K + */ mddev->bitmap_info.default_offset = 1024 >> 9; - + mddev->bitmap_info.default_space = (4096-1024) >> 9; + mddev->reshape_backwards = 0; + mddev->recovery_cp = le64_to_cpu(sb->resync_offset); memcpy(mddev->uuid, sb->set_uuid, 16); mddev->max_disks = (4096-256)/2; if ((le32_to_cpu(sb->feature_map) & MD_FEATURE_BITMAP_OFFSET) && - mddev->bitmap_info.file == NULL ) + mddev->bitmap_info.file == NULL) { mddev->bitmap_info.offset = (__s32)le32_to_cpu(sb->bitmap_offset); + /* Metadata doesn't record how much space is available. + * For 1.0, we assume we can use up to the superblock + * if before, else to 4K beyond superblock. + * For others, assume no change is possible. + */ + if (mddev->minor_version > 0) + mddev->bitmap_info.space = 0; + else if (mddev->bitmap_info.offset > 0) + mddev->bitmap_info.space = + 8 - mddev->bitmap_info.offset; + else + mddev->bitmap_info.space = + -mddev->bitmap_info.offset; + } if ((le32_to_cpu(sb->feature_map) & MD_FEATURE_RESHAPE_ACTIVE)) { mddev->reshape_position = le64_to_cpu(sb->reshape_position); @@ -1662,6 +1722,11 @@ static int super_1_validate(struct mddev *mddev, struct md_rdev *rdev) mddev->new_level = le32_to_cpu(sb->new_level); mddev->new_layout = le32_to_cpu(sb->new_layout); mddev->new_chunk_sectors = le32_to_cpu(sb->new_chunk); + if (mddev->delta_disks < 0 || + (mddev->delta_disks == 0 && + (le32_to_cpu(sb->feature_map) + & MD_FEATURE_RESHAPE_BACKWARDS))) + mddev->reshape_backwards = 1; } else { mddev->reshape_position = MaxSector; mddev->delta_disks = 0; @@ -1735,7 +1800,6 @@ static void super_1_sync(struct mddev *mddev, struct md_rdev *rdev) sb->feature_map = 0; sb->pad0 = 0; sb->recovery_offset = cpu_to_le64(0); - memset(sb->pad1, 0, sizeof(sb->pad1)); memset(sb->pad3, 0, sizeof(sb->pad3)); sb->utime = cpu_to_le64((__u64)mddev->utime); @@ -1757,6 +1821,8 @@ static void super_1_sync(struct mddev *mddev, struct md_rdev *rdev) sb->devflags |= WriteMostly1; else sb->devflags &= ~WriteMostly1; + sb->data_offset = cpu_to_le64(rdev->data_offset); + sb->data_size = cpu_to_le64(rdev->sectors); if (mddev->bitmap && mddev->bitmap_info.file == NULL) { sb->bitmap_offset = cpu_to_le32((__u32)mddev->bitmap_info.offset); @@ -1781,6 +1847,16 @@ static void super_1_sync(struct mddev *mddev, struct md_rdev *rdev) sb->delta_disks = cpu_to_le32(mddev->delta_disks); sb->new_level = cpu_to_le32(mddev->new_level); sb->new_chunk = cpu_to_le32(mddev->new_chunk_sectors); + if (mddev->delta_disks == 0 && + mddev->reshape_backwards) + sb->feature_map + |= cpu_to_le32(MD_FEATURE_RESHAPE_BACKWARDS); + if (rdev->new_data_offset != rdev->data_offset) { + sb->feature_map + |= cpu_to_le32(MD_FEATURE_NEW_OFFSET); + sb->new_offset = cpu_to_le32((__u32)(rdev->new_data_offset + - rdev->data_offset)); + } } if (rdev->badblocks.count == 0) @@ -1857,6 +1933,8 @@ super_1_rdev_size_change(struct md_rdev *rdev, sector_t num_sectors) sector_t max_sectors; if (num_sectors && num_sectors < rdev->mddev->dev_sectors) return 0; /* component must fit device */ + if (rdev->data_offset != rdev->new_data_offset) + return 0; /* too confusing */ if (rdev->sb_start < rdev->data_offset) { /* minor versions 1 and 2; superblock before data */ max_sectors = i_size_read(rdev->bdev->bd_inode) >> 9; @@ -1884,6 +1962,40 @@ super_1_rdev_size_change(struct md_rdev *rdev, sector_t num_sectors) rdev->sb_page); md_super_wait(rdev->mddev); return num_sectors; + +} + +static int +super_1_allow_new_offset(struct md_rdev *rdev, + unsigned long long new_offset) +{ + /* All necessary checks on new >= old have been done */ + struct bitmap *bitmap; + if (new_offset >= rdev->data_offset) + return 1; + + /* with 1.0 metadata, there is no metadata to tread on + * so we can always move back */ + if (rdev->mddev->minor_version == 0) + return 1; + + /* otherwise we must be sure not to step on + * any metadata, so stay: + * 36K beyond start of superblock + * beyond end of badblocks + * beyond write-intent bitmap + */ + if (rdev->sb_start + (32+4)*2 > new_offset) + return 0; + bitmap = rdev->mddev->bitmap; + if (bitmap && !rdev->mddev->bitmap_info.file && + rdev->sb_start + rdev->mddev->bitmap_info.offset + + bitmap->storage.file_pages * (PAGE_SIZE>>9) > new_offset) + return 0; + if (rdev->badblocks.sector + rdev->badblocks.size > new_offset) + return 0; + + return 1; } static struct super_type super_types[] = { @@ -1894,6 +2006,7 @@ static struct super_type super_types[] = { .validate_super = super_90_validate, .sync_super = super_90_sync, .rdev_size_change = super_90_rdev_size_change, + .allow_new_offset = super_90_allow_new_offset, }, [1] = { .name = "md-1", @@ -1902,6 +2015,7 @@ static struct super_type super_types[] = { .validate_super = super_1_validate, .sync_super = super_1_sync, .rdev_size_change = super_1_rdev_size_change, + .allow_new_offset = super_1_allow_new_offset, }, }; @@ -2105,9 +2219,7 @@ static void unbind_rdev_from_array(struct md_rdev * rdev) sysfs_remove_link(&rdev->kobj, "block"); sysfs_put(rdev->sysfs_state); rdev->sysfs_state = NULL; - kfree(rdev->badblocks.page); rdev->badblocks.count = 0; - rdev->badblocks.page = NULL; /* We need to delay this, otherwise we can deadlock when * writing to 'remove' to "dev/state". We also need * to delay it due to rcu usage. @@ -2158,7 +2270,7 @@ static void export_rdev(struct md_rdev * rdev) bdevname(rdev->bdev,b)); if (rdev->mddev) MD_BUG(); - free_disk_sb(rdev); + md_rdev_clear(rdev); #ifndef MODULE if (test_bit(AutoDetected, &rdev->flags)) md_autodetect_dev(rdev->bdev->bd_dev); @@ -2809,9 +2921,8 @@ offset_show(struct md_rdev *rdev, char *page) static ssize_t offset_store(struct md_rdev *rdev, const char *buf, size_t len) { - char *e; - unsigned long long offset = simple_strtoull(buf, &e, 10); - if (e==buf || (*e && *e != '\n')) + unsigned long long offset; + if (strict_strtoull(buf, 10, &offset) < 0) return -EINVAL; if (rdev->mddev->pers && rdev->raid_disk >= 0) return -EBUSY; @@ -2826,6 +2937,63 @@ offset_store(struct md_rdev *rdev, const char *buf, size_t len) static struct rdev_sysfs_entry rdev_offset = __ATTR(offset, S_IRUGO|S_IWUSR, offset_show, offset_store); +static ssize_t new_offset_show(struct md_rdev *rdev, char *page) +{ + return sprintf(page, "%llu\n", + (unsigned long long)rdev->new_data_offset); +} + +static ssize_t new_offset_store(struct md_rdev *rdev, + const char *buf, size_t len) +{ + unsigned long long new_offset; + struct mddev *mddev = rdev->mddev; + + if (strict_strtoull(buf, 10, &new_offset) < 0) + return -EINVAL; + + if (mddev->sync_thread) + return -EBUSY; + if (new_offset == rdev->data_offset) + /* reset is always permitted */ + ; + else if (new_offset > rdev->data_offset) { + /* must not push array size beyond rdev_sectors */ + if (new_offset - rdev->data_offset + + mddev->dev_sectors > rdev->sectors) + return -E2BIG; + } + /* Metadata worries about other space details. */ + + /* decreasing the offset is inconsistent with a backwards + * reshape. + */ + if (new_offset < rdev->data_offset && + mddev->reshape_backwards) + return -EINVAL; + /* Increasing offset is inconsistent with forwards + * reshape. reshape_direction should be set to + * 'backwards' first. + */ + if (new_offset > rdev->data_offset && + !mddev->reshape_backwards) + return -EINVAL; + + if (mddev->pers && mddev->persistent && + !super_types[mddev->major_version] + .allow_new_offset(rdev, new_offset)) + return -E2BIG; + rdev->new_data_offset = new_offset; + if (new_offset > rdev->data_offset) + mddev->reshape_backwards = 1; + else if (new_offset < rdev->data_offset) + mddev->reshape_backwards = 0; + + return len; +} +static struct rdev_sysfs_entry rdev_new_offset = +__ATTR(new_offset, S_IRUGO|S_IWUSR, new_offset_show, new_offset_store); + static ssize_t rdev_size_show(struct md_rdev *rdev, char *page) { @@ -2870,6 +3038,8 @@ rdev_size_store(struct md_rdev *rdev, const char *buf, size_t len) if (strict_blocks_to_sectors(buf, §ors) < 0) return -EINVAL; + if (rdev->data_offset != rdev->new_data_offset) + return -EINVAL; /* too confusing */ if (my_mddev->pers && rdev->raid_disk >= 0) { if (my_mddev->persistent) { sectors = super_types[my_mddev->major_version]. @@ -3006,6 +3176,7 @@ static struct attribute *rdev_default_attrs[] = { &rdev_errors.attr, &rdev_slot.attr, &rdev_offset.attr, + &rdev_new_offset.attr, &rdev_size.attr, &rdev_recovery_start.attr, &rdev_bad_blocks.attr, @@ -3080,6 +3251,7 @@ int md_rdev_init(struct md_rdev *rdev) rdev->raid_disk = -1; rdev->flags = 0; rdev->data_offset = 0; + rdev->new_data_offset = 0; rdev->sb_events = 0; rdev->last_read_error.tv_sec = 0; rdev->last_read_error.tv_nsec = 0; @@ -3178,8 +3350,7 @@ static struct md_rdev *md_import_device(dev_t newdev, int super_format, int supe abort_free: if (rdev->bdev) unlock_rdev(rdev); - free_disk_sb(rdev); - kfree(rdev->badblocks.page); + md_rdev_clear(rdev); kfree(rdev); return ERR_PTR(err); } @@ -3419,6 +3590,7 @@ level_store(struct mddev *mddev, const char *buf, size_t len) mddev->new_chunk_sectors = mddev->chunk_sectors; mddev->raid_disks -= mddev->delta_disks; mddev->delta_disks = 0; + mddev->reshape_backwards = 0; module_put(pers->owner); printk(KERN_WARNING "md: %s: %s would not accept array\n", mdname(mddev), clevel); @@ -3492,6 +3664,7 @@ level_store(struct mddev *mddev, const char *buf, size_t len) mddev->layout = mddev->new_layout; mddev->chunk_sectors = mddev->new_chunk_sectors; mddev->delta_disks = 0; + mddev->reshape_backwards = 0; mddev->degraded = 0; if (mddev->pers->sync_request == NULL) { /* this is now an array without redundancy, so @@ -3501,10 +3674,8 @@ level_store(struct mddev *mddev, const char *buf, size_t len) del_timer_sync(&mddev->safemode_timer); } pers->run(mddev); - mddev_resume(mddev); set_bit(MD_CHANGE_DEVS, &mddev->flags); - set_bit(MD_RECOVERY_NEEDED, &mddev->recovery); - md_wakeup_thread(mddev->thread); + mddev_resume(mddev); sysfs_notify(&mddev->kobj, NULL, "level"); md_new_event(mddev); return rv; @@ -3582,9 +3753,20 @@ raid_disks_store(struct mddev *mddev, const char *buf, size_t len) if (mddev->pers) rv = update_raid_disks(mddev, n); else if (mddev->reshape_position != MaxSector) { + struct md_rdev *rdev; int olddisks = mddev->raid_disks - mddev->delta_disks; + + rdev_for_each(rdev, mddev) { + if (olddisks < n && + rdev->data_offset < rdev->new_data_offset) + return -EINVAL; + if (olddisks > n && + rdev->data_offset > rdev->new_data_offset) + return -EINVAL; + } mddev->delta_disks = n - olddisks; mddev->raid_disks = n; + mddev->reshape_backwards = (mddev->delta_disks < 0); } else mddev->raid_disks = n; return rv ? rv : len; @@ -4266,7 +4448,8 @@ sync_completed_show(struct mddev *mddev, char *page) if (!test_bit(MD_RECOVERY_RUNNING, &mddev->recovery)) return sprintf(page, "none\n"); - if (test_bit(MD_RECOVERY_SYNC, &mddev->recovery)) + if (test_bit(MD_RECOVERY_SYNC, &mddev->recovery) || + test_bit(MD_RECOVERY_RESHAPE, &mddev->recovery)) max_sectors = mddev->resync_max_sectors; else max_sectors = mddev->dev_sectors; @@ -4428,6 +4611,7 @@ reshape_position_show(struct mddev *mddev, char *page) static ssize_t reshape_position_store(struct mddev *mddev, const char *buf, size_t len) { + struct md_rdev *rdev; char *e; unsigned long long new = simple_strtoull(buf, &e, 10); if (mddev->pers) @@ -4436,9 +4620,12 @@ reshape_position_store(struct mddev *mddev, const char *buf, size_t len) return -EINVAL; mddev->reshape_position = new; mddev->delta_disks = 0; + mddev->reshape_backwards = 0; mddev->new_level = mddev->level; mddev->new_layout = mddev->layout; mddev->new_chunk_sectors = mddev->chunk_sectors; + rdev_for_each(rdev, mddev) + rdev->new_data_offset = rdev->data_offset; return len; } @@ -4447,6 +4634,42 @@ __ATTR(reshape_position, S_IRUGO|S_IWUSR, reshape_position_show, reshape_position_store); static ssize_t +reshape_direction_show(struct mddev *mddev, char *page) +{ + return sprintf(page, "%s\n", + mddev->reshape_backwards ? "backwards" : "forwards"); +} + +static ssize_t +reshape_direction_store(struct mddev *mddev, const char *buf, size_t len) +{ + int backwards = 0; + if (cmd_match(buf, "forwards")) + backwards = 0; + else if (cmd_match(buf, "backwards")) + backwards = 1; + else + return -EINVAL; + if (mddev->reshape_backwards == backwards) + return len; + + /* check if we are allowed to change */ + if (mddev->delta_disks) + return -EBUSY; + + if (mddev->persistent && + mddev->major_version == 0) + return -EINVAL; + + mddev->reshape_backwards = backwards; + return len; +} + +static struct md_sysfs_entry md_reshape_direction = +__ATTR(reshape_direction, S_IRUGO|S_IWUSR, reshape_direction_show, + reshape_direction_store); + +static ssize_t array_size_show(struct mddev *mddev, char *page) { if (mddev->external_size) @@ -4501,6 +4724,7 @@ static struct attribute *md_default_attrs[] = { &md_safe_delay.attr, &md_array_state.attr, &md_reshape_position.attr, + &md_reshape_direction.attr, &md_array_size.attr, &max_corr_read_errors.attr, NULL, @@ -4914,7 +5138,8 @@ int md_run(struct mddev *mddev) err = -EINVAL; mddev->pers->stop(mddev); } - if (err == 0 && mddev->pers->sync_request) { + if (err == 0 && mddev->pers->sync_request && + (mddev->bitmap_info.file || mddev->bitmap_info.offset)) { err = bitmap_create(mddev); if (err) { printk(KERN_ERR "%s: failed to create bitmap (%d)\n", @@ -5064,6 +5289,7 @@ static void md_clean(struct mddev *mddev) mddev->events = 0; mddev->can_decrease_events = 0; mddev->delta_disks = 0; + mddev->reshape_backwards = 0; mddev->new_level = LEVEL_NONE; mddev->new_layout = 0; mddev->new_chunk_sectors = 0; @@ -5079,6 +5305,7 @@ static void md_clean(struct mddev *mddev) mddev->merge_check_needed = 0; mddev->bitmap_info.offset = 0; mddev->bitmap_info.default_offset = 0; + mddev->bitmap_info.default_space = 0; mddev->bitmap_info.chunksize = 0; mddev->bitmap_info.daemon_sleep = 0; mddev->bitmap_info.max_write_behind = 0; @@ -5421,7 +5648,7 @@ static int get_bitmap_file(struct mddev * mddev, void __user * arg) goto out; /* bitmap disabled, zero the first byte and copy out */ - if (!mddev->bitmap || !mddev->bitmap->file) { + if (!mddev->bitmap || !mddev->bitmap->storage.file) { file->pathname[0] = '\0'; goto copy_out; } @@ -5430,7 +5657,8 @@ static int get_bitmap_file(struct mddev * mddev, void __user * arg) if (!buf) goto out; - ptr = d_path(&mddev->bitmap->file->f_path, buf, sizeof(file->pathname)); + ptr = d_path(&mddev->bitmap->storage.file->f_path, + buf, sizeof(file->pathname)); if (IS_ERR(ptr)) goto out; @@ -5875,6 +6103,7 @@ static int set_array_info(struct mddev * mddev, mdu_array_info_t *info) set_bit(MD_CHANGE_DEVS, &mddev->flags); mddev->bitmap_info.default_offset = MD_SB_BYTES >> 9; + mddev->bitmap_info.default_space = 64*2 - (MD_SB_BYTES >> 9); mddev->bitmap_info.offset = 0; mddev->reshape_position = MaxSector; @@ -5888,6 +6117,7 @@ static int set_array_info(struct mddev * mddev, mdu_array_info_t *info) mddev->new_chunk_sectors = mddev->chunk_sectors; mddev->new_layout = mddev->layout; mddev->delta_disks = 0; + mddev->reshape_backwards = 0; return 0; } @@ -5922,11 +6152,7 @@ static int update_size(struct mddev *mddev, sector_t num_sectors) */ if (mddev->sync_thread) return -EBUSY; - if (mddev->bitmap) - /* Sorry, cannot grow a bitmap yet, just remove it, - * grow, and re-add. - */ - return -EBUSY; + rdev_for_each(rdev, mddev) { sector_t avail = rdev->sectors; @@ -5944,6 +6170,7 @@ static int update_size(struct mddev *mddev, sector_t num_sectors) static int update_raid_disks(struct mddev *mddev, int raid_disks) { int rv; + struct md_rdev *rdev; /* change the number of raid disks */ if (mddev->pers->check_reshape == NULL) return -EINVAL; @@ -5952,11 +6179,27 @@ static int update_raid_disks(struct mddev *mddev, int raid_disks) return -EINVAL; if (mddev->sync_thread || mddev->reshape_position != MaxSector) return -EBUSY; + + rdev_for_each(rdev, mddev) { + if (mddev->raid_disks < raid_disks && + rdev->data_offset < rdev->new_data_offset) + return -EINVAL; + if (mddev->raid_disks > raid_disks && + rdev->data_offset > rdev->new_data_offset) + return -EINVAL; + } + mddev->delta_disks = raid_disks - mddev->raid_disks; + if (mddev->delta_disks < 0) + mddev->reshape_backwards = 1; + else if (mddev->delta_disks > 0) + mddev->reshape_backwards = 0; rv = mddev->pers->check_reshape(mddev); - if (rv < 0) + if (rv < 0) { mddev->delta_disks = 0; + mddev->reshape_backwards = 0; + } return rv; } @@ -6039,6 +6282,8 @@ static int update_array_info(struct mddev *mddev, mdu_array_info_t *info) return -EINVAL; mddev->bitmap_info.offset = mddev->bitmap_info.default_offset; + mddev->bitmap_info.space = + mddev->bitmap_info.default_space; mddev->pers->quiesce(mddev, 1); rv = bitmap_create(mddev); if (!rv) @@ -6050,7 +6295,7 @@ static int update_array_info(struct mddev *mddev, mdu_array_info_t *info) /* remove the bitmap */ if (!mddev->bitmap) return -ENOENT; - if (mddev->bitmap->file) + if (mddev->bitmap->storage.file) return -EINVAL; mddev->pers->quiesce(mddev, 1); bitmap_destroy(mddev); @@ -6373,6 +6618,9 @@ static int md_open(struct block_device *bdev, fmode_t mode) struct mddev *mddev = mddev_find(bdev->bd_dev); int err; + if (!mddev) + return -ENODEV; + if (mddev->gendisk != bdev->bd_disk) { /* we are racing with mddev_put which is discarding this * bd_disk. @@ -6584,7 +6832,8 @@ static void status_resync(struct seq_file *seq, struct mddev * mddev) resync = mddev->curr_resync - atomic_read(&mddev->recovery_active); - if (test_bit(MD_RECOVERY_SYNC, &mddev->recovery)) + if (test_bit(MD_RECOVERY_SYNC, &mddev->recovery) || + test_bit(MD_RECOVERY_RESHAPE, &mddev->recovery)) max_sectors = mddev->resync_max_sectors; else max_sectors = mddev->dev_sectors; @@ -7147,7 +7396,7 @@ void md_do_sync(struct mddev *mddev) j = mddev->recovery_cp; } else if (test_bit(MD_RECOVERY_RESHAPE, &mddev->recovery)) - max_sectors = mddev->dev_sectors; + max_sectors = mddev->resync_max_sectors; else { /* recovery follows the physical size of devices */ max_sectors = mddev->dev_sectors; @@ -7598,7 +7847,7 @@ void md_check_recovery(struct mddev *mddev) goto unlock; if (mddev->pers->sync_request) { - if (spares && mddev->bitmap && ! mddev->bitmap->file) { + if (spares) { /* We are adding a device or devices to an array * which has the bitmap stored on all devices. * So make sure all bitmap pages get written @@ -7646,6 +7895,20 @@ void md_wait_for_blocked_rdev(struct md_rdev *rdev, struct mddev *mddev) } EXPORT_SYMBOL(md_wait_for_blocked_rdev); +void md_finish_reshape(struct mddev *mddev) +{ + /* called be personality module when reshape completes. */ + struct md_rdev *rdev; + + rdev_for_each(rdev, mddev) { + if (rdev->data_offset > rdev->new_data_offset) + rdev->sectors += rdev->data_offset - rdev->new_data_offset; + else + rdev->sectors -= rdev->new_data_offset - rdev->data_offset; + rdev->data_offset = rdev->new_data_offset; + } +} +EXPORT_SYMBOL(md_finish_reshape); /* Bad block management. * We can record which blocks on each device are 'bad' and so just @@ -7894,10 +8157,15 @@ static int md_set_badblocks(struct badblocks *bb, sector_t s, int sectors, } int rdev_set_badblocks(struct md_rdev *rdev, sector_t s, int sectors, - int acknowledged) + int is_new) { - int rv = md_set_badblocks(&rdev->badblocks, - s + rdev->data_offset, sectors, acknowledged); + int rv; + if (is_new) + s += rdev->new_data_offset; + else + s += rdev->data_offset; + rv = md_set_badblocks(&rdev->badblocks, + s, sectors, 0); if (rv) { /* Make sure they get written out promptly */ sysfs_notify_dirent_safe(rdev->sysfs_state); @@ -8003,11 +8271,15 @@ out: return rv; } -int rdev_clear_badblocks(struct md_rdev *rdev, sector_t s, int sectors) +int rdev_clear_badblocks(struct md_rdev *rdev, sector_t s, int sectors, + int is_new) { + if (is_new) + s += rdev->new_data_offset; + else + s += rdev->data_offset; return md_clear_badblocks(&rdev->badblocks, - s + rdev->data_offset, - sectors); + s, sectors); } EXPORT_SYMBOL_GPL(rdev_clear_badblocks); diff --git a/drivers/md/md.h b/drivers/md/md.h index 1c2063ccf48e..7b4a3c318cae 100644 --- a/drivers/md/md.h +++ b/drivers/md/md.h @@ -55,6 +55,7 @@ struct md_rdev { int sb_loaded; __u64 sb_events; sector_t data_offset; /* start of data in array */ + sector_t new_data_offset;/* only relevant while reshaping */ sector_t sb_start; /* offset of the super block (in 512byte sectors) */ int sb_size; /* bytes in the superblock */ int preferred_minor; /* autorun support */ @@ -193,8 +194,9 @@ static inline int is_badblock(struct md_rdev *rdev, sector_t s, int sectors, return 0; } extern int rdev_set_badblocks(struct md_rdev *rdev, sector_t s, int sectors, - int acknowledged); -extern int rdev_clear_badblocks(struct md_rdev *rdev, sector_t s, int sectors); + int is_new); +extern int rdev_clear_badblocks(struct md_rdev *rdev, sector_t s, int sectors, + int is_new); extern void md_ack_all_badblocks(struct badblocks *bb); struct mddev { @@ -262,6 +264,7 @@ struct mddev { sector_t reshape_position; int delta_disks, new_level, new_layout; int new_chunk_sectors; + int reshape_backwards; atomic_t plug_cnt; /* If device is expecting * more bios soon. @@ -390,10 +393,13 @@ struct mddev { * For external metadata, offset * from start of device. */ + unsigned long space; /* space available at this offset */ loff_t default_offset; /* this is the offset to use when * hot-adding a bitmap. It should * eventually be settable by sysfs. */ + unsigned long default_space; /* space available at + * default offset */ struct mutex mutex; unsigned long chunksize; unsigned long daemon_sleep; /* how many jiffies between updates? */ @@ -591,6 +597,7 @@ extern void md_write_start(struct mddev *mddev, struct bio *bi); extern void md_write_end(struct mddev *mddev); extern void md_done_sync(struct mddev *mddev, int blocks, int ok); extern void md_error(struct mddev *mddev, struct md_rdev *rdev); +extern void md_finish_reshape(struct mddev *mddev); extern int mddev_congested(struct mddev *mddev, int bits); extern void md_flush_request(struct mddev *mddev, struct bio *bio); @@ -615,6 +622,7 @@ extern int md_run(struct mddev *mddev); extern void md_stop(struct mddev *mddev); extern void md_stop_writes(struct mddev *mddev); extern int md_rdev_init(struct md_rdev *rdev); +extern void md_rdev_clear(struct md_rdev *rdev); extern void mddev_suspend(struct mddev *mddev); extern void mddev_resume(struct mddev *mddev); diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c index 15dd59b84e94..835de7168cd3 100644 --- a/drivers/md/raid1.c +++ b/drivers/md/raid1.c @@ -1859,7 +1859,9 @@ static void fix_read_error(struct r1conf *conf, int read_disk, rdev = conf->mirrors[d].rdev; if (rdev && - test_bit(In_sync, &rdev->flags) && + (test_bit(In_sync, &rdev->flags) || + (!test_bit(Faulty, &rdev->flags) && + rdev->recovery_offset >= sect + s)) && is_badblock(rdev, sect, s, &first_bad, &bad_sectors) == 0 && sync_page_io(rdev, sect, s<<9, @@ -2024,7 +2026,7 @@ static void handle_sync_write_finished(struct r1conf *conf, struct r1bio *r1_bio continue; if (test_bit(BIO_UPTODATE, &bio->bi_flags) && test_bit(R1BIO_MadeGood, &r1_bio->state)) { - rdev_clear_badblocks(rdev, r1_bio->sector, s); + rdev_clear_badblocks(rdev, r1_bio->sector, s, 0); } if (!test_bit(BIO_UPTODATE, &bio->bi_flags) && test_bit(R1BIO_WriteError, &r1_bio->state)) { @@ -2044,7 +2046,7 @@ static void handle_write_finished(struct r1conf *conf, struct r1bio *r1_bio) struct md_rdev *rdev = conf->mirrors[m].rdev; rdev_clear_badblocks(rdev, r1_bio->sector, - r1_bio->sectors); + r1_bio->sectors, 0); rdev_dec_pending(rdev, conf->mddev); } else if (r1_bio->bios[m] != NULL) { /* This drive got a write error. We need to @@ -2598,7 +2600,8 @@ static struct r1conf *setup_conf(struct mddev *mddev) if (!disk->rdev || !test_bit(In_sync, &disk->rdev->flags)) { disk->head_position = 0; - if (disk->rdev) + if (disk->rdev && + (disk->rdev->saved_raid_disk < 0)) conf->fullsync = 1; } else if (conf->last_used < 0) /* @@ -2750,9 +2753,16 @@ static int raid1_resize(struct mddev *mddev, sector_t sectors) * any io in the removed space completes, but it hardly seems * worth it. */ - md_set_array_sectors(mddev, raid1_size(mddev, sectors, 0)); - if (mddev->array_sectors > raid1_size(mddev, sectors, 0)) + sector_t newsize = raid1_size(mddev, sectors, 0); + if (mddev->external_size && + mddev->array_sectors > newsize) return -EINVAL; + if (mddev->bitmap) { + int ret = bitmap_resize(mddev->bitmap, newsize, 0, 0); + if (ret) + return ret; + } + md_set_array_sectors(mddev, newsize); set_capacity(mddev->gendisk, mddev->array_sectors); revalidate_disk(mddev->gendisk); if (sectors > mddev->dev_sectors && diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c index 3f91c2e1dfe7..987db37cb875 100644 --- a/drivers/md/raid10.c +++ b/drivers/md/raid10.c @@ -24,6 +24,7 @@ #include <linux/module.h> #include <linux/seq_file.h> #include <linux/ratelimit.h> +#include <linux/kthread.h> #include "md.h" #include "raid10.h" #include "raid0.h" @@ -68,6 +69,11 @@ static int max_queued_requests = 1024; static void allow_barrier(struct r10conf *conf); static void lower_barrier(struct r10conf *conf); static int enough(struct r10conf *conf, int ignore); +static sector_t reshape_request(struct mddev *mddev, sector_t sector_nr, + int *skipped); +static void reshape_request_write(struct mddev *mddev, struct r10bio *r10_bio); +static void end_reshape_write(struct bio *bio, int error); +static void end_reshape(struct r10conf *conf); static void * r10bio_pool_alloc(gfp_t gfp_flags, void *data) { @@ -112,7 +118,8 @@ static void * r10buf_pool_alloc(gfp_t gfp_flags, void *data) if (!r10_bio) return NULL; - if (test_bit(MD_RECOVERY_SYNC, &conf->mddev->recovery)) + if (test_bit(MD_RECOVERY_SYNC, &conf->mddev->recovery) || + test_bit(MD_RECOVERY_RESHAPE, &conf->mddev->recovery)) nalloc = conf->copies; /* resync */ else nalloc = 2; /* recovery */ @@ -140,9 +147,10 @@ static void * r10buf_pool_alloc(gfp_t gfp_flags, void *data) struct bio *rbio = r10_bio->devs[j].repl_bio; bio = r10_bio->devs[j].bio; for (i = 0; i < RESYNC_PAGES; i++) { - if (j == 1 && !test_bit(MD_RECOVERY_SYNC, - &conf->mddev->recovery)) { - /* we can share bv_page's during recovery */ + if (j > 0 && !test_bit(MD_RECOVERY_SYNC, + &conf->mddev->recovery)) { + /* we can share bv_page's during recovery + * and reshape */ struct bio *rbio = r10_bio->devs[0].bio; page = rbio->bi_io_vec[i].bv_page; get_page(page); @@ -165,10 +173,11 @@ out_free_pages: while (j--) for (i = 0; i < RESYNC_PAGES ; i++) safe_put_page(r10_bio->devs[j].bio->bi_io_vec[i].bv_page); - j = -1; + j = 0; out_free_bio: - while (++j < nalloc) { - bio_put(r10_bio->devs[j].bio); + for ( ; j < nalloc; j++) { + if (r10_bio->devs[j].bio) + bio_put(r10_bio->devs[j].bio); if (r10_bio->devs[j].repl_bio) bio_put(r10_bio->devs[j].repl_bio); } @@ -504,79 +513,96 @@ static void raid10_end_write_request(struct bio *bio, int error) * sector offset to a virtual address */ -static void raid10_find_phys(struct r10conf *conf, struct r10bio *r10bio) +static void __raid10_find_phys(struct geom *geo, struct r10bio *r10bio) { int n,f; sector_t sector; sector_t chunk; sector_t stripe; int dev; - int slot = 0; /* now calculate first sector/dev */ - chunk = r10bio->sector >> conf->chunk_shift; - sector = r10bio->sector & conf->chunk_mask; + chunk = r10bio->sector >> geo->chunk_shift; + sector = r10bio->sector & geo->chunk_mask; - chunk *= conf->near_copies; + chunk *= geo->near_copies; stripe = chunk; - dev = sector_div(stripe, conf->raid_disks); - if (conf->far_offset) - stripe *= conf->far_copies; + dev = sector_div(stripe, geo->raid_disks); + if (geo->far_offset) + stripe *= geo->far_copies; - sector += stripe << conf->chunk_shift; + sector += stripe << geo->chunk_shift; /* and calculate all the others */ - for (n=0; n < conf->near_copies; n++) { + for (n = 0; n < geo->near_copies; n++) { int d = dev; sector_t s = sector; r10bio->devs[slot].addr = sector; r10bio->devs[slot].devnum = d; slot++; - for (f = 1; f < conf->far_copies; f++) { - d += conf->near_copies; - if (d >= conf->raid_disks) - d -= conf->raid_disks; - s += conf->stride; + for (f = 1; f < geo->far_copies; f++) { + d += geo->near_copies; + if (d >= geo->raid_disks) + d -= geo->raid_disks; + s += geo->stride; r10bio->devs[slot].devnum = d; r10bio->devs[slot].addr = s; slot++; } dev++; - if (dev >= conf->raid_disks) { + if (dev >= geo->raid_disks) { dev = 0; - sector += (conf->chunk_mask + 1); + sector += (geo->chunk_mask + 1); } } - BUG_ON(slot != conf->copies); +} + +static void raid10_find_phys(struct r10conf *conf, struct r10bio *r10bio) +{ + struct geom *geo = &conf->geo; + + if (conf->reshape_progress != MaxSector && + ((r10bio->sector >= conf->reshape_progress) != + conf->mddev->reshape_backwards)) { + set_bit(R10BIO_Previous, &r10bio->state); + geo = &conf->prev; + } else + clear_bit(R10BIO_Previous, &r10bio->state); + + __raid10_find_phys(geo, r10bio); } static sector_t raid10_find_virt(struct r10conf *conf, sector_t sector, int dev) { sector_t offset, chunk, vchunk; + /* Never use conf->prev as this is only called during resync + * or recovery, so reshape isn't happening + */ + struct geom *geo = &conf->geo; - offset = sector & conf->chunk_mask; - if (conf->far_offset) { + offset = sector & geo->chunk_mask; + if (geo->far_offset) { int fc; - chunk = sector >> conf->chunk_shift; - fc = sector_div(chunk, conf->far_copies); - dev -= fc * conf->near_copies; + chunk = sector >> geo->chunk_shift; + fc = sector_div(chunk, geo->far_copies); + dev -= fc * geo->near_copies; if (dev < 0) - dev += conf->raid_disks; + dev += geo->raid_disks; } else { - while (sector >= conf->stride) { - sector -= conf->stride; - if (dev < conf->near_copies) - dev += conf->raid_disks - conf->near_copies; + while (sector >= geo->stride) { + sector -= geo->stride; + if (dev < geo->near_copies) + dev += geo->raid_disks - geo->near_copies; else - dev -= conf->near_copies; + dev -= geo->near_copies; } - chunk = sector >> conf->chunk_shift; + chunk = sector >> geo->chunk_shift; } - vchunk = chunk * conf->raid_disks + dev; - sector_div(vchunk, conf->near_copies); - return (vchunk << conf->chunk_shift) + offset; + vchunk = chunk * geo->raid_disks + dev; + sector_div(vchunk, geo->near_copies); + return (vchunk << geo->chunk_shift) + offset; } /** @@ -597,10 +623,17 @@ static int raid10_mergeable_bvec(struct request_queue *q, struct r10conf *conf = mddev->private; sector_t sector = bvm->bi_sector + get_start_sect(bvm->bi_bdev); int max; - unsigned int chunk_sectors = mddev->chunk_sectors; + unsigned int chunk_sectors; unsigned int bio_sectors = bvm->bi_size >> 9; + struct geom *geo = &conf->geo; + + chunk_sectors = (conf->geo.chunk_mask & conf->prev.chunk_mask) + 1; + if (conf->reshape_progress != MaxSector && + ((sector >= conf->reshape_progress) != + conf->mddev->reshape_backwards)) + geo = &conf->prev; - if (conf->near_copies < conf->raid_disks) { + if (geo->near_copies < geo->raid_disks) { max = (chunk_sectors - ((sector & (chunk_sectors - 1)) + bio_sectors)) << 9; if (max < 0) @@ -614,6 +647,12 @@ static int raid10_mergeable_bvec(struct request_queue *q, if (mddev->merge_check_needed) { struct r10bio r10_bio; int s; + if (conf->reshape_progress != MaxSector) { + /* Cannot give any guidance during reshape */ + if (max <= biovec->bv_len && bio_sectors == 0) + return biovec->bv_len; + return 0; + } r10_bio.sector = sector; raid10_find_phys(conf, &r10_bio); rcu_read_lock(); @@ -681,6 +720,7 @@ static struct md_rdev *read_balance(struct r10conf *conf, struct md_rdev *rdev, *best_rdev; int do_balance; int best_slot; + struct geom *geo = &conf->geo; raid10_find_phys(conf, r10_bio); rcu_read_lock(); @@ -761,11 +801,11 @@ retry: * sequential read speed for 'far copies' arrays. So only * keep it for 'near' arrays, and review those later. */ - if (conf->near_copies > 1 && !atomic_read(&rdev->nr_pending)) + if (geo->near_copies > 1 && !atomic_read(&rdev->nr_pending)) break; /* for far > 1 always use the lowest address */ - if (conf->far_copies > 1) + if (geo->far_copies > 1) new_distance = r10_bio->devs[slot].addr; else new_distance = abs(r10_bio->devs[slot].addr - @@ -812,7 +852,10 @@ static int raid10_congested(void *data, int bits) if (mddev_congested(mddev, bits)) return 1; rcu_read_lock(); - for (i = 0; i < conf->raid_disks && ret == 0; i++) { + for (i = 0; + (i < conf->geo.raid_disks || i < conf->prev.raid_disks) + && ret == 0; + i++) { struct md_rdev *rdev = rcu_dereference(conf->mirrors[i].rdev); if (rdev && !test_bit(Faulty, &rdev->flags)) { struct request_queue *q = bdev_get_queue(rdev->bdev); @@ -973,13 +1016,24 @@ static void unfreeze_array(struct r10conf *conf) spin_unlock_irq(&conf->resync_lock); } +static sector_t choose_data_offset(struct r10bio *r10_bio, + struct md_rdev *rdev) +{ + if (!test_bit(MD_RECOVERY_RESHAPE, &rdev->mddev->recovery) || + test_bit(R10BIO_Previous, &r10_bio->state)) + return rdev->data_offset; + else + return rdev->new_data_offset; +} + static void make_request(struct mddev *mddev, struct bio * bio) { struct r10conf *conf = mddev->private; struct r10bio *r10_bio; struct bio *read_bio; int i; - int chunk_sects = conf->chunk_mask + 1; + sector_t chunk_mask = (conf->geo.chunk_mask & conf->prev.chunk_mask); + int chunk_sects = chunk_mask + 1; const int rw = bio_data_dir(bio); const unsigned long do_sync = (bio->bi_rw & REQ_SYNC); const unsigned long do_fua = (bio->bi_rw & REQ_FUA); @@ -988,6 +1042,7 @@ static void make_request(struct mddev *mddev, struct bio * bio) int plugged; int sectors_handled; int max_sectors; + int sectors; if (unlikely(bio->bi_rw & REQ_FLUSH)) { md_flush_request(mddev, bio); @@ -997,9 +1052,10 @@ static void make_request(struct mddev *mddev, struct bio * bio) /* If this request crosses a chunk boundary, we need to * split it. This will only happen for 1 PAGE (or less) requests. */ - if (unlikely( (bio->bi_sector & conf->chunk_mask) + (bio->bi_size >> 9) - > chunk_sects && - conf->near_copies < conf->raid_disks)) { + if (unlikely((bio->bi_sector & chunk_mask) + (bio->bi_size >> 9) + > chunk_sects + && (conf->geo.near_copies < conf->geo.raid_disks + || conf->prev.near_copies < conf->prev.raid_disks))) { struct bio_pair *bp; /* Sanity check -- queue functions should prevent this happening */ if (bio->bi_vcnt != 1 || @@ -1051,10 +1107,41 @@ static void make_request(struct mddev *mddev, struct bio * bio) */ wait_barrier(conf); + sectors = bio->bi_size >> 9; + while (test_bit(MD_RECOVERY_RESHAPE, &mddev->recovery) && + bio->bi_sector < conf->reshape_progress && + bio->bi_sector + sectors > conf->reshape_progress) { + /* IO spans the reshape position. Need to wait for + * reshape to pass + */ + allow_barrier(conf); + wait_event(conf->wait_barrier, + conf->reshape_progress <= bio->bi_sector || + conf->reshape_progress >= bio->bi_sector + sectors); + wait_barrier(conf); + } + if (test_bit(MD_RECOVERY_RESHAPE, &mddev->recovery) && + bio_data_dir(bio) == WRITE && + (mddev->reshape_backwards + ? (bio->bi_sector < conf->reshape_safe && + bio->bi_sector + sectors > conf->reshape_progress) + : (bio->bi_sector + sectors > conf->reshape_safe && + bio->bi_sector < conf->reshape_progress))) { + /* Need to update reshape_position in metadata */ + mddev->reshape_position = conf->reshape_progress; + set_bit(MD_CHANGE_DEVS, &mddev->flags); + set_bit(MD_CHANGE_PENDING, &mddev->flags); + md_wakeup_thread(mddev->thread); + wait_event(mddev->sb_wait, + !test_bit(MD_CHANGE_PENDING, &mddev->flags)); + + conf->reshape_safe = mddev->reshape_position; + } + r10_bio = mempool_alloc(conf->r10bio_pool, GFP_NOIO); r10_bio->master_bio = bio; - r10_bio->sectors = bio->bi_size >> 9; + r10_bio->sectors = sectors; r10_bio->mddev = mddev; r10_bio->sector = bio->bi_sector; @@ -1093,7 +1180,7 @@ read_again: r10_bio->devs[slot].rdev = rdev; read_bio->bi_sector = r10_bio->devs[slot].addr + - rdev->data_offset; + choose_data_offset(r10_bio, rdev); read_bio->bi_bdev = rdev->bdev; read_bio->bi_end_io = raid10_end_read_request; read_bio->bi_rw = READ | do_sync; @@ -1297,7 +1384,8 @@ retry_write: r10_bio->devs[i].bio = mbio; mbio->bi_sector = (r10_bio->devs[i].addr+ - conf->mirrors[d].rdev->data_offset); + choose_data_offset(r10_bio, + conf->mirrors[d].rdev)); mbio->bi_bdev = conf->mirrors[d].rdev->bdev; mbio->bi_end_io = raid10_end_write_request; mbio->bi_rw = WRITE | do_sync | do_fua; @@ -1321,8 +1409,10 @@ retry_write: * so it cannot disappear, so the replacement cannot * become NULL here */ - mbio->bi_sector = (r10_bio->devs[i].addr+ - conf->mirrors[d].replacement->data_offset); + mbio->bi_sector = (r10_bio->devs[i].addr + + choose_data_offset( + r10_bio, + conf->mirrors[d].replacement)); mbio->bi_bdev = conf->mirrors[d].replacement->bdev; mbio->bi_end_io = raid10_end_write_request; mbio->bi_rw = WRITE | do_sync | do_fua; @@ -1368,19 +1458,19 @@ static void status(struct seq_file *seq, struct mddev *mddev) struct r10conf *conf = mddev->private; int i; - if (conf->near_copies < conf->raid_disks) + if (conf->geo.near_copies < conf->geo.raid_disks) seq_printf(seq, " %dK chunks", mddev->chunk_sectors / 2); - if (conf->near_copies > 1) - seq_printf(seq, " %d near-copies", conf->near_copies); - if (conf->far_copies > 1) { - if (conf->far_offset) - seq_printf(seq, " %d offset-copies", conf->far_copies); + if (conf->geo.near_copies > 1) + seq_printf(seq, " %d near-copies", conf->geo.near_copies); + if (conf->geo.far_copies > 1) { + if (conf->geo.far_offset) + seq_printf(seq, " %d offset-copies", conf->geo.far_copies); else - seq_printf(seq, " %d far-copies", conf->far_copies); + seq_printf(seq, " %d far-copies", conf->geo.far_copies); } - seq_printf(seq, " [%d/%d] [", conf->raid_disks, - conf->raid_disks - mddev->degraded); - for (i = 0; i < conf->raid_disks; i++) + seq_printf(seq, " [%d/%d] [", conf->geo.raid_disks, + conf->geo.raid_disks - mddev->degraded); + for (i = 0; i < conf->geo.raid_disks; i++) seq_printf(seq, "%s", conf->mirrors[i].rdev && test_bit(In_sync, &conf->mirrors[i].rdev->flags) ? "U" : "_"); @@ -1392,7 +1482,7 @@ static void status(struct seq_file *seq, struct mddev *mddev) * Don't consider the device numbered 'ignore' * as we might be about to remove it. */ -static int enough(struct r10conf *conf, int ignore) +static int _enough(struct r10conf *conf, struct geom *geo, int ignore) { int first = 0; @@ -1403,7 +1493,7 @@ static int enough(struct r10conf *conf, int ignore) if (conf->mirrors[first].rdev && first != ignore) cnt++; - first = (first+1) % conf->raid_disks; + first = (first+1) % geo->raid_disks; } if (cnt == 0) return 0; @@ -1411,6 +1501,12 @@ static int enough(struct r10conf *conf, int ignore) return 1; } +static int enough(struct r10conf *conf, int ignore) +{ + return _enough(conf, &conf->geo, ignore) && + _enough(conf, &conf->prev, ignore); +} + static void error(struct mddev *mddev, struct md_rdev *rdev) { char b[BDEVNAME_SIZE]; @@ -1445,7 +1541,7 @@ static void error(struct mddev *mddev, struct md_rdev *rdev) "md/raid10:%s: Disk failure on %s, disabling device.\n" "md/raid10:%s: Operation continuing on %d devices.\n", mdname(mddev), bdevname(rdev->bdev, b), - mdname(mddev), conf->raid_disks - mddev->degraded); + mdname(mddev), conf->geo.raid_disks - mddev->degraded); } static void print_conf(struct r10conf *conf) @@ -1458,10 +1554,10 @@ static void print_conf(struct r10conf *conf) printk(KERN_DEBUG "(!conf)\n"); return; } - printk(KERN_DEBUG " --- wd:%d rd:%d\n", conf->raid_disks - conf->mddev->degraded, - conf->raid_disks); + printk(KERN_DEBUG " --- wd:%d rd:%d\n", conf->geo.raid_disks - conf->mddev->degraded, + conf->geo.raid_disks); - for (i = 0; i < conf->raid_disks; i++) { + for (i = 0; i < conf->geo.raid_disks; i++) { char b[BDEVNAME_SIZE]; tmp = conf->mirrors + i; if (tmp->rdev) @@ -1493,7 +1589,7 @@ static int raid10_spare_active(struct mddev *mddev) * Find all non-in_sync disks within the RAID10 configuration * and mark them in_sync */ - for (i = 0; i < conf->raid_disks; i++) { + for (i = 0; i < conf->geo.raid_disks; i++) { tmp = conf->mirrors + i; if (tmp->replacement && tmp->replacement->recovery_offset == MaxSector @@ -1535,7 +1631,7 @@ static int raid10_add_disk(struct mddev *mddev, struct md_rdev *rdev) int err = -EEXIST; int mirror; int first = 0; - int last = conf->raid_disks - 1; + int last = conf->geo.raid_disks - 1; struct request_queue *q = bdev_get_queue(rdev->bdev); if (mddev->recovery_cp < MaxSector) @@ -1543,7 +1639,7 @@ static int raid10_add_disk(struct mddev *mddev, struct md_rdev *rdev) * very different from resync */ return -EBUSY; - if (rdev->saved_raid_disk < 0 && !enough(conf, -1)) + if (rdev->saved_raid_disk < 0 && !_enough(conf, &conf->prev, -1)) return -EINVAL; if (rdev->raid_disk >= 0) @@ -1635,6 +1731,7 @@ static int raid10_remove_disk(struct mddev *mddev, struct md_rdev *rdev) if (!test_bit(Faulty, &rdev->flags) && mddev->recovery_disabled != p->recovery_disabled && (!p->replacement || p->replacement == rdev) && + number < conf->geo.raid_disks && enough(conf, -1)) { err = -EBUSY; goto abort; @@ -1676,7 +1773,11 @@ static void end_sync_read(struct bio *bio, int error) struct r10conf *conf = r10_bio->mddev->private; int d; - d = find_bio_disk(conf, r10_bio, bio, NULL, NULL); + if (bio == r10_bio->master_bio) { + /* this is a reshape read */ + d = r10_bio->read_slot; /* really the read dev */ + } else + d = find_bio_disk(conf, r10_bio, bio, NULL, NULL); if (test_bit(BIO_UPTODATE, &bio->bi_flags)) set_bit(R10BIO_Uptodate, &r10_bio->state); @@ -2218,7 +2319,9 @@ static void fix_read_error(struct r10conf *conf, struct mddev *mddev, struct r10 " (%d sectors at %llu on %s)\n", mdname(mddev), s, (unsigned long long)( - sect + rdev->data_offset), + sect + + choose_data_offset(r10_bio, + rdev)), bdevname(rdev->bdev, b)); printk(KERN_NOTICE "md/raid10:%s: %s: failing " "drive\n", @@ -2256,7 +2359,8 @@ static void fix_read_error(struct r10conf *conf, struct mddev *mddev, struct r10 " (%d sectors at %llu on %s)\n", mdname(mddev), s, (unsigned long long)( - sect + rdev->data_offset), + sect + + choose_data_offset(r10_bio, rdev)), bdevname(rdev->bdev, b)); printk(KERN_NOTICE "md/raid10:%s: %s: failing " "drive\n", @@ -2269,7 +2373,8 @@ static void fix_read_error(struct r10conf *conf, struct mddev *mddev, struct r10 " (%d sectors at %llu on %s)\n", mdname(mddev), s, (unsigned long long)( - sect + rdev->data_offset), + sect + + choose_data_offset(r10_bio, rdev)), bdevname(rdev->bdev, b)); atomic_add(s, &rdev->corrected_errors); } @@ -2343,7 +2448,7 @@ static int narrow_write_error(struct r10bio *r10_bio, int i) wbio = bio_clone_mddev(bio, GFP_NOIO, mddev); md_trim_bio(wbio, sector - bio->bi_sector, sectors); wbio->bi_sector = (r10_bio->devs[i].addr+ - rdev->data_offset+ + choose_data_offset(r10_bio, rdev) + (sector - r10_bio->sector)); wbio->bi_bdev = rdev->bdev; if (submit_bio_wait(WRITE, wbio) == 0) @@ -2420,7 +2525,7 @@ read_more: r10_bio->devs[slot].bio = bio; r10_bio->devs[slot].rdev = rdev; bio->bi_sector = r10_bio->devs[slot].addr - + rdev->data_offset; + + choose_data_offset(r10_bio, rdev); bio->bi_bdev = rdev->bdev; bio->bi_rw = READ | do_sync; bio->bi_private = r10_bio; @@ -2480,7 +2585,7 @@ static void handle_write_completed(struct r10conf *conf, struct r10bio *r10_bio) rdev_clear_badblocks( rdev, r10_bio->devs[m].addr, - r10_bio->sectors); + r10_bio->sectors, 0); } else { if (!rdev_set_badblocks( rdev, @@ -2496,7 +2601,7 @@ static void handle_write_completed(struct r10conf *conf, struct r10bio *r10_bio) rdev_clear_badblocks( rdev, r10_bio->devs[m].addr, - r10_bio->sectors); + r10_bio->sectors, 0); } else { if (!rdev_set_badblocks( rdev, @@ -2515,7 +2620,7 @@ static void handle_write_completed(struct r10conf *conf, struct r10bio *r10_bio) rdev_clear_badblocks( rdev, r10_bio->devs[m].addr, - r10_bio->sectors); + r10_bio->sectors, 0); rdev_dec_pending(rdev, conf->mddev); } else if (bio != NULL && !test_bit(BIO_UPTODATE, &bio->bi_flags)) { @@ -2532,7 +2637,7 @@ static void handle_write_completed(struct r10conf *conf, struct r10bio *r10_bio) rdev_clear_badblocks( rdev, r10_bio->devs[m].addr, - r10_bio->sectors); + r10_bio->sectors, 0); rdev_dec_pending(rdev, conf->mddev); } } @@ -2573,6 +2678,8 @@ static void raid10d(struct mddev *mddev) if (test_bit(R10BIO_MadeGood, &r10_bio->state) || test_bit(R10BIO_WriteError, &r10_bio->state)) handle_write_completed(conf, r10_bio); + else if (test_bit(R10BIO_IsReshape, &r10_bio->state)) + reshape_request_write(mddev, r10_bio); else if (test_bit(R10BIO_IsSync, &r10_bio->state)) sync_request_write(mddev, r10_bio); else if (test_bit(R10BIO_IsRecover, &r10_bio->state)) @@ -2603,7 +2710,7 @@ static int init_resync(struct r10conf *conf) buffs = RESYNC_WINDOW / RESYNC_BLOCK_SIZE; BUG_ON(conf->r10buf_pool); conf->have_replacement = 0; - for (i = 0; i < conf->raid_disks; i++) + for (i = 0; i < conf->geo.raid_disks; i++) if (conf->mirrors[i].replacement) conf->have_replacement = 1; conf->r10buf_pool = mempool_create(buffs, r10buf_pool_alloc, r10buf_pool_free, conf); @@ -2657,6 +2764,7 @@ static sector_t sync_request(struct mddev *mddev, sector_t sector_nr, sector_t sync_blocks; sector_t sectors_skipped = 0; int chunks_skipped = 0; + sector_t chunk_mask = conf->geo.chunk_mask; if (!conf->r10buf_pool) if (init_resync(conf)) @@ -2664,7 +2772,8 @@ static sector_t sync_request(struct mddev *mddev, sector_t sector_nr, skipped: max_sector = mddev->dev_sectors; - if (test_bit(MD_RECOVERY_SYNC, &mddev->recovery)) + if (test_bit(MD_RECOVERY_SYNC, &mddev->recovery) || + test_bit(MD_RECOVERY_RESHAPE, &mddev->recovery)) max_sector = mddev->resync_max_sectors; if (sector_nr >= max_sector) { /* If we aborted, we need to abort the @@ -2676,11 +2785,16 @@ static sector_t sync_request(struct mddev *mddev, sector_t sector_nr, * we need to convert that to several * virtual addresses. */ + if (test_bit(MD_RECOVERY_RESHAPE, &mddev->recovery)) { + end_reshape(conf); + return 0; + } + if (mddev->curr_resync < max_sector) { /* aborted */ if (test_bit(MD_RECOVERY_SYNC, &mddev->recovery)) bitmap_end_sync(mddev->bitmap, mddev->curr_resync, &sync_blocks, 1); - else for (i=0; i<conf->raid_disks; i++) { + else for (i = 0; i < conf->geo.raid_disks; i++) { sector_t sect = raid10_find_virt(conf, mddev->curr_resync, i); bitmap_end_sync(mddev->bitmap, sect, @@ -2694,7 +2808,7 @@ static sector_t sync_request(struct mddev *mddev, sector_t sector_nr, /* Completed a full sync so the replacements * are now fully recovered. */ - for (i = 0; i < conf->raid_disks; i++) + for (i = 0; i < conf->geo.raid_disks; i++) if (conf->mirrors[i].replacement) conf->mirrors[i].replacement ->recovery_offset @@ -2707,7 +2821,11 @@ static sector_t sync_request(struct mddev *mddev, sector_t sector_nr, *skipped = 1; return sectors_skipped; } - if (chunks_skipped >= conf->raid_disks) { + + if (test_bit(MD_RECOVERY_RESHAPE, &mddev->recovery)) + return reshape_request(mddev, sector_nr, skipped); + + if (chunks_skipped >= conf->geo.raid_disks) { /* if there has been nothing to do on any drive, * then there is nothing to do at all.. */ @@ -2721,9 +2839,9 @@ static sector_t sync_request(struct mddev *mddev, sector_t sector_nr, /* make sure whole request will fit in a chunk - if chunks * are meaningful */ - if (conf->near_copies < conf->raid_disks && - max_sector > (sector_nr | conf->chunk_mask)) - max_sector = (sector_nr | conf->chunk_mask) + 1; + if (conf->geo.near_copies < conf->geo.raid_disks && + max_sector > (sector_nr | chunk_mask)) + max_sector = (sector_nr | chunk_mask) + 1; /* * If there is non-resync activity waiting for us then * put in a delay to throttle resync. @@ -2752,7 +2870,7 @@ static sector_t sync_request(struct mddev *mddev, sector_t sector_nr, int j; r10_bio = NULL; - for (i=0 ; i<conf->raid_disks; i++) { + for (i = 0 ; i < conf->geo.raid_disks; i++) { int still_degraded; struct r10bio *rb2; sector_t sect; @@ -2806,7 +2924,7 @@ static sector_t sync_request(struct mddev *mddev, sector_t sector_nr, /* Need to check if the array will still be * degraded */ - for (j=0; j<conf->raid_disks; j++) + for (j = 0; j < conf->geo.raid_disks; j++) if (conf->mirrors[j].rdev == NULL || test_bit(Faulty, &conf->mirrors[j].rdev->flags)) { still_degraded = 1; @@ -2984,9 +3102,9 @@ static sector_t sync_request(struct mddev *mddev, sector_t sector_nr, r10_bio->sector = sector_nr; set_bit(R10BIO_IsSync, &r10_bio->state); raid10_find_phys(conf, r10_bio); - r10_bio->sectors = (sector_nr | conf->chunk_mask) - sector_nr +1; + r10_bio->sectors = (sector_nr | chunk_mask) - sector_nr + 1; - for (i=0; i<conf->copies; i++) { + for (i = 0; i < conf->copies; i++) { int d = r10_bio->devs[i].devnum; sector_t first_bad, sector; int bad_sectors; @@ -3152,16 +3270,17 @@ raid10_size(struct mddev *mddev, sector_t sectors, int raid_disks) struct r10conf *conf = mddev->private; if (!raid_disks) - raid_disks = conf->raid_disks; + raid_disks = min(conf->geo.raid_disks, + conf->prev.raid_disks); if (!sectors) sectors = conf->dev_sectors; - size = sectors >> conf->chunk_shift; - sector_div(size, conf->far_copies); + size = sectors >> conf->geo.chunk_shift; + sector_div(size, conf->geo.far_copies); size = size * raid_disks; - sector_div(size, conf->near_copies); + sector_div(size, conf->geo.near_copies); - return size << conf->chunk_shift; + return size << conf->geo.chunk_shift; } static void calc_sectors(struct r10conf *conf, sector_t size) @@ -3171,10 +3290,10 @@ static void calc_sectors(struct r10conf *conf, sector_t size) * conf->stride */ - size = size >> conf->chunk_shift; - sector_div(size, conf->far_copies); - size = size * conf->raid_disks; - sector_div(size, conf->near_copies); + size = size >> conf->geo.chunk_shift; + sector_div(size, conf->geo.far_copies); + size = size * conf->geo.raid_disks; + sector_div(size, conf->geo.near_copies); /* 'size' is now the number of chunks in the array */ /* calculate "used chunks per device" */ size = size * conf->copies; @@ -3182,38 +3301,76 @@ static void calc_sectors(struct r10conf *conf, sector_t size) /* We need to round up when dividing by raid_disks to * get the stride size. */ - size = DIV_ROUND_UP_SECTOR_T(size, conf->raid_disks); + size = DIV_ROUND_UP_SECTOR_T(size, conf->geo.raid_disks); - conf->dev_sectors = size << conf->chunk_shift; + conf->dev_sectors = size << conf->geo.chunk_shift; - if (conf->far_offset) - conf->stride = 1 << conf->chunk_shift; + if (conf->geo.far_offset) + conf->geo.stride = 1 << conf->geo.chunk_shift; else { - sector_div(size, conf->far_copies); - conf->stride = size << conf->chunk_shift; + sector_div(size, conf->geo.far_copies); + conf->geo.stride = size << conf->geo.chunk_shift; } } +enum geo_type {geo_new, geo_old, geo_start}; +static int setup_geo(struct geom *geo, struct mddev *mddev, enum geo_type new) +{ + int nc, fc, fo; + int layout, chunk, disks; + switch (new) { + case geo_old: + layout = mddev->layout; + chunk = mddev->chunk_sectors; + disks = mddev->raid_disks - mddev->delta_disks; + break; + case geo_new: + layout = mddev->new_layout; + chunk = mddev->new_chunk_sectors; + disks = mddev->raid_disks; + break; + default: /* avoid 'may be unused' warnings */ + case geo_start: /* new when starting reshape - raid_disks not + * updated yet. */ + layout = mddev->new_layout; + chunk = mddev->new_chunk_sectors; + disks = mddev->raid_disks + mddev->delta_disks; + break; + } + if (layout >> 17) + return -1; + if (chunk < (PAGE_SIZE >> 9) || + !is_power_of_2(chunk)) + return -2; + nc = layout & 255; + fc = (layout >> 8) & 255; + fo = layout & (1<<16); + geo->raid_disks = disks; + geo->near_copies = nc; + geo->far_copies = fc; + geo->far_offset = fo; + geo->chunk_mask = chunk - 1; + geo->chunk_shift = ffz(~chunk); + return nc*fc; +} + static struct r10conf *setup_conf(struct mddev *mddev) { struct r10conf *conf = NULL; - int nc, fc, fo; int err = -EINVAL; + struct geom geo; + int copies; + + copies = setup_geo(&geo, mddev, geo_new); - if (mddev->new_chunk_sectors < (PAGE_SIZE >> 9) || - !is_power_of_2(mddev->new_chunk_sectors)) { + if (copies == -2) { printk(KERN_ERR "md/raid10:%s: chunk size must be " "at least PAGE_SIZE(%ld) and be a power of 2.\n", mdname(mddev), PAGE_SIZE); goto out; } - nc = mddev->new_layout & 255; - fc = (mddev->new_layout >> 8) & 255; - fo = mddev->new_layout & (1<<16); - - if ((nc*fc) <2 || (nc*fc) > mddev->raid_disks || - (mddev->new_layout >> 17)) { + if (copies < 2 || copies > mddev->raid_disks) { printk(KERN_ERR "md/raid10:%s: unsupported raid10 layout: 0x%8x\n", mdname(mddev), mddev->new_layout); goto out; @@ -3224,7 +3381,9 @@ static struct r10conf *setup_conf(struct mddev *mddev) if (!conf) goto out; - conf->mirrors = kzalloc(sizeof(struct mirror_info)*mddev->raid_disks, + /* FIXME calc properly */ + conf->mirrors = kzalloc(sizeof(struct mirror_info)*(mddev->raid_disks + + max(0,mddev->delta_disks)), GFP_KERNEL); if (!conf->mirrors) goto out; @@ -3233,22 +3392,29 @@ static struct r10conf *setup_conf(struct mddev *mddev) if (!conf->tmppage) goto out; - - conf->raid_disks = mddev->raid_disks; - conf->near_copies = nc; - conf->far_copies = fc; - conf->copies = nc*fc; - conf->far_offset = fo; - conf->chunk_mask = mddev->new_chunk_sectors - 1; - conf->chunk_shift = ffz(~mddev->new_chunk_sectors); - + conf->geo = geo; + conf->copies = copies; conf->r10bio_pool = mempool_create(NR_RAID10_BIOS, r10bio_pool_alloc, r10bio_pool_free, conf); if (!conf->r10bio_pool) goto out; calc_sectors(conf, mddev->dev_sectors); - + if (mddev->reshape_position == MaxSector) { + conf->prev = conf->geo; + conf->reshape_progress = MaxSector; + } else { + if (setup_geo(&conf->prev, mddev, geo_old) != conf->copies) { + err = -EINVAL; + goto out; + } + conf->reshape_progress = mddev->reshape_position; + if (conf->prev.far_offset) + conf->prev.stride = 1 << conf->prev.chunk_shift; + else + /* far_copies must be 1 */ + conf->prev.stride = conf->dev_sectors; + } spin_lock_init(&conf->device_lock); INIT_LIST_HEAD(&conf->retry_list); @@ -3263,8 +3429,9 @@ static struct r10conf *setup_conf(struct mddev *mddev) return conf; out: - printk(KERN_ERR "md/raid10:%s: couldn't allocate memory.\n", - mdname(mddev)); + if (err == -ENOMEM) + printk(KERN_ERR "md/raid10:%s: couldn't allocate memory.\n", + mdname(mddev)); if (conf) { if (conf->r10bio_pool) mempool_destroy(conf->r10bio_pool); @@ -3282,12 +3449,8 @@ static int run(struct mddev *mddev) struct mirror_info *disk; struct md_rdev *rdev; sector_t size; - - /* - * copy the already verified devices into our private RAID10 - * bookkeeping area. [whatever we allocate in run(), - * should be freed in stop()] - */ + sector_t min_offset_diff = 0; + int first = 1; if (mddev->private == NULL) { conf = setup_conf(mddev); @@ -3304,17 +3467,20 @@ static int run(struct mddev *mddev) chunk_size = mddev->chunk_sectors << 9; blk_queue_io_min(mddev->queue, chunk_size); - if (conf->raid_disks % conf->near_copies) - blk_queue_io_opt(mddev->queue, chunk_size * conf->raid_disks); + if (conf->geo.raid_disks % conf->geo.near_copies) + blk_queue_io_opt(mddev->queue, chunk_size * conf->geo.raid_disks); else blk_queue_io_opt(mddev->queue, chunk_size * - (conf->raid_disks / conf->near_copies)); + (conf->geo.raid_disks / conf->geo.near_copies)); rdev_for_each(rdev, mddev) { + long long diff; disk_idx = rdev->raid_disk; - if (disk_idx >= conf->raid_disks - || disk_idx < 0) + if (disk_idx < 0) + continue; + if (disk_idx >= conf->geo.raid_disks && + disk_idx >= conf->prev.raid_disks) continue; disk = conf->mirrors + disk_idx; @@ -3327,12 +3493,20 @@ static int run(struct mddev *mddev) goto out_free_conf; disk->rdev = rdev; } + diff = (rdev->new_data_offset - rdev->data_offset); + if (!mddev->reshape_backwards) + diff = -diff; + if (diff < 0) + diff = 0; + if (first || diff < min_offset_diff) + min_offset_diff = diff; disk_stack_limits(mddev->gendisk, rdev->bdev, rdev->data_offset << 9); disk->head_position = 0; } + /* need to check that every block has at least one working mirror */ if (!enough(conf, -1)) { printk(KERN_ERR "md/raid10:%s: not enough operational mirrors.\n", @@ -3340,8 +3514,21 @@ static int run(struct mddev *mddev) goto out_free_conf; } + if (conf->reshape_progress != MaxSector) { + /* must ensure that shape change is supported */ + if (conf->geo.far_copies != 1 && + conf->geo.far_offset == 0) + goto out_free_conf; + if (conf->prev.far_copies != 1 && + conf->geo.far_offset == 0) + goto out_free_conf; + } + mddev->degraded = 0; - for (i = 0; i < conf->raid_disks; i++) { + for (i = 0; + i < conf->geo.raid_disks + || i < conf->prev.raid_disks; + i++) { disk = conf->mirrors + i; @@ -3368,8 +3555,8 @@ static int run(struct mddev *mddev) mdname(mddev)); printk(KERN_INFO "md/raid10:%s: active with %d out of %d devices\n", - mdname(mddev), conf->raid_disks - mddev->degraded, - conf->raid_disks); + mdname(mddev), conf->geo.raid_disks - mddev->degraded, + conf->geo.raid_disks); /* * Ok, everything is just fine now */ @@ -3386,11 +3573,11 @@ static int run(struct mddev *mddev) * maybe... */ { - int stripe = conf->raid_disks * + int stripe = conf->geo.raid_disks * ((mddev->chunk_sectors << 9) / PAGE_SIZE); - stripe /= conf->near_copies; - if (mddev->queue->backing_dev_info.ra_pages < 2* stripe) - mddev->queue->backing_dev_info.ra_pages = 2* stripe; + stripe /= conf->geo.near_copies; + if (mddev->queue->backing_dev_info.ra_pages < 2 * stripe) + mddev->queue->backing_dev_info.ra_pages = 2 * stripe; } blk_queue_merge_bvec(mddev->queue, raid10_mergeable_bvec); @@ -3398,6 +3585,30 @@ static int run(struct mddev *mddev) if (md_integrity_register(mddev)) goto out_free_conf; + if (conf->reshape_progress != MaxSector) { + unsigned long before_length, after_length; + + before_length = ((1 << conf->prev.chunk_shift) * + conf->prev.far_copies); + after_length = ((1 << conf->geo.chunk_shift) * + conf->geo.far_copies); + + if (max(before_length, after_length) > min_offset_diff) { + /* This cannot work */ + printk("md/raid10: offset difference not enough to continue reshape\n"); + goto out_free_conf; + } + conf->offset_diff = min_offset_diff; + + conf->reshape_safe = conf->reshape_progress; + clear_bit(MD_RECOVERY_SYNC, &mddev->recovery); + clear_bit(MD_RECOVERY_CHECK, &mddev->recovery); + set_bit(MD_RECOVERY_RESHAPE, &mddev->recovery); + set_bit(MD_RECOVERY_RUNNING, &mddev->recovery); + mddev->sync_thread = md_register_thread(md_do_sync, mddev, + "reshape"); + } + return 0; out_free_conf: @@ -3460,14 +3671,23 @@ static int raid10_resize(struct mddev *mddev, sector_t sectors) struct r10conf *conf = mddev->private; sector_t oldsize, size; - if (conf->far_copies > 1 && !conf->far_offset) + if (mddev->reshape_position != MaxSector) + return -EBUSY; + + if (conf->geo.far_copies > 1 && !conf->geo.far_offset) return -EINVAL; oldsize = raid10_size(mddev, 0, 0); size = raid10_size(mddev, sectors, 0); - md_set_array_sectors(mddev, size); - if (mddev->array_sectors > size) + if (mddev->external_size && + mddev->array_sectors > size) return -EINVAL; + if (mddev->bitmap) { + int ret = bitmap_resize(mddev->bitmap, size, 0, 0); + if (ret) + return ret; + } + md_set_array_sectors(mddev, size); set_capacity(mddev->gendisk, mddev->array_sectors); revalidate_disk(mddev->gendisk); if (sectors > mddev->dev_sectors && @@ -3534,6 +3754,758 @@ static void *raid10_takeover(struct mddev *mddev) return ERR_PTR(-EINVAL); } +static int raid10_check_reshape(struct mddev *mddev) +{ + /* Called when there is a request to change + * - layout (to ->new_layout) + * - chunk size (to ->new_chunk_sectors) + * - raid_disks (by delta_disks) + * or when trying to restart a reshape that was ongoing. + * + * We need to validate the request and possibly allocate + * space if that might be an issue later. + * + * Currently we reject any reshape of a 'far' mode array, + * allow chunk size to change if new is generally acceptable, + * allow raid_disks to increase, and allow + * a switch between 'near' mode and 'offset' mode. + */ + struct r10conf *conf = mddev->private; + struct geom geo; + + if (conf->geo.far_copies != 1 && !conf->geo.far_offset) + return -EINVAL; + + if (setup_geo(&geo, mddev, geo_start) != conf->copies) + /* mustn't change number of copies */ + return -EINVAL; + if (geo.far_copies > 1 && !geo.far_offset) + /* Cannot switch to 'far' mode */ + return -EINVAL; + + if (mddev->array_sectors & geo.chunk_mask) + /* not factor of array size */ + return -EINVAL; + + if (!enough(conf, -1)) + return -EINVAL; + + kfree(conf->mirrors_new); + conf->mirrors_new = NULL; + if (mddev->delta_disks > 0) { + /* allocate new 'mirrors' list */ + conf->mirrors_new = kzalloc( + sizeof(struct mirror_info) + *(mddev->raid_disks + + mddev->delta_disks), + GFP_KERNEL); + if (!conf->mirrors_new) + return -ENOMEM; + } + return 0; +} + +/* + * Need to check if array has failed when deciding whether to: + * - start an array + * - remove non-faulty devices + * - add a spare + * - allow a reshape + * This determination is simple when no reshape is happening. + * However if there is a reshape, we need to carefully check + * both the before and after sections. + * This is because some failed devices may only affect one + * of the two sections, and some non-in_sync devices may + * be insync in the section most affected by failed devices. + */ +static int calc_degraded(struct r10conf *conf) +{ + int degraded, degraded2; + int i; + + rcu_read_lock(); + degraded = 0; + /* 'prev' section first */ + for (i = 0; i < conf->prev.raid_disks; i++) { + struct md_rdev *rdev = rcu_dereference(conf->mirrors[i].rdev); + if (!rdev || test_bit(Faulty, &rdev->flags)) + degraded++; + else if (!test_bit(In_sync, &rdev->flags)) + /* When we can reduce the number of devices in + * an array, this might not contribute to + * 'degraded'. It does now. + */ + degraded++; + } + rcu_read_unlock(); + if (conf->geo.raid_disks == conf->prev.raid_disks) + return degraded; + rcu_read_lock(); + degraded2 = 0; + for (i = 0; i < conf->geo.raid_disks; i++) { + struct md_rdev *rdev = rcu_dereference(conf->mirrors[i].rdev); + if (!rdev || test_bit(Faulty, &rdev->flags)) + degraded2++; + else if (!test_bit(In_sync, &rdev->flags)) { + /* If reshape is increasing the number of devices, + * this section has already been recovered, so + * it doesn't contribute to degraded. + * else it does. + */ + if (conf->geo.raid_disks <= conf->prev.raid_disks) + degraded2++; + } + } + rcu_read_unlock(); + if (degraded2 > degraded) + return degraded2; + return degraded; +} + +static int raid10_start_reshape(struct mddev *mddev) +{ + /* A 'reshape' has been requested. This commits + * the various 'new' fields and sets MD_RECOVER_RESHAPE + * This also checks if there are enough spares and adds them + * to the array. + * We currently require enough spares to make the final + * array non-degraded. We also require that the difference + * between old and new data_offset - on each device - is + * enough that we never risk over-writing. + */ + + unsigned long before_length, after_length; + sector_t min_offset_diff = 0; + int first = 1; + struct geom new; + struct r10conf *conf = mddev->private; + struct md_rdev *rdev; + int spares = 0; + int ret; + + if (test_bit(MD_RECOVERY_RUNNING, &mddev->recovery)) + return -EBUSY; + + if (setup_geo(&new, mddev, geo_start) != conf->copies) + return -EINVAL; + + before_length = ((1 << conf->prev.chunk_shift) * + conf->prev.far_copies); + after_length = ((1 << conf->geo.chunk_shift) * + conf->geo.far_copies); + + rdev_for_each(rdev, mddev) { + if (!test_bit(In_sync, &rdev->flags) + && !test_bit(Faulty, &rdev->flags)) + spares++; + if (rdev->raid_disk >= 0) { + long long diff = (rdev->new_data_offset + - rdev->data_offset); + if (!mddev->reshape_backwards) + diff = -diff; + if (diff < 0) + diff = 0; + if (first || diff < min_offset_diff) + min_offset_diff = diff; + } + } + + if (max(before_length, after_length) > min_offset_diff) + return -EINVAL; + + if (spares < mddev->delta_disks) + return -EINVAL; + + conf->offset_diff = min_offset_diff; + spin_lock_irq(&conf->device_lock); + if (conf->mirrors_new) { + memcpy(conf->mirrors_new, conf->mirrors, + sizeof(struct mirror_info)*conf->prev.raid_disks); + smp_mb(); + kfree(conf->mirrors_old); /* FIXME and elsewhere */ + conf->mirrors_old = conf->mirrors; + conf->mirrors = conf->mirrors_new; + conf->mirrors_new = NULL; + } + setup_geo(&conf->geo, mddev, geo_start); + smp_mb(); + if (mddev->reshape_backwards) { + sector_t size = raid10_size(mddev, 0, 0); + if (size < mddev->array_sectors) { + spin_unlock_irq(&conf->device_lock); + printk(KERN_ERR "md/raid10:%s: array size must be reduce before number of disks\n", + mdname(mddev)); + return -EINVAL; + } + mddev->resync_max_sectors = size; + conf->reshape_progress = size; + } else + conf->reshape_progress = 0; + spin_unlock_irq(&conf->device_lock); + + if (mddev->delta_disks && mddev->bitmap) { + ret = bitmap_resize(mddev->bitmap, + raid10_size(mddev, 0, + conf->geo.raid_disks), + 0, 0); + if (ret) + goto abort; + } + if (mddev->delta_disks > 0) { + rdev_for_each(rdev, mddev) + if (rdev->raid_disk < 0 && + !test_bit(Faulty, &rdev->flags)) { + if (raid10_add_disk(mddev, rdev) == 0) { + if (rdev->raid_disk >= + conf->prev.raid_disks) + set_bit(In_sync, &rdev->flags); + else + rdev->recovery_offset = 0; + + if (sysfs_link_rdev(mddev, rdev)) + /* Failure here is OK */; + } + } else if (rdev->raid_disk >= conf->prev.raid_disks + && !test_bit(Faulty, &rdev->flags)) { + /* This is a spare that was manually added */ + set_bit(In_sync, &rdev->flags); + } + } + /* When a reshape changes the number of devices, + * ->degraded is measured against the larger of the + * pre and post numbers. + */ + spin_lock_irq(&conf->device_lock); + mddev->degraded = calc_degraded(conf); + spin_unlock_irq(&conf->device_lock); + mddev->raid_disks = conf->geo.raid_disks; + mddev->reshape_position = conf->reshape_progress; + set_bit(MD_CHANGE_DEVS, &mddev->flags); + + clear_bit(MD_RECOVERY_SYNC, &mddev->recovery); + clear_bit(MD_RECOVERY_CHECK, &mddev->recovery); + set_bit(MD_RECOVERY_RESHAPE, &mddev->recovery); + set_bit(MD_RECOVERY_RUNNING, &mddev->recovery); + + mddev->sync_thread = md_register_thread(md_do_sync, mddev, + "reshape"); + if (!mddev->sync_thread) { + ret = -EAGAIN; + goto abort; + } + conf->reshape_checkpoint = jiffies; + md_wakeup_thread(mddev->sync_thread); + md_new_event(mddev); + return 0; + +abort: + mddev->recovery = 0; + spin_lock_irq(&conf->device_lock); + conf->geo = conf->prev; + mddev->raid_disks = conf->geo.raid_disks; + rdev_for_each(rdev, mddev) + rdev->new_data_offset = rdev->data_offset; + smp_wmb(); + conf->reshape_progress = MaxSector; + mddev->reshape_position = MaxSector; + spin_unlock_irq(&conf->device_lock); + return ret; +} + +/* Calculate the last device-address that could contain + * any block from the chunk that includes the array-address 's' + * and report the next address. + * i.e. the address returned will be chunk-aligned and after + * any data that is in the chunk containing 's'. + */ +static sector_t last_dev_address(sector_t s, struct geom *geo) +{ + s = (s | geo->chunk_mask) + 1; + s >>= geo->chunk_shift; + s *= geo->near_copies; + s = DIV_ROUND_UP_SECTOR_T(s, geo->raid_disks); + s *= geo->far_copies; + s <<= geo->chunk_shift; + return s; +} + +/* Calculate the first device-address that could contain + * any block from the chunk that includes the array-address 's'. + * This too will be the start of a chunk + */ +static sector_t first_dev_address(sector_t s, struct geom *geo) +{ + s >>= geo->chunk_shift; + s *= geo->near_copies; + sector_div(s, geo->raid_disks); + s *= geo->far_copies; + s <<= geo->chunk_shift; + return s; +} + +static sector_t reshape_request(struct mddev *mddev, sector_t sector_nr, + int *skipped) +{ + /* We simply copy at most one chunk (smallest of old and new) + * at a time, possibly less if that exceeds RESYNC_PAGES, + * or we hit a bad block or something. + * This might mean we pause for normal IO in the middle of + * a chunk, but that is not a problem was mddev->reshape_position + * can record any location. + * + * If we will want to write to a location that isn't + * yet recorded as 'safe' (i.e. in metadata on disk) then + * we need to flush all reshape requests and update the metadata. + * + * When reshaping forwards (e.g. to more devices), we interpret + * 'safe' as the earliest block which might not have been copied + * down yet. We divide this by previous stripe size and multiply + * by previous stripe length to get lowest device offset that we + * cannot write to yet. + * We interpret 'sector_nr' as an address that we want to write to. + * From this we use last_device_address() to find where we might + * write to, and first_device_address on the 'safe' position. + * If this 'next' write position is after the 'safe' position, + * we must update the metadata to increase the 'safe' position. + * + * When reshaping backwards, we round in the opposite direction + * and perform the reverse test: next write position must not be + * less than current safe position. + * + * In all this the minimum difference in data offsets + * (conf->offset_diff - always positive) allows a bit of slack, + * so next can be after 'safe', but not by more than offset_disk + * + * We need to prepare all the bios here before we start any IO + * to ensure the size we choose is acceptable to all devices. + * The means one for each copy for write-out and an extra one for + * read-in. + * We store the read-in bio in ->master_bio and the others in + * ->devs[x].bio and ->devs[x].repl_bio. + */ + struct r10conf *conf = mddev->private; + struct r10bio *r10_bio; + sector_t next, safe, last; + int max_sectors; + int nr_sectors; + int s; + struct md_rdev *rdev; + int need_flush = 0; + struct bio *blist; + struct bio *bio, *read_bio; + int sectors_done = 0; + + if (sector_nr == 0) { + /* If restarting in the middle, skip the initial sectors */ + if (mddev->reshape_backwards && + conf->reshape_progress < raid10_size(mddev, 0, 0)) { + sector_nr = (raid10_size(mddev, 0, 0) + - conf->reshape_progress); + } else if (!mddev->reshape_backwards && + conf->reshape_progress > 0) + sector_nr = conf->reshape_progress; + if (sector_nr) { + mddev->curr_resync_completed = sector_nr; + sysfs_notify(&mddev->kobj, NULL, "sync_completed"); + *skipped = 1; + return sector_nr; + } + } + + /* We don't use sector_nr to track where we are up to + * as that doesn't work well for ->reshape_backwards. + * So just use ->reshape_progress. + */ + if (mddev->reshape_backwards) { + /* 'next' is the earliest device address that we might + * write to for this chunk in the new layout + */ + next = first_dev_address(conf->reshape_progress - 1, + &conf->geo); + + /* 'safe' is the last device address that we might read from + * in the old layout after a restart + */ + safe = last_dev_address(conf->reshape_safe - 1, + &conf->prev); + + if (next + conf->offset_diff < safe) + need_flush = 1; + + last = conf->reshape_progress - 1; + sector_nr = last & ~(sector_t)(conf->geo.chunk_mask + & conf->prev.chunk_mask); + if (sector_nr + RESYNC_BLOCK_SIZE/512 < last) + sector_nr = last + 1 - RESYNC_BLOCK_SIZE/512; + } else { + /* 'next' is after the last device address that we + * might write to for this chunk in the new layout + */ + next = last_dev_address(conf->reshape_progress, &conf->geo); + + /* 'safe' is the earliest device address that we might + * read from in the old layout after a restart + */ + safe = first_dev_address(conf->reshape_safe, &conf->prev); + + /* Need to update metadata if 'next' might be beyond 'safe' + * as that would possibly corrupt data + */ + if (next > safe + conf->offset_diff) + need_flush = 1; + + sector_nr = conf->reshape_progress; + last = sector_nr | (conf->geo.chunk_mask + & conf->prev.chunk_mask); + + if (sector_nr + RESYNC_BLOCK_SIZE/512 <= last) + last = sector_nr + RESYNC_BLOCK_SIZE/512 - 1; + } + + if (need_flush || + time_after(jiffies, conf->reshape_checkpoint + 10*HZ)) { + /* Need to update reshape_position in metadata */ + wait_barrier(conf); + mddev->reshape_position = conf->reshape_progress; + if (mddev->reshape_backwards) + mddev->curr_resync_completed = raid10_size(mddev, 0, 0) + - conf->reshape_progress; + else + mddev->curr_resync_completed = conf->reshape_progress; + conf->reshape_checkpoint = jiffies; + set_bit(MD_CHANGE_DEVS, &mddev->flags); + md_wakeup_thread(mddev->thread); + wait_event(mddev->sb_wait, mddev->flags == 0 || + kthread_should_stop()); + conf->reshape_safe = mddev->reshape_position; + allow_barrier(conf); + } + +read_more: + /* Now schedule reads for blocks from sector_nr to last */ + r10_bio = mempool_alloc(conf->r10buf_pool, GFP_NOIO); + raise_barrier(conf, sectors_done != 0); + atomic_set(&r10_bio->remaining, 0); + r10_bio->mddev = mddev; + r10_bio->sector = sector_nr; + set_bit(R10BIO_IsReshape, &r10_bio->state); + r10_bio->sectors = last - sector_nr + 1; + rdev = read_balance(conf, r10_bio, &max_sectors); + BUG_ON(!test_bit(R10BIO_Previous, &r10_bio->state)); + + if (!rdev) { + /* Cannot read from here, so need to record bad blocks + * on all the target devices. + */ + // FIXME + set_bit(MD_RECOVERY_INTR, &mddev->recovery); + return sectors_done; + } + + read_bio = bio_alloc_mddev(GFP_KERNEL, RESYNC_PAGES, mddev); + + read_bio->bi_bdev = rdev->bdev; + read_bio->bi_sector = (r10_bio->devs[r10_bio->read_slot].addr + + rdev->data_offset); + read_bio->bi_private = r10_bio; + read_bio->bi_end_io = end_sync_read; + read_bio->bi_rw = READ; + read_bio->bi_flags &= ~(BIO_POOL_MASK - 1); + read_bio->bi_flags |= 1 << BIO_UPTODATE; + read_bio->bi_vcnt = 0; + read_bio->bi_idx = 0; + read_bio->bi_size = 0; + r10_bio->master_bio = read_bio; + r10_bio->read_slot = r10_bio->devs[r10_bio->read_slot].devnum; + + /* Now find the locations in the new layout */ + __raid10_find_phys(&conf->geo, r10_bio); + + blist = read_bio; + read_bio->bi_next = NULL; + + for (s = 0; s < conf->copies*2; s++) { + struct bio *b; + int d = r10_bio->devs[s/2].devnum; + struct md_rdev *rdev2; + if (s&1) { + rdev2 = conf->mirrors[d].replacement; + b = r10_bio->devs[s/2].repl_bio; + } else { + rdev2 = conf->mirrors[d].rdev; + b = r10_bio->devs[s/2].bio; + } + if (!rdev2 || test_bit(Faulty, &rdev2->flags)) + continue; + b->bi_bdev = rdev2->bdev; + b->bi_sector = r10_bio->devs[s/2].addr + rdev2->new_data_offset; + b->bi_private = r10_bio; + b->bi_end_io = end_reshape_write; + b->bi_rw = WRITE; + b->bi_flags &= ~(BIO_POOL_MASK - 1); + b->bi_flags |= 1 << BIO_UPTODATE; + b->bi_next = blist; + b->bi_vcnt = 0; + b->bi_idx = 0; + b->bi_size = 0; + blist = b; + } + + /* Now add as many pages as possible to all of these bios. */ + + nr_sectors = 0; + for (s = 0 ; s < max_sectors; s += PAGE_SIZE >> 9) { + struct page *page = r10_bio->devs[0].bio->bi_io_vec[s/(PAGE_SIZE>>9)].bv_page; + int len = (max_sectors - s) << 9; + if (len > PAGE_SIZE) + len = PAGE_SIZE; + for (bio = blist; bio ; bio = bio->bi_next) { + struct bio *bio2; + if (bio_add_page(bio, page, len, 0)) + continue; + + /* Didn't fit, must stop */ + for (bio2 = blist; + bio2 && bio2 != bio; + bio2 = bio2->bi_next) { + /* Remove last page from this bio */ + bio2->bi_vcnt--; + bio2->bi_size -= len; + bio2->bi_flags &= ~(1<<BIO_SEG_VALID); + } + goto bio_full; + } + sector_nr += len >> 9; + nr_sectors += len >> 9; + } +bio_full: + r10_bio->sectors = nr_sectors; + + /* Now submit the read */ + md_sync_acct(read_bio->bi_bdev, r10_bio->sectors); + atomic_inc(&r10_bio->remaining); + read_bio->bi_next = NULL; + generic_make_request(read_bio); + sector_nr += nr_sectors; + sectors_done += nr_sectors; + if (sector_nr <= last) + goto read_more; + + /* Now that we have done the whole section we can + * update reshape_progress + */ + if (mddev->reshape_backwards) + conf->reshape_progress -= sectors_done; + else + conf->reshape_progress += sectors_done; + + return sectors_done; +} + +static void end_reshape_request(struct r10bio *r10_bio); +static int handle_reshape_read_error(struct mddev *mddev, + struct r10bio *r10_bio); +static void reshape_request_write(struct mddev *mddev, struct r10bio *r10_bio) +{ + /* Reshape read completed. Hopefully we have a block + * to write out. + * If we got a read error then we do sync 1-page reads from + * elsewhere until we find the data - or give up. + */ + struct r10conf *conf = mddev->private; + int s; + + if (!test_bit(R10BIO_Uptodate, &r10_bio->state)) + if (handle_reshape_read_error(mddev, r10_bio) < 0) { + /* Reshape has been aborted */ + md_done_sync(mddev, r10_bio->sectors, 0); + return; + } + + /* We definitely have the data in the pages, schedule the + * writes. + */ + atomic_set(&r10_bio->remaining, 1); + for (s = 0; s < conf->copies*2; s++) { + struct bio *b; + int d = r10_bio->devs[s/2].devnum; + struct md_rdev *rdev; + if (s&1) { + rdev = conf->mirrors[d].replacement; + b = r10_bio->devs[s/2].repl_bio; + } else { + rdev = conf->mirrors[d].rdev; + b = r10_bio->devs[s/2].bio; + } + if (!rdev || test_bit(Faulty, &rdev->flags)) + continue; + atomic_inc(&rdev->nr_pending); + md_sync_acct(b->bi_bdev, r10_bio->sectors); + atomic_inc(&r10_bio->remaining); + b->bi_next = NULL; + generic_make_request(b); + } + end_reshape_request(r10_bio); +} + +static void end_reshape(struct r10conf *conf) +{ + if (test_bit(MD_RECOVERY_INTR, &conf->mddev->recovery)) + return; + + spin_lock_irq(&conf->device_lock); + conf->prev = conf->geo; + md_finish_reshape(conf->mddev); + smp_wmb(); + conf->reshape_progress = MaxSector; + spin_unlock_irq(&conf->device_lock); + + /* read-ahead size must cover two whole stripes, which is + * 2 * (datadisks) * chunksize where 'n' is the number of raid devices + */ + if (conf->mddev->queue) { + int stripe = conf->geo.raid_disks * + ((conf->mddev->chunk_sectors << 9) / PAGE_SIZE); + stripe /= conf->geo.near_copies; + if (conf->mddev->queue->backing_dev_info.ra_pages < 2 * stripe) + conf->mddev->queue->backing_dev_info.ra_pages = 2 * stripe; + } + conf->fullsync = 0; +} + + +static int handle_reshape_read_error(struct mddev *mddev, + struct r10bio *r10_bio) +{ + /* Use sync reads to get the blocks from somewhere else */ + int sectors = r10_bio->sectors; + struct r10bio r10b; + struct r10conf *conf = mddev->private; + int slot = 0; + int idx = 0; + struct bio_vec *bvec = r10_bio->master_bio->bi_io_vec; + + r10b.sector = r10_bio->sector; + __raid10_find_phys(&conf->prev, &r10b); + + while (sectors) { + int s = sectors; + int success = 0; + int first_slot = slot; + + if (s > (PAGE_SIZE >> 9)) + s = PAGE_SIZE >> 9; + + while (!success) { + int d = r10b.devs[slot].devnum; + struct md_rdev *rdev = conf->mirrors[d].rdev; + sector_t addr; + if (rdev == NULL || + test_bit(Faulty, &rdev->flags) || + !test_bit(In_sync, &rdev->flags)) + goto failed; + + addr = r10b.devs[slot].addr + idx * PAGE_SIZE; + success = sync_page_io(rdev, + addr, + s << 9, + bvec[idx].bv_page, + READ, false); + if (success) + break; + failed: + slot++; + if (slot >= conf->copies) + slot = 0; + if (slot == first_slot) + break; + } + if (!success) { + /* couldn't read this block, must give up */ + set_bit(MD_RECOVERY_INTR, + &mddev->recovery); + return -EIO; + } + sectors -= s; + idx++; + } + return 0; +} + +static void end_reshape_write(struct bio *bio, int error) +{ + int uptodate = test_bit(BIO_UPTODATE, &bio->bi_flags); + struct r10bio *r10_bio = bio->bi_private; + struct mddev *mddev = r10_bio->mddev; + struct r10conf *conf = mddev->private; + int d; + int slot; + int repl; + struct md_rdev *rdev = NULL; + + d = find_bio_disk(conf, r10_bio, bio, &slot, &repl); + if (repl) + rdev = conf->mirrors[d].replacement; + if (!rdev) { + smp_mb(); + rdev = conf->mirrors[d].rdev; + } + + if (!uptodate) { + /* FIXME should record badblock */ + md_error(mddev, rdev); + } + + rdev_dec_pending(rdev, mddev); + end_reshape_request(r10_bio); +} + +static void end_reshape_request(struct r10bio *r10_bio) +{ + if (!atomic_dec_and_test(&r10_bio->remaining)) + return; + md_done_sync(r10_bio->mddev, r10_bio->sectors, 1); + bio_put(r10_bio->master_bio); + put_buf(r10_bio); +} + +static void raid10_finish_reshape(struct mddev *mddev) +{ + struct r10conf *conf = mddev->private; + + if (test_bit(MD_RECOVERY_INTR, &mddev->recovery)) + return; + + if (mddev->delta_disks > 0) { + sector_t size = raid10_size(mddev, 0, 0); + md_set_array_sectors(mddev, size); + if (mddev->recovery_cp > mddev->resync_max_sectors) { + mddev->recovery_cp = mddev->resync_max_sectors; + set_bit(MD_RECOVERY_NEEDED, &mddev->recovery); + } + mddev->resync_max_sectors = size; + set_capacity(mddev->gendisk, mddev->array_sectors); + revalidate_disk(mddev->gendisk); + } else { + int d; + for (d = conf->geo.raid_disks ; + d < conf->geo.raid_disks - mddev->delta_disks; + d++) { + struct md_rdev *rdev = conf->mirrors[d].rdev; + if (rdev) + clear_bit(In_sync, &rdev->flags); + rdev = conf->mirrors[d].replacement; + if (rdev) + clear_bit(In_sync, &rdev->flags); + } + } + mddev->layout = mddev->new_layout; + mddev->chunk_sectors = 1 << conf->geo.chunk_shift; + mddev->reshape_position = MaxSector; + mddev->delta_disks = 0; + mddev->reshape_backwards = 0; +} + static struct md_personality raid10_personality = { .name = "raid10", @@ -3552,6 +4524,9 @@ static struct md_personality raid10_personality = .size = raid10_size, .resize = raid10_resize, .takeover = raid10_takeover, + .check_reshape = raid10_check_reshape, + .start_reshape = raid10_start_reshape, + .finish_reshape = raid10_finish_reshape, }; static int __init raid_init(void) diff --git a/drivers/md/raid10.h b/drivers/md/raid10.h index 7c615613c381..135b1b0a1554 100644 --- a/drivers/md/raid10.h +++ b/drivers/md/raid10.h @@ -14,32 +14,38 @@ struct mirror_info { struct r10conf { struct mddev *mddev; struct mirror_info *mirrors; - int raid_disks; + struct mirror_info *mirrors_new, *mirrors_old; spinlock_t device_lock; /* geometry */ - int near_copies; /* number of copies laid out + struct geom { + int raid_disks; + int near_copies; /* number of copies laid out * raid0 style */ - int far_copies; /* number of copies laid out + int far_copies; /* number of copies laid out * at large strides across drives */ - int far_offset; /* far_copies are offset by 1 + int far_offset; /* far_copies are offset by 1 * stripe instead of many */ - int copies; /* near_copies * far_copies. - * must be <= raid_disks - */ - sector_t stride; /* distance between far copies. + sector_t stride; /* distance between far copies. * This is size / far_copies unless * far_offset, in which case it is * 1 stripe. */ + int chunk_shift; /* shift from chunks to sectors */ + sector_t chunk_mask; + } prev, geo; + int copies; /* near_copies * far_copies. + * must be <= raid_disks + */ sector_t dev_sectors; /* temp copy of * mddev->dev_sectors */ - - int chunk_shift; /* shift from chunks to sectors */ - sector_t chunk_mask; + sector_t reshape_progress; + sector_t reshape_safe; + unsigned long reshape_checkpoint; + sector_t offset_diff; struct list_head retry_list; /* queue pending writes and submit them on unplug */ @@ -136,6 +142,7 @@ enum r10bio_state { R10BIO_Uptodate, R10BIO_IsSync, R10BIO_IsRecover, + R10BIO_IsReshape, R10BIO_Degraded, /* Set ReadError on bios that experience a read error * so that raid10d knows what to do with them. @@ -146,5 +153,10 @@ enum r10bio_state { */ R10BIO_MadeGood, R10BIO_WriteError, +/* During a reshape we might be performing IO on the + * 'previous' part of the array, in which case this + * flag is set + */ + R10BIO_Previous, }; #endif diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index f351422938e0..d26767246d26 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -488,6 +488,27 @@ get_active_stripe(struct r5conf *conf, sector_t sector, return sh; } +/* Determine if 'data_offset' or 'new_data_offset' should be used + * in this stripe_head. + */ +static int use_new_offset(struct r5conf *conf, struct stripe_head *sh) +{ + sector_t progress = conf->reshape_progress; + /* Need a memory barrier to make sure we see the value + * of conf->generation, or ->data_offset that was set before + * reshape_progress was updated. + */ + smp_rmb(); + if (progress == MaxSector) + return 0; + if (sh->generation == conf->generation - 1) + return 0; + /* We are in a reshape, and this is a new-generation stripe, + * so use new_data_offset. + */ + return 1; +} + static void raid5_end_read_request(struct bio *bi, int error); static void @@ -518,6 +539,8 @@ static void ops_run_io(struct stripe_head *sh, struct stripe_head_state *s) replace_only = 1; } else continue; + if (test_and_clear_bit(R5_SyncIO, &sh->dev[i].flags)) + rw |= REQ_SYNC; bi = &sh->dev[i].req; rbi = &sh->dev[i].rreq; /* For writing to replacement */ @@ -603,7 +626,12 @@ static void ops_run_io(struct stripe_head *sh, struct stripe_head_state *s) __func__, (unsigned long long)sh->sector, bi->bi_rw, i); atomic_inc(&sh->count); - bi->bi_sector = sh->sector + rdev->data_offset; + if (use_new_offset(conf, sh)) + bi->bi_sector = (sh->sector + + rdev->new_data_offset); + else + bi->bi_sector = (sh->sector + + rdev->data_offset); bi->bi_flags = 1 << BIO_UPTODATE; bi->bi_idx = 0; bi->bi_io_vec[0].bv_len = STRIPE_SIZE; @@ -627,7 +655,12 @@ static void ops_run_io(struct stripe_head *sh, struct stripe_head_state *s) __func__, (unsigned long long)sh->sector, rbi->bi_rw, i); atomic_inc(&sh->count); - rbi->bi_sector = sh->sector + rrdev->data_offset; + if (use_new_offset(conf, sh)) + rbi->bi_sector = (sh->sector + + rrdev->new_data_offset); + else + rbi->bi_sector = (sh->sector + + rrdev->data_offset); rbi->bi_flags = 1 << BIO_UPTODATE; rbi->bi_idx = 0; rbi->bi_io_vec[0].bv_len = STRIPE_SIZE; @@ -1114,6 +1147,8 @@ ops_run_biodrain(struct stripe_head *sh, struct dma_async_tx_descriptor *tx) dev->sector + STRIPE_SECTORS) { if (wbi->bi_rw & REQ_FUA) set_bit(R5_WantFUA, &dev->flags); + if (wbi->bi_rw & REQ_SYNC) + set_bit(R5_SyncIO, &dev->flags); tx = async_copy_data(1, wbi, dev->page, dev->sector, tx); wbi = r5_next_bio(wbi, dev->sector); @@ -1131,13 +1166,15 @@ static void ops_complete_reconstruct(void *stripe_head_ref) int pd_idx = sh->pd_idx; int qd_idx = sh->qd_idx; int i; - bool fua = false; + bool fua = false, sync = false; pr_debug("%s: stripe %llu\n", __func__, (unsigned long long)sh->sector); - for (i = disks; i--; ) + for (i = disks; i--; ) { fua |= test_bit(R5_WantFUA, &sh->dev[i].flags); + sync |= test_bit(R5_SyncIO, &sh->dev[i].flags); + } for (i = disks; i--; ) { struct r5dev *dev = &sh->dev[i]; @@ -1146,6 +1183,8 @@ static void ops_complete_reconstruct(void *stripe_head_ref) set_bit(R5_UPTODATE, &dev->flags); if (fua) set_bit(R5_WantFUA, &dev->flags); + if (sync) + set_bit(R5_SyncIO, &dev->flags); } } @@ -1648,7 +1687,7 @@ static void raid5_end_read_request(struct bio * bi, int error) int uptodate = test_bit(BIO_UPTODATE, &bi->bi_flags); char b[BDEVNAME_SIZE]; struct md_rdev *rdev = NULL; - + sector_t s; for (i=0 ; i<disks; i++) if (bi == &sh->dev[i].req) @@ -1671,6 +1710,10 @@ static void raid5_end_read_request(struct bio * bi, int error) if (!rdev) rdev = conf->disks[i].rdev; + if (use_new_offset(conf, sh)) + s = sh->sector + rdev->new_data_offset; + else + s = sh->sector + rdev->data_offset; if (uptodate) { set_bit(R5_UPTODATE, &sh->dev[i].flags); if (test_bit(R5_ReadError, &sh->dev[i].flags)) { @@ -1683,8 +1726,7 @@ static void raid5_end_read_request(struct bio * bi, int error) "md/raid:%s: read error corrected" " (%lu sectors at %llu on %s)\n", mdname(conf->mddev), STRIPE_SECTORS, - (unsigned long long)(sh->sector - + rdev->data_offset), + (unsigned long long)s, bdevname(rdev->bdev, b)); atomic_add(STRIPE_SECTORS, &rdev->corrected_errors); clear_bit(R5_ReadError, &sh->dev[i].flags); @@ -1704,8 +1746,7 @@ static void raid5_end_read_request(struct bio * bi, int error) "md/raid:%s: read error on replacement device " "(sector %llu on %s).\n", mdname(conf->mddev), - (unsigned long long)(sh->sector - + rdev->data_offset), + (unsigned long long)s, bdn); else if (conf->mddev->degraded >= conf->max_degraded) printk_ratelimited( @@ -1713,8 +1754,7 @@ static void raid5_end_read_request(struct bio * bi, int error) "md/raid:%s: read error not correctable " "(sector %llu on %s).\n", mdname(conf->mddev), - (unsigned long long)(sh->sector - + rdev->data_offset), + (unsigned long long)s, bdn); else if (test_bit(R5_ReWrite, &sh->dev[i].flags)) /* Oh, no!!! */ @@ -1723,8 +1763,7 @@ static void raid5_end_read_request(struct bio * bi, int error) "md/raid:%s: read error NOT corrected!! " "(sector %llu on %s).\n", mdname(conf->mddev), - (unsigned long long)(sh->sector - + rdev->data_offset), + (unsigned long long)s, bdn); else if (atomic_read(&rdev->read_errors) > conf->max_nr_stripes) @@ -3561,7 +3600,7 @@ finish: if (test_and_clear_bit(R5_MadeGood, &dev->flags)) { rdev = conf->disks[i].rdev; rdev_clear_badblocks(rdev, sh->sector, - STRIPE_SECTORS); + STRIPE_SECTORS, 0); rdev_dec_pending(rdev, conf->mddev); } if (test_and_clear_bit(R5_MadeGoodRepl, &dev->flags)) { @@ -3570,7 +3609,7 @@ finish: /* rdev have been moved down */ rdev = conf->disks[i].rdev; rdev_clear_badblocks(rdev, sh->sector, - STRIPE_SECTORS); + STRIPE_SECTORS, 0); rdev_dec_pending(rdev, conf->mddev); } } @@ -3842,6 +3881,7 @@ static int chunk_aligned_read(struct mddev *mddev, struct bio * raid_bio) raid_bio->bi_next = (void*)rdev; align_bi->bi_bdev = rdev->bdev; align_bi->bi_flags &= ~(1 << BIO_SEG_VALID); + /* No reshape active, so we can trust rdev->data_offset */ align_bi->bi_sector += rdev->data_offset; if (!bio_fits_rdev(align_bi) || @@ -3953,12 +3993,10 @@ static void make_request(struct mddev *mddev, struct bio * bi) plugged = mddev_check_plugged(mddev); for (;logical_sector < last_sector; logical_sector += STRIPE_SECTORS) { DEFINE_WAIT(w); - int disks, data_disks; int previous; retry: previous = 0; - disks = conf->raid_disks; prepare_to_wait(&conf->wait_for_overlap, &w, TASK_UNINTERRUPTIBLE); if (unlikely(conf->reshape_progress != MaxSector)) { /* spinlock is needed as reshape_progress may be @@ -3970,13 +4008,12 @@ static void make_request(struct mddev *mddev, struct bio * bi) * to check again. */ spin_lock_irq(&conf->device_lock); - if (mddev->delta_disks < 0 + if (mddev->reshape_backwards ? logical_sector < conf->reshape_progress : logical_sector >= conf->reshape_progress) { - disks = conf->previous_raid_disks; previous = 1; } else { - if (mddev->delta_disks < 0 + if (mddev->reshape_backwards ? logical_sector < conf->reshape_safe : logical_sector >= conf->reshape_safe) { spin_unlock_irq(&conf->device_lock); @@ -3986,7 +4023,6 @@ static void make_request(struct mddev *mddev, struct bio * bi) } spin_unlock_irq(&conf->device_lock); } - data_disks = disks - conf->max_degraded; new_sector = raid5_compute_sector(conf, logical_sector, previous, @@ -4009,7 +4045,7 @@ static void make_request(struct mddev *mddev, struct bio * bi) */ int must_retry = 0; spin_lock_irq(&conf->device_lock); - if (mddev->delta_disks < 0 + if (mddev->reshape_backwards ? logical_sector >= conf->reshape_progress : logical_sector < conf->reshape_progress) /* mismatch, need to try again */ @@ -4108,11 +4144,11 @@ static sector_t reshape_request(struct mddev *mddev, sector_t sector_nr, int *sk if (sector_nr == 0) { /* If restarting in the middle, skip the initial sectors */ - if (mddev->delta_disks < 0 && + if (mddev->reshape_backwards && conf->reshape_progress < raid5_size(mddev, 0, 0)) { sector_nr = raid5_size(mddev, 0, 0) - conf->reshape_progress; - } else if (mddev->delta_disks >= 0 && + } else if (!mddev->reshape_backwards && conf->reshape_progress > 0) sector_nr = conf->reshape_progress; sector_div(sector_nr, new_data_disks); @@ -4133,13 +4169,11 @@ static sector_t reshape_request(struct mddev *mddev, sector_t sector_nr, int *sk else reshape_sectors = mddev->chunk_sectors; - /* we update the metadata when there is more than 3Meg - * in the block range (that is rather arbitrary, should - * probably be time based) or when the data about to be - * copied would over-write the source of the data at - * the front of the range. - * i.e. one new_stripe along from reshape_progress new_maps - * to after where reshape_safe old_maps to + /* We update the metadata at least every 10 seconds, or when + * the data about to be copied would over-write the source of + * the data at the front of the range. i.e. one new_stripe + * along from reshape_progress new_maps to after where + * reshape_safe old_maps to */ writepos = conf->reshape_progress; sector_div(writepos, new_data_disks); @@ -4147,7 +4181,7 @@ static sector_t reshape_request(struct mddev *mddev, sector_t sector_nr, int *sk sector_div(readpos, data_disks); safepos = conf->reshape_safe; sector_div(safepos, data_disks); - if (mddev->delta_disks < 0) { + if (mddev->reshape_backwards) { writepos -= min_t(sector_t, reshape_sectors, writepos); readpos += reshape_sectors; safepos += reshape_sectors; @@ -4157,11 +4191,29 @@ static sector_t reshape_request(struct mddev *mddev, sector_t sector_nr, int *sk safepos -= min_t(sector_t, reshape_sectors, safepos); } + /* Having calculated the 'writepos' possibly use it + * to set 'stripe_addr' which is where we will write to. + */ + if (mddev->reshape_backwards) { + BUG_ON(conf->reshape_progress == 0); + stripe_addr = writepos; + BUG_ON((mddev->dev_sectors & + ~((sector_t)reshape_sectors - 1)) + - reshape_sectors - stripe_addr + != sector_nr); + } else { + BUG_ON(writepos != sector_nr + reshape_sectors); + stripe_addr = sector_nr; + } + /* 'writepos' is the most advanced device address we might write. * 'readpos' is the least advanced device address we might read. * 'safepos' is the least address recorded in the metadata as having * been reshaped. - * If 'readpos' is behind 'writepos', then there is no way that we can + * If there is a min_offset_diff, these are adjusted either by + * increasing the safepos/readpos if diff is negative, or + * increasing writepos if diff is positive. + * If 'readpos' is then behind 'writepos', there is no way that we can * ensure safety in the face of a crash - that must be done by userspace * making a backup of the data. So in that case there is no particular * rush to update metadata. @@ -4174,7 +4226,13 @@ static sector_t reshape_request(struct mddev *mddev, sector_t sector_nr, int *sk * Maybe that number should be configurable, but I'm not sure it is * worth it.... maybe it could be a multiple of safemode_delay??? */ - if ((mddev->delta_disks < 0 + if (conf->min_offset_diff < 0) { + safepos += -conf->min_offset_diff; + readpos += -conf->min_offset_diff; + } else + writepos += conf->min_offset_diff; + + if ((mddev->reshape_backwards ? (safepos > writepos && readpos < writepos) : (safepos < writepos && readpos > writepos)) || time_after(jiffies, conf->reshape_checkpoint + 10*HZ)) { @@ -4195,17 +4253,6 @@ static sector_t reshape_request(struct mddev *mddev, sector_t sector_nr, int *sk sysfs_notify(&mddev->kobj, NULL, "sync_completed"); } - if (mddev->delta_disks < 0) { - BUG_ON(conf->reshape_progress == 0); - stripe_addr = writepos; - BUG_ON((mddev->dev_sectors & - ~((sector_t)reshape_sectors - 1)) - - reshape_sectors - stripe_addr - != sector_nr); - } else { - BUG_ON(writepos != sector_nr + reshape_sectors); - stripe_addr = sector_nr; - } INIT_LIST_HEAD(&stripes); for (i = 0; i < reshape_sectors; i += STRIPE_SECTORS) { int j; @@ -4239,7 +4286,7 @@ static sector_t reshape_request(struct mddev *mddev, sector_t sector_nr, int *sk list_add(&sh->lru, &stripes); } spin_lock_irq(&conf->device_lock); - if (mddev->delta_disks < 0) + if (mddev->reshape_backwards) conf->reshape_progress -= reshape_sectors * new_data_disks; else conf->reshape_progress += reshape_sectors * new_data_disks; @@ -4952,16 +4999,42 @@ static int run(struct mddev *mddev) struct md_rdev *rdev; sector_t reshape_offset = 0; int i; + long long min_offset_diff = 0; + int first = 1; if (mddev->recovery_cp != MaxSector) printk(KERN_NOTICE "md/raid:%s: not clean" " -- starting background reconstruction\n", mdname(mddev)); + + rdev_for_each(rdev, mddev) { + long long diff; + if (rdev->raid_disk < 0) + continue; + diff = (rdev->new_data_offset - rdev->data_offset); + if (first) { + min_offset_diff = diff; + first = 0; + } else if (mddev->reshape_backwards && + diff < min_offset_diff) + min_offset_diff = diff; + else if (!mddev->reshape_backwards && + diff > min_offset_diff) + min_offset_diff = diff; + } + if (mddev->reshape_position != MaxSector) { /* Check that we can continue the reshape. - * Currently only disks can change, it must - * increase, and we must be past the point where - * a stripe over-writes itself + * Difficulties arise if the stripe we would write to + * next is at or after the stripe we would read from next. + * For a reshape that changes the number of devices, this + * is only possible for a very short time, and mdadm makes + * sure that time appears to have past before assembling + * the array. So we fail if that time hasn't passed. + * For a reshape that keeps the number of devices the same + * mdadm must be monitoring the reshape can keeping the + * critical areas read-only and backed up. It will start + * the array in read-only mode, so we check for that. */ sector_t here_new, here_old; int old_disks; @@ -4993,26 +5066,34 @@ static int run(struct mddev *mddev) /* here_old is the first stripe that we might need to read * from */ if (mddev->delta_disks == 0) { + if ((here_new * mddev->new_chunk_sectors != + here_old * mddev->chunk_sectors)) { + printk(KERN_ERR "md/raid:%s: reshape position is" + " confused - aborting\n", mdname(mddev)); + return -EINVAL; + } /* We cannot be sure it is safe to start an in-place - * reshape. It is only safe if user-space if monitoring + * reshape. It is only safe if user-space is monitoring * and taking constant backups. * mdadm always starts a situation like this in * readonly mode so it can take control before * allowing any writes. So just check for that. */ - if ((here_new * mddev->new_chunk_sectors != - here_old * mddev->chunk_sectors) || - mddev->ro == 0) { - printk(KERN_ERR "md/raid:%s: in-place reshape must be started" - " in read-only mode - aborting\n", + if (abs(min_offset_diff) >= mddev->chunk_sectors && + abs(min_offset_diff) >= mddev->new_chunk_sectors) + /* not really in-place - so OK */; + else if (mddev->ro == 0) { + printk(KERN_ERR "md/raid:%s: in-place reshape " + "must be started in read-only mode " + "- aborting\n", mdname(mddev)); return -EINVAL; } - } else if (mddev->delta_disks < 0 - ? (here_new * mddev->new_chunk_sectors <= + } else if (mddev->reshape_backwards + ? (here_new * mddev->new_chunk_sectors + min_offset_diff <= here_old * mddev->chunk_sectors) : (here_new * mddev->new_chunk_sectors >= - here_old * mddev->chunk_sectors)) { + here_old * mddev->chunk_sectors + (-min_offset_diff))) { /* Reading from the same stripe as writing to - bad */ printk(KERN_ERR "md/raid:%s: reshape_position too early for " "auto-recovery - aborting.\n", @@ -5037,6 +5118,7 @@ static int run(struct mddev *mddev) if (IS_ERR(conf)) return PTR_ERR(conf); + conf->min_offset_diff = min_offset_diff; mddev->thread = conf->thread; conf->thread = NULL; mddev->private = conf; @@ -5182,9 +5264,12 @@ static int run(struct mddev *mddev) blk_queue_io_opt(mddev->queue, chunk_size * (conf->raid_disks - conf->max_degraded)); - rdev_for_each(rdev, mddev) + rdev_for_each(rdev, mddev) { disk_stack_limits(mddev->gendisk, rdev->bdev, rdev->data_offset << 9); + disk_stack_limits(mddev->gendisk, rdev->bdev, + rdev->new_data_offset << 9); + } } return 0; @@ -5418,12 +5503,18 @@ static int raid5_resize(struct mddev *mddev, sector_t sectors) * any io in the removed space completes, but it hardly seems * worth it. */ + sector_t newsize; sectors &= ~((sector_t)mddev->chunk_sectors - 1); - md_set_array_sectors(mddev, raid5_size(mddev, sectors, - mddev->raid_disks)); - if (mddev->array_sectors > - raid5_size(mddev, sectors, mddev->raid_disks)) + newsize = raid5_size(mddev, sectors, mddev->raid_disks); + if (mddev->external_size && + mddev->array_sectors > newsize) return -EINVAL; + if (mddev->bitmap) { + int ret = bitmap_resize(mddev->bitmap, sectors, 0, 0); + if (ret) + return ret; + } + md_set_array_sectors(mddev, newsize); set_capacity(mddev->gendisk, mddev->array_sectors); revalidate_disk(mddev->gendisk); if (sectors > mddev->dev_sectors && @@ -5468,9 +5559,6 @@ static int check_reshape(struct mddev *mddev) mddev->new_layout == mddev->layout && mddev->new_chunk_sectors == mddev->chunk_sectors) return 0; /* nothing to do */ - if (mddev->bitmap) - /* Cannot grow a bitmap yet */ - return -EBUSY; if (has_failed(conf)) return -EINVAL; if (mddev->delta_disks < 0) { @@ -5505,10 +5593,14 @@ static int raid5_start_reshape(struct mddev *mddev) if (!check_stripe_cache(mddev)) return -ENOSPC; - rdev_for_each(rdev, mddev) + if (has_failed(conf)) + return -EINVAL; + + rdev_for_each(rdev, mddev) { if (!test_bit(In_sync, &rdev->flags) && !test_bit(Faulty, &rdev->flags)) spares++; + } if (spares - mddev->degraded < mddev->delta_disks - conf->max_degraded) /* Not enough devices even to make a degraded array @@ -5535,12 +5627,16 @@ static int raid5_start_reshape(struct mddev *mddev) conf->chunk_sectors = mddev->new_chunk_sectors; conf->prev_algo = conf->algorithm; conf->algorithm = mddev->new_layout; - if (mddev->delta_disks < 0) + conf->generation++; + /* Code that selects data_offset needs to see the generation update + * if reshape_progress has been set - so a memory barrier needed. + */ + smp_mb(); + if (mddev->reshape_backwards) conf->reshape_progress = raid5_size(mddev, 0, 0); else conf->reshape_progress = 0; conf->reshape_safe = conf->reshape_progress; - conf->generation++; spin_unlock_irq(&conf->device_lock); /* Add some new drives, as many as will fit. @@ -5592,6 +5688,9 @@ static int raid5_start_reshape(struct mddev *mddev) mddev->recovery = 0; spin_lock_irq(&conf->device_lock); mddev->raid_disks = conf->raid_disks = conf->previous_raid_disks; + rdev_for_each(rdev, mddev) + rdev->new_data_offset = rdev->data_offset; + smp_wmb(); conf->reshape_progress = MaxSector; mddev->reshape_position = MaxSector; spin_unlock_irq(&conf->device_lock); @@ -5610,9 +5709,13 @@ static void end_reshape(struct r5conf *conf) { if (!test_bit(MD_RECOVERY_INTR, &conf->mddev->recovery)) { + struct md_rdev *rdev; spin_lock_irq(&conf->device_lock); conf->previous_raid_disks = conf->raid_disks; + rdev_for_each(rdev, conf->mddev) + rdev->data_offset = rdev->new_data_offset; + smp_wmb(); conf->reshape_progress = MaxSector; spin_unlock_irq(&conf->device_lock); wake_up(&conf->wait_for_overlap); @@ -5652,17 +5755,18 @@ static void raid5_finish_reshape(struct mddev *mddev) d < conf->raid_disks - mddev->delta_disks; d++) { struct md_rdev *rdev = conf->disks[d].rdev; - if (rdev && - raid5_remove_disk(mddev, rdev) == 0) { - sysfs_unlink_rdev(mddev, rdev); - rdev->raid_disk = -1; - } + if (rdev) + clear_bit(In_sync, &rdev->flags); + rdev = conf->disks[d].replacement; + if (rdev) + clear_bit(In_sync, &rdev->flags); } } mddev->layout = conf->algorithm; mddev->chunk_sectors = conf->chunk_sectors; mddev->reshape_position = MaxSector; mddev->delta_disks = 0; + mddev->reshape_backwards = 0; } } diff --git a/drivers/md/raid5.h b/drivers/md/raid5.h index 8d8e13934a48..2164021f3b5f 100644 --- a/drivers/md/raid5.h +++ b/drivers/md/raid5.h @@ -285,6 +285,7 @@ enum r5dev_flags { */ R5_Wantdrain, /* dev->towrite needs to be drained */ R5_WantFUA, /* Write should be FUA */ + R5_SyncIO, /* The IO is sync */ R5_WriteError, /* got a write error - need to record it */ R5_MadeGood, /* A bad block has been fixed by writing to it */ R5_ReadRepl, /* Will/did read from replacement rather than orig */ @@ -385,6 +386,12 @@ struct r5conf { short generation; /* increments with every reshape */ unsigned long reshape_checkpoint; /* Time we last updated * metadata */ + long long min_offset_diff; /* minimum difference between + * data_offset and + * new_data_offset across all + * devices. May be negative, + * but is closest to zero. + */ struct list_head handle_list; /* stripes needing handling */ struct list_head hold_list; /* preread ready stripes */ diff --git a/drivers/message/i2o/i2o_proc.c b/drivers/message/i2o/i2o_proc.c index 6d115c7208ab..506c36f6e1db 100644 --- a/drivers/message/i2o/i2o_proc.c +++ b/drivers/message/i2o/i2o_proc.c @@ -283,7 +283,6 @@ static char *bus_strings[] = { "Local Bus", "ISA", "EISA", - "MCA", "PCI", "PCMCIA", "NUBUS", @@ -351,18 +350,6 @@ static int i2o_seq_show_hrt(struct seq_file *seq, void *v) EisaSlotNumber); break; - case I2O_BUS_MCA: - seq_printf(seq, " IOBase: %0#6x,", - hrt->hrt_entry[i].bus.mca_bus. - McaBaseIOPort); - seq_printf(seq, " MemoryBase: %0#10x,", - hrt->hrt_entry[i].bus.mca_bus. - McaBaseMemoryAddress); - seq_printf(seq, " Slot: %0#4x,", - hrt->hrt_entry[i].bus.mca_bus. - McaSlotNumber); - break; - case I2O_BUS_PCI: seq_printf(seq, " Bus: %0#4x", hrt->hrt_entry[i].bus.pci_bus. diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 220686159c15..f4b4dad77391 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -377,7 +377,6 @@ config PMIC_DA9052 config MFD_DA9052_SPI bool "Support Dialog Semiconductor DA9052/53 PMIC variants with SPI" - select IRQ_DOMAIN select REGMAP_SPI select REGMAP_IRQ select PMIC_DA9052 @@ -390,7 +389,6 @@ config MFD_DA9052_SPI config MFD_DA9052_I2C bool "Support Dialog Semiconductor DA9052/53 PMIC variants with I2C" - select IRQ_DOMAIN select REGMAP_I2C select REGMAP_IRQ select PMIC_DA9052 @@ -561,7 +559,6 @@ config MFD_WM8994 bool "Support Wolfson Microelectronics WM8994" select MFD_CORE select REGMAP_I2C - select IRQ_DOMAIN select REGMAP_IRQ depends on I2C=y && GENERIC_HARDIRQS help diff --git a/drivers/mfd/mc13xxx-core.c b/drivers/mfd/mc13xxx-core.c index 9fd4f63c45cc..738722cdecaa 100644 --- a/drivers/mfd/mc13xxx-core.c +++ b/drivers/mfd/mc13xxx-core.c @@ -813,7 +813,8 @@ err_revision: mc13xxx_add_subdevice(mc13xxx, "%s-adc"); if (mc13xxx->flags & MC13XXX_USE_CODEC) - mc13xxx_add_subdevice(mc13xxx, "%s-codec"); + mc13xxx_add_subdevice_pdata(mc13xxx, "%s-codec", + pdata->codec, sizeof(*pdata->codec)); if (mc13xxx->flags & MC13XXX_USE_RTC) mc13xxx_add_subdevice(mc13xxx, "%s-rtc"); diff --git a/drivers/ps3/ps3av.c b/drivers/ps3/ps3av.c index a409fa050a1a..93d0a8b7718a 100644 --- a/drivers/ps3/ps3av.c +++ b/drivers/ps3/ps3av.c @@ -338,7 +338,7 @@ int ps3av_do_pkt(u32 cid, u16 send_len, size_t usr_buf_size, mutex_unlock(&ps3av->mutex); return 0; - err: +err: mutex_unlock(&ps3av->mutex); printk(KERN_ERR "%s: failed cid:%x res:%d\n", __func__, cid, res); return res; @@ -477,7 +477,6 @@ int ps3av_set_audio_mode(u32 ch, u32 fs, u32 word_bits, u32 format, u32 source) return 0; } - EXPORT_SYMBOL_GPL(ps3av_set_audio_mode); static int ps3av_set_videomode(void) @@ -501,7 +500,7 @@ static void ps3av_set_videomode_packet(u32 id) video_mode = &video_mode_table[id & PS3AV_MODE_MASK]; - avb_param.num_of_video_pkt = PS3AV_AVB_NUM_VIDEO; /* num of head */ + avb_param.num_of_video_pkt = PS3AV_AVB_NUM_VIDEO; /* num of head */ avb_param.num_of_audio_pkt = 0; avb_param.num_of_av_video_pkt = ps3av->av_hw_conf.num_of_hdmi + ps3av->av_hw_conf.num_of_avmulti; @@ -521,7 +520,7 @@ static void ps3av_set_videomode_packet(u32 id) #ifndef PS3AV_HDMI_YUV if (ps3av->av_port[i] == PS3AV_CMD_AVPORT_HDMI_0 || ps3av->av_port[i] == PS3AV_CMD_AVPORT_HDMI_1) - av_video_cs = RGB8; /* use RGB for HDMI */ + av_video_cs = RGB8; /* use RGB for HDMI */ #endif len += ps3av_cmd_set_av_video_cs(&avb_param.buf[len], ps3av->av_port[i], @@ -590,8 +589,8 @@ static void ps3avd(struct work_struct *work) #define SHIFT_VESA 8 static const struct { - unsigned mask : 19; - unsigned id : 4; + unsigned mask:19; + unsigned id:4; } ps3av_preferred_modes[] = { { PS3AV_RESBIT_WUXGA << SHIFT_VESA, PS3AV_MODE_WUXGA }, { PS3AV_RESBIT_1920x1080P << SHIFT_60, PS3AV_MODE_1080P60 }, @@ -667,7 +666,8 @@ static enum ps3av_mode_num ps3av_hdmi_get_id(struct ps3av_info_monitor *info) return id; } -static void ps3av_monitor_info_dump(const struct ps3av_pkt_av_get_monitor_info *monitor_info) +static void ps3av_monitor_info_dump( + const struct ps3av_pkt_av_get_monitor_info *monitor_info) { const struct ps3av_info_monitor *info = &monitor_info->info; const struct ps3av_info_audio *audio = info->audio; @@ -717,8 +717,8 @@ static void ps3av_monitor_info_dump(const struct ps3av_pkt_av_get_monitor_info * /* audio block */ for (i = 0; i < info->num_of_audio_block; i++) { - pr_debug("audio[%d] type: %02x max_ch: %02x fs: %02x sbit: " - "%02x\n", + pr_debug( + "audio[%d] type: %02x max_ch: %02x fs: %02x sbit: %02x\n", i, audio->type, audio->max_num_of_ch, audio->fs, audio->sbit); audio++; @@ -870,21 +870,18 @@ int ps3av_set_video_mode(int id) return 0; } - EXPORT_SYMBOL_GPL(ps3av_set_video_mode); int ps3av_get_auto_mode(void) { return ps3av_auto_videomode(&ps3av->av_hw_conf); } - EXPORT_SYMBOL_GPL(ps3av_get_auto_mode); int ps3av_get_mode(void) { return ps3av ? ps3av->ps3av_mode : 0; } - EXPORT_SYMBOL_GPL(ps3av_get_mode); /* get resolution by video_mode */ @@ -902,7 +899,6 @@ int ps3av_video_mode2res(u32 id, u32 *xres, u32 *yres) *yres = video_mode_table[id].y; return 0; } - EXPORT_SYMBOL_GPL(ps3av_video_mode2res); /* mute */ @@ -911,7 +907,6 @@ int ps3av_video_mute(int mute) return ps3av_set_av_video_mute(mute ? PS3AV_CMD_MUTE_ON : PS3AV_CMD_MUTE_OFF); } - EXPORT_SYMBOL_GPL(ps3av_video_mute); /* mute analog output only */ @@ -935,7 +930,6 @@ int ps3av_audio_mute(int mute) return ps3av_set_audio_mute(mute ? PS3AV_CMD_MUTE_ON : PS3AV_CMD_MUTE_OFF); } - EXPORT_SYMBOL_GPL(ps3av_audio_mute); static int __devinit ps3av_probe(struct ps3_system_bus_device *dev) diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig index bea04e5d3b51..e9559782d3ec 100644 --- a/drivers/scsi/Kconfig +++ b/drivers/scsi/Kconfig @@ -808,19 +808,6 @@ config SCSI_FUTURE_DOMAIN To compile this driver as a module, choose M here: the module will be called fdomain. -config SCSI_FD_MCS - tristate "Future Domain MCS-600/700 SCSI support" - depends on MCA_LEGACY && SCSI - ---help--- - This is support for Future Domain MCS 600/700 MCA SCSI adapters. - Some PS/2 computers are equipped with IBM Fast SCSI Adapter/A which - is identical to the MCS 700 and hence also supported by this driver. - This driver also supports the Reply SB16/SCSI card (the SCSI part). - It supports multiple adapters in the same system. - - To compile this driver as a module, choose M here: the - module will be called fd_mcs. - config SCSI_GDTH tristate "Intel/ICP (former GDT SCSI Disk Array) RAID Controller support" depends on (ISA || EISA || PCI) && SCSI && ISA_DMA_API @@ -890,76 +877,6 @@ config SCSI_GENERIC_NCR53C400 not detect your card. See the file <file:Documentation/scsi/g_NCR5380.txt> for details. -config SCSI_IBMMCA - tristate "IBMMCA SCSI support" - depends on MCA && SCSI - ---help--- - This is support for the IBM SCSI adapter found in many of the PS/2 - series computers. These machines have an MCA bus, so you need to - answer Y to "MCA support" as well and read - <file:Documentation/mca.txt>. - - If the adapter isn't found during boot (a common problem for models - 56, 57, 76, and 77) you'll need to use the 'ibmmcascsi=<pun>' kernel - option, where <pun> is the id of the SCSI subsystem (usually 7, but - if that doesn't work check your reference diskette). Owners of - model 95 with a LED-matrix-display can in addition activate some - activity info like under OS/2, but more informative, by setting - 'ibmmcascsi=display' as an additional kernel parameter. Try "man - bootparam" or see the documentation of your boot loader about how to - pass options to the kernel. - - To compile this driver as a module, choose M here: the - module will be called ibmmca. - -config IBMMCA_SCSI_ORDER_STANDARD - bool "Standard SCSI-order" - depends on SCSI_IBMMCA - ---help--- - In the PC-world and in most modern SCSI-BIOS-setups, SCSI-hard disks - are assigned to the drive letters, starting with the lowest SCSI-id - (physical number -- pun) to be drive C:, as seen from DOS and - similar operating systems. When looking into papers describing the - ANSI-SCSI-standard, this assignment of drives appears to be wrong. - The SCSI-standard follows a hardware-hierarchy which says that id 7 - has the highest priority and id 0 the lowest. Therefore, the host - adapters are still today everywhere placed as SCSI-id 7 by default. - In the SCSI-standard, the drive letters express the priority of the - disk. C: should be the hard disk, or a partition on it, with the - highest priority. This must therefore be the disk with the highest - SCSI-id (e.g. 6) and not the one with the lowest! IBM-BIOS kept the - original definition of the SCSI-standard as also industrial- and - process-control-machines, like VME-CPUs running under realtime-OSes - (e.g. LynxOS, OS9) do. - - If you like to run Linux on your MCA-machine with the same - assignment of hard disks as seen from e.g. DOS or OS/2 on your - machine, which is in addition conformant to the SCSI-standard, you - must say Y here. This is also necessary for MCA-Linux users who want - to keep downward compatibility to older releases of the - IBM-MCA-SCSI-driver (older than driver-release 2.00 and older than - June 1997). - - If you like to have the lowest SCSI-id assigned as drive C:, as - modern SCSI-BIOSes do, which does not conform to the standard, but - is widespread and common in the PC-world of today, you must say N - here. If unsure, say Y. - -config IBMMCA_SCSI_DEV_RESET - bool "Reset SCSI-devices at boottime" - depends on SCSI_IBMMCA - ---help--- - By default, SCSI-devices are reset when the machine is powered on. - However, some devices exist, like special-control-devices, - SCSI-CNC-machines, SCSI-printer or scanners of older type, that do - not reset when switched on. If you say Y here, each device connected - to your SCSI-bus will be issued a reset-command after it has been - probed, while the kernel is booting. This may cause problems with - more modern devices, like hard disks, which do not appreciate these - reset commands, and can cause your system to hang. So say Y only if - you know that one of your older devices needs it; N is the safe - answer. - config SCSI_IPS tristate "IBM ServeRAID support" depends on PCI && SCSI diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile index 8deedeaf5608..1a3368b08615 100644 --- a/drivers/scsi/Makefile +++ b/drivers/scsi/Makefile @@ -75,7 +75,6 @@ obj-$(CONFIG_SCSI_AIC94XX) += aic94xx/ obj-$(CONFIG_SCSI_PM8001) += pm8001/ obj-$(CONFIG_SCSI_ISCI) += isci/ obj-$(CONFIG_SCSI_IPS) += ips.o -obj-$(CONFIG_SCSI_FD_MCS) += fd_mcs.o obj-$(CONFIG_SCSI_FUTURE_DOMAIN)+= fdomain.o obj-$(CONFIG_SCSI_IN2000) += in2000.o obj-$(CONFIG_SCSI_GENERIC_NCR5380) += g_NCR5380.o @@ -100,7 +99,6 @@ obj-$(CONFIG_SCSI_SYM53C8XX_2) += sym53c8xx_2/ obj-$(CONFIG_SCSI_ZALON) += zalon7xx.o obj-$(CONFIG_SCSI_EATA_PIO) += eata_pio.o obj-$(CONFIG_SCSI_7000FASST) += wd7000.o -obj-$(CONFIG_SCSI_IBMMCA) += ibmmca.o obj-$(CONFIG_SCSI_EATA) += eata.o obj-$(CONFIG_SCSI_DC395x) += dc395x.o obj-$(CONFIG_SCSI_DC390T) += tmscsim.o diff --git a/drivers/scsi/aha1542.c b/drivers/scsi/aha1542.c index ede91f378000..f79c8f9e33a4 100644 --- a/drivers/scsi/aha1542.c +++ b/drivers/scsi/aha1542.c @@ -22,7 +22,7 @@ * Added module command-line options * 19-Jul-99 * Modified by Adam Fritzler - * Added proper detection of the AHA-1640 (MCA version of AHA-1540) + * Added proper detection of the AHA-1640 (MCA, now deleted) */ #include <linux/module.h> @@ -37,8 +37,6 @@ #include <linux/spinlock.h> #include <linux/isapnp.h> #include <linux/blkdev.h> -#include <linux/mca.h> -#include <linux/mca-legacy.h> #include <linux/slab.h> #include <asm/dma.h> @@ -71,7 +69,7 @@ #define MAXBOARDS 4 /* Increase this and the sizes of the arrays below, if you need more.. */ -/* Boards 3,4 slots are reserved for ISAPnP/MCA scans */ +/* Boards 3,4 slots are reserved for ISAPnP scans */ static unsigned int bases[MAXBOARDS] __initdata = {0x330, 0x334, 0, 0}; @@ -1009,66 +1007,6 @@ static int __init aha1542_detect(struct scsi_host_template * tpnt) #endif /* - * Find MicroChannel cards (AHA1640) - */ -#ifdef CONFIG_MCA_LEGACY - if(MCA_bus) { - int slot = 0; - int pos = 0; - - for (indx = 0; (slot != MCA_NOTFOUND) && (indx < ARRAY_SIZE(bases)); indx++) { - - if (bases[indx]) - continue; - - /* Detect only AHA-1640 cards -- MCA ID 0F1F */ - slot = mca_find_unused_adapter(0x0f1f, slot); - if (slot == MCA_NOTFOUND) - break; - - /* Found one */ - pos = mca_read_stored_pos(slot, 3); - - /* Decode address */ - if (pos & 0x80) { - if (pos & 0x02) { - if (pos & 0x01) - bases[indx] = 0x334; - else - bases[indx] = 0x234; - } else { - if (pos & 0x01) - bases[indx] = 0x134; - } - } else { - if (pos & 0x02) { - if (pos & 0x01) - bases[indx] = 0x330; - else - bases[indx] = 0x230; - } else { - if (pos & 0x01) - bases[indx] = 0x130; - } - } - - /* No need to decode IRQ and Arb level -- those are - * read off the card later. - */ - printk(KERN_INFO "Found an AHA-1640 in MCA slot %d, I/O 0x%04x\n", slot, bases[indx]); - - mca_set_adapter_name(slot, "Adapter AHA-1640"); - mca_set_adapter_procfn(slot, NULL, NULL); - mca_mark_as_used(slot); - - /* Go on */ - slot++; - } - - } -#endif - - /* * Hunt for ISA Plug'n'Pray Adaptecs (AHA1535) */ diff --git a/drivers/scsi/fd_mcs.c b/drivers/scsi/fd_mcs.c deleted file mode 100644 index 53bfcaa86f09..000000000000 --- a/drivers/scsi/fd_mcs.c +++ /dev/null @@ -1,1354 +0,0 @@ -/* fd_mcs.c -- Future Domain MCS 600/700 (or IBM OEM) driver - * - * FutureDomain MCS-600/700 v0.2 03/11/1998 by ZP Gu ([email protected]) - * - * This driver is cloned from fdomain.* to specifically support - * the Future Domain MCS 600/700 MCA SCSI adapters. Some PS/2s - * also equipped with IBM Fast SCSI Adapter/A which is an OEM - * of MCS 700. - * - * This driver also supports Reply SB16/SCSI card (the SCSI part). - * - * What makes this driver different is that this driver is MCA only - * and it supports multiple adapters in the same system, IRQ - * sharing, some driver statistics, and maps highest SCSI id to sda. - * All cards are auto-detected. - * - * Assumptions: TMC-1800/18C50/18C30, BIOS >= 3.4 - * - * LILO command-line options: - * fd_mcs=<FIFO_COUNT>[,<FIFO_SIZE>] - * - * ******************************************************** - * Please see Copyrights/Comments in fdomain.* for credits. - * Following is from fdomain.c for acknowledgement: - * - * Created: Sun May 3 18:53:19 1992 by [email protected] - * Revised: Wed Oct 2 11:10:55 1996 by [email protected] - * Author: Rickard E. Faith, [email protected] - * Copyright 1992, 1993, 1994, 1995, 1996 Rickard E. Faith - * - * $Id: fdomain.c,v 5.45 1996/10/02 15:13:06 root Exp $ - - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2, or (at your option) any - * later version. - - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 675 Mass Ave, Cambridge, MA 02139, USA. - - ************************************************************************** - - NOTES ON USER DEFINABLE OPTIONS: - - DEBUG: This turns on the printing of various debug information. - - ENABLE_PARITY: This turns on SCSI parity checking. With the current - driver, all attached devices must support SCSI parity. If none of your - devices support parity, then you can probably get the driver to work by - turning this option off. I have no way of testing this, however, and it - would appear that no one ever uses this option. - - FIFO_COUNT: The host adapter has an 8K cache (host adapters based on the - 18C30 chip have a 2k cache). When this many 512 byte blocks are filled by - the SCSI device, an interrupt will be raised. Therefore, this could be as - low as 0, or as high as 16. Note, however, that values which are too high - or too low seem to prevent any interrupts from occurring, and thereby lock - up the machine. I have found that 2 is a good number, but throughput may - be increased by changing this value to values which are close to 2. - Please let me know if you try any different values. - [*****Now a runtime option*****] - - RESELECTION: This is no longer an option, since I gave up trying to - implement it in version 4.x of this driver. It did not improve - performance at all and made the driver unstable (because I never found one - of the two race conditions which were introduced by the multiple - outstanding command code). The instability seems a very high price to pay - just so that you don't have to wait for the tape to rewind. If you want - this feature implemented, send me patches. I'll be happy to send a copy - of my (broken) driver to anyone who would like to see a copy. - - **************************************************************************/ - -#include <linux/module.h> -#include <linux/init.h> -#include <linux/interrupt.h> -#include <linux/blkdev.h> -#include <linux/errno.h> -#include <linux/string.h> -#include <linux/ioport.h> -#include <linux/proc_fs.h> -#include <linux/delay.h> -#include <linux/mca.h> -#include <linux/spinlock.h> -#include <linux/slab.h> -#include <scsi/scsicam.h> -#include <linux/mca-legacy.h> - -#include <asm/io.h> - -#include "scsi.h" -#include <scsi/scsi_host.h> - -#define DRIVER_VERSION "v0.2 by ZP Gu<[email protected]>" - -/* START OF USER DEFINABLE OPTIONS */ - -#define DEBUG 0 /* Enable debugging output */ -#define ENABLE_PARITY 1 /* Enable SCSI Parity */ - -/* END OF USER DEFINABLE OPTIONS */ - -#if DEBUG -#define EVERY_ACCESS 0 /* Write a line on every scsi access */ -#define ERRORS_ONLY 1 /* Only write a line if there is an error */ -#define DEBUG_MESSAGES 1 /* Debug MESSAGE IN phase */ -#define DEBUG_ABORT 1 /* Debug abort() routine */ -#define DEBUG_RESET 1 /* Debug reset() routine */ -#define DEBUG_RACE 1 /* Debug interrupt-driven race condition */ -#else -#define EVERY_ACCESS 0 /* LEAVE THESE ALONE--CHANGE THE ONES ABOVE */ -#define ERRORS_ONLY 0 -#define DEBUG_MESSAGES 0 -#define DEBUG_ABORT 0 -#define DEBUG_RESET 0 -#define DEBUG_RACE 0 -#endif - -/* Errors are reported on the line, so we don't need to report them again */ -#if EVERY_ACCESS -#undef ERRORS_ONLY -#define ERRORS_ONLY 0 -#endif - -#if ENABLE_PARITY -#define PARITY_MASK 0x08 -#else -#define PARITY_MASK 0x00 -#endif - -enum chip_type { - unknown = 0x00, - tmc1800 = 0x01, - tmc18c50 = 0x02, - tmc18c30 = 0x03, -}; - -enum { - in_arbitration = 0x02, - in_selection = 0x04, - in_other = 0x08, - disconnect = 0x10, - aborted = 0x20, - sent_ident = 0x40, -}; - -enum in_port_type { - Read_SCSI_Data = 0, - SCSI_Status = 1, - TMC_Status = 2, - FIFO_Status = 3, /* tmc18c50/tmc18c30 only */ - Interrupt_Cond = 4, /* tmc18c50/tmc18c30 only */ - LSB_ID_Code = 5, - MSB_ID_Code = 6, - Read_Loopback = 7, - SCSI_Data_NoACK = 8, - Interrupt_Status = 9, - Configuration1 = 10, - Configuration2 = 11, /* tmc18c50/tmc18c30 only */ - Read_FIFO = 12, - FIFO_Data_Count = 14 -}; - -enum out_port_type { - Write_SCSI_Data = 0, - SCSI_Cntl = 1, - Interrupt_Cntl = 2, - SCSI_Mode_Cntl = 3, - TMC_Cntl = 4, - Memory_Cntl = 5, /* tmc18c50/tmc18c30 only */ - Write_Loopback = 7, - IO_Control = 11, /* tmc18c30 only */ - Write_FIFO = 12 -}; - -struct fd_hostdata { - unsigned long _bios_base; - int _bios_major; - int _bios_minor; - volatile int _in_command; - Scsi_Cmnd *_current_SC; - enum chip_type _chip; - int _adapter_mask; - int _fifo_count; /* Number of 512 byte blocks before INTR */ - - char _adapter_name[64]; -#if DEBUG_RACE - volatile int _in_interrupt_flag; -#endif - - int _SCSI_Mode_Cntl_port; - int _FIFO_Data_Count_port; - int _Interrupt_Cntl_port; - int _Interrupt_Status_port; - int _Interrupt_Cond_port; - int _Read_FIFO_port; - int _Read_SCSI_Data_port; - int _SCSI_Cntl_port; - int _SCSI_Data_NoACK_port; - int _SCSI_Status_port; - int _TMC_Cntl_port; - int _TMC_Status_port; - int _Write_FIFO_port; - int _Write_SCSI_Data_port; - - int _FIFO_Size; /* = 0x2000; 8k FIFO for - pre-tmc18c30 chips */ - /* simple stats */ - int _Bytes_Read; - int _Bytes_Written; - int _INTR_Processed; -}; - -#define FD_MAX_HOSTS 3 /* enough? */ - -#define HOSTDATA(shpnt) ((struct fd_hostdata *) shpnt->hostdata) -#define bios_base (HOSTDATA(shpnt)->_bios_base) -#define bios_major (HOSTDATA(shpnt)->_bios_major) -#define bios_minor (HOSTDATA(shpnt)->_bios_minor) -#define in_command (HOSTDATA(shpnt)->_in_command) -#define current_SC (HOSTDATA(shpnt)->_current_SC) -#define chip (HOSTDATA(shpnt)->_chip) -#define adapter_mask (HOSTDATA(shpnt)->_adapter_mask) -#define FIFO_COUNT (HOSTDATA(shpnt)->_fifo_count) -#define adapter_name (HOSTDATA(shpnt)->_adapter_name) -#if DEBUG_RACE -#define in_interrupt_flag (HOSTDATA(shpnt)->_in_interrupt_flag) -#endif -#define SCSI_Mode_Cntl_port (HOSTDATA(shpnt)->_SCSI_Mode_Cntl_port) -#define FIFO_Data_Count_port (HOSTDATA(shpnt)->_FIFO_Data_Count_port) -#define Interrupt_Cntl_port (HOSTDATA(shpnt)->_Interrupt_Cntl_port) -#define Interrupt_Status_port (HOSTDATA(shpnt)->_Interrupt_Status_port) -#define Interrupt_Cond_port (HOSTDATA(shpnt)->_Interrupt_Cond_port) -#define Read_FIFO_port (HOSTDATA(shpnt)->_Read_FIFO_port) -#define Read_SCSI_Data_port (HOSTDATA(shpnt)->_Read_SCSI_Data_port) -#define SCSI_Cntl_port (HOSTDATA(shpnt)->_SCSI_Cntl_port) -#define SCSI_Data_NoACK_port (HOSTDATA(shpnt)->_SCSI_Data_NoACK_port) -#define SCSI_Status_port (HOSTDATA(shpnt)->_SCSI_Status_port) -#define TMC_Cntl_port (HOSTDATA(shpnt)->_TMC_Cntl_port) -#define TMC_Status_port (HOSTDATA(shpnt)->_TMC_Status_port) -#define Write_FIFO_port (HOSTDATA(shpnt)->_Write_FIFO_port) -#define Write_SCSI_Data_port (HOSTDATA(shpnt)->_Write_SCSI_Data_port) -#define FIFO_Size (HOSTDATA(shpnt)->_FIFO_Size) -#define Bytes_Read (HOSTDATA(shpnt)->_Bytes_Read) -#define Bytes_Written (HOSTDATA(shpnt)->_Bytes_Written) -#define INTR_Processed (HOSTDATA(shpnt)->_INTR_Processed) - -struct fd_mcs_adapters_struct { - char *name; - int id; - enum chip_type fd_chip; - int fifo_size; - int fifo_count; -}; - -#define REPLY_ID 0x5137 - -static struct fd_mcs_adapters_struct fd_mcs_adapters[] = { - {"Future Domain SCSI Adapter MCS-700(18C50)", - 0x60e9, - tmc18c50, - 0x2000, - 4}, - {"Future Domain SCSI Adapter MCS-600/700(TMC-1800)", - 0x6127, - tmc1800, - 0x2000, - 4}, - {"Reply Sound Blaster/SCSI Adapter", - REPLY_ID, - tmc18c30, - 0x800, - 2}, -}; - -#define FD_BRDS ARRAY_SIZE(fd_mcs_adapters) - -static irqreturn_t fd_mcs_intr(int irq, void *dev_id); - -static unsigned long addresses[] = { 0xc8000, 0xca000, 0xce000, 0xde000 }; -static unsigned short ports[] = { 0x140, 0x150, 0x160, 0x170 }; -static unsigned short interrupts[] = { 3, 5, 10, 11, 12, 14, 15, 0 }; - -/* host information */ -static int found = 0; -static struct Scsi_Host *hosts[FD_MAX_HOSTS + 1] = { NULL }; - -static int user_fifo_count = 0; -static int user_fifo_size = 0; - -#ifndef MODULE -static int __init fd_mcs_setup(char *str) -{ - static int done_setup = 0; - int ints[3]; - - get_options(str, 3, ints); - if (done_setup++ || ints[0] < 1 || ints[0] > 2 || ints[1] < 1 || ints[1] > 16) { - printk("fd_mcs: usage: fd_mcs=FIFO_COUNT, FIFO_SIZE\n"); - return 0; - } - - user_fifo_count = ints[0] >= 1 ? ints[1] : 0; - user_fifo_size = ints[0] >= 2 ? ints[2] : 0; - return 1; -} - -__setup("fd_mcs=", fd_mcs_setup); -#endif /* !MODULE */ - -static void print_banner(struct Scsi_Host *shpnt) -{ - printk("scsi%d <fd_mcs>: ", shpnt->host_no); - - if (bios_base) { - printk("BIOS at 0x%lX", bios_base); - } else { - printk("No BIOS"); - } - - printk(", HostID %d, %s Chip, IRQ %d, IO 0x%lX\n", shpnt->this_id, chip == tmc18c50 ? "TMC-18C50" : (chip == tmc18c30 ? "TMC-18C30" : (chip == tmc1800 ? "TMC-1800" : "Unknown")), shpnt->irq, shpnt->io_port); -} - - -static void do_pause(unsigned amount) -{ /* Pause for amount*10 milliseconds */ - do { - mdelay(10); - } while (--amount); -} - -static void fd_mcs_make_bus_idle(struct Scsi_Host *shpnt) -{ - outb(0, SCSI_Cntl_port); - outb(0, SCSI_Mode_Cntl_port); - if (chip == tmc18c50 || chip == tmc18c30) - outb(0x21 | PARITY_MASK, TMC_Cntl_port); /* Clear forced intr. */ - else - outb(0x01 | PARITY_MASK, TMC_Cntl_port); -} - -static int fd_mcs_detect(struct scsi_host_template * tpnt) -{ - int loop; - struct Scsi_Host *shpnt; - - /* get id, port, bios, irq */ - int slot; - u_char pos2, pos3, pos4; - int id, port, irq; - unsigned long bios; - - /* if not MCA machine, return */ - if (!MCA_bus) - return 0; - - /* changeable? */ - id = 7; - - for (loop = 0; loop < FD_BRDS; loop++) { - slot = 0; - while (MCA_NOTFOUND != (slot = mca_find_adapter(fd_mcs_adapters[loop].id, slot))) { - - /* if we get this far, an adapter has been detected and is - enabled */ - - printk(KERN_INFO "scsi <fd_mcs>: %s at slot %d\n", fd_mcs_adapters[loop].name, slot + 1); - - pos2 = mca_read_stored_pos(slot, 2); - pos3 = mca_read_stored_pos(slot, 3); - pos4 = mca_read_stored_pos(slot, 4); - - /* ready for next probe */ - slot++; - - if (fd_mcs_adapters[loop].id == REPLY_ID) { /* reply card */ - static int reply_irq[] = { 10, 11, 14, 15 }; - - bios = 0; /* no bios */ - - if (pos2 & 0x2) - port = ports[pos4 & 0x3]; - else - continue; - - /* can't really disable it, same as irq=10 */ - irq = reply_irq[((pos4 >> 2) & 0x1) + 2 * ((pos4 >> 4) & 0x1)]; - } else { - bios = addresses[pos2 >> 6]; - port = ports[(pos2 >> 4) & 0x03]; - irq = interrupts[(pos2 >> 1) & 0x07]; - } - - if (irq) { - /* claim the slot */ - mca_set_adapter_name(slot - 1, fd_mcs_adapters[loop].name); - - /* check irq/region */ - if (request_irq(irq, fd_mcs_intr, IRQF_SHARED, "fd_mcs", hosts)) { - printk(KERN_ERR "fd_mcs: interrupt is not available, skipping...\n"); - continue; - } - - /* request I/O region */ - if (request_region(port, 0x10, "fd_mcs")) { - printk(KERN_ERR "fd_mcs: I/O region is already in use, skipping...\n"); - continue; - } - /* register */ - if (!(shpnt = scsi_register(tpnt, sizeof(struct fd_hostdata)))) { - printk(KERN_ERR "fd_mcs: scsi_register() failed\n"); - release_region(port, 0x10); - free_irq(irq, hosts); - continue; - } - - - /* save name */ - strcpy(adapter_name, fd_mcs_adapters[loop].name); - - /* chip/fifo */ - chip = fd_mcs_adapters[loop].fd_chip; - /* use boot time value if available */ - FIFO_COUNT = user_fifo_count ? user_fifo_count : fd_mcs_adapters[loop].fifo_count; - FIFO_Size = user_fifo_size ? user_fifo_size : fd_mcs_adapters[loop].fifo_size; - -/* FIXME: Do we need to keep this bit of code inside NOT_USED around at all? */ -#ifdef NOT_USED - /* *************************************************** */ - /* Try to toggle 32-bit mode. This only - works on an 18c30 chip. (User reports - say this works, so we should switch to - it in the near future.) */ - outb(0x80, port + IO_Control); - if ((inb(port + Configuration2) & 0x80) == 0x80) { - outb(0x00, port + IO_Control); - if ((inb(port + Configuration2) & 0x80) == 0x00) { - chip = tmc18c30; - FIFO_Size = 0x800; /* 2k FIFO */ - - printk("FIRST: chip=%s, fifo_size=0x%x\n", (chip == tmc18c30) ? "tmc18c30" : "tmc18c50", FIFO_Size); - } - } - - /* That should have worked, but appears to - have problems. Let's assume it is an - 18c30 if the RAM is disabled. */ - - if (inb(port + Configuration2) & 0x02) { - chip = tmc18c30; - FIFO_Size = 0x800; /* 2k FIFO */ - - printk("SECOND: chip=%s, fifo_size=0x%x\n", (chip == tmc18c30) ? "tmc18c30" : "tmc18c50", FIFO_Size); - } - /* *************************************************** */ -#endif - - /* IBM/ANSI scsi scan ordering */ - /* Stick this back in when the scsi.c changes are there */ - shpnt->reverse_ordering = 1; - - - /* saving info */ - hosts[found++] = shpnt; - - shpnt->this_id = id; - shpnt->irq = irq; - shpnt->io_port = port; - shpnt->n_io_port = 0x10; - - /* save */ - bios_base = bios; - adapter_mask = (1 << id); - - /* save more */ - SCSI_Mode_Cntl_port = port + SCSI_Mode_Cntl; - FIFO_Data_Count_port = port + FIFO_Data_Count; - Interrupt_Cntl_port = port + Interrupt_Cntl; - Interrupt_Status_port = port + Interrupt_Status; - Interrupt_Cond_port = port + Interrupt_Cond; - Read_FIFO_port = port + Read_FIFO; - Read_SCSI_Data_port = port + Read_SCSI_Data; - SCSI_Cntl_port = port + SCSI_Cntl; - SCSI_Data_NoACK_port = port + SCSI_Data_NoACK; - SCSI_Status_port = port + SCSI_Status; - TMC_Cntl_port = port + TMC_Cntl; - TMC_Status_port = port + TMC_Status; - Write_FIFO_port = port + Write_FIFO; - Write_SCSI_Data_port = port + Write_SCSI_Data; - - Bytes_Read = 0; - Bytes_Written = 0; - INTR_Processed = 0; - - /* say something */ - print_banner(shpnt); - - /* reset */ - outb(1, SCSI_Cntl_port); - do_pause(2); - outb(0, SCSI_Cntl_port); - do_pause(115); - outb(0, SCSI_Mode_Cntl_port); - outb(PARITY_MASK, TMC_Cntl_port); - /* done reset */ - } - } - - if (found == FD_MAX_HOSTS) { - printk("fd_mcs: detecting reached max=%d host adapters.\n", FD_MAX_HOSTS); - break; - } - } - - return found; -} - -static const char *fd_mcs_info(struct Scsi_Host *shpnt) -{ - return adapter_name; -} - -static int TOTAL_INTR = 0; - -/* - * inout : decides on the direction of the dataflow and the meaning of the - * variables - * buffer: If inout==FALSE data is being written to it else read from it - * *start: If inout==FALSE start of the valid data in the buffer - * offset: If inout==FALSE offset from the beginning of the imaginary file - * from which we start writing into the buffer - * length: If inout==FALSE max number of bytes to be written into the buffer - * else number of bytes in the buffer - */ -static int fd_mcs_proc_info(struct Scsi_Host *shpnt, char *buffer, char **start, off_t offset, int length, int inout) -{ - int len = 0; - - if (inout) - return (-ENOSYS); - - *start = buffer + offset; - - len += sprintf(buffer + len, "Future Domain MCS-600/700 Driver %s\n", DRIVER_VERSION); - len += sprintf(buffer + len, "HOST #%d: %s\n", shpnt->host_no, adapter_name); - len += sprintf(buffer + len, "FIFO Size=0x%x, FIFO Count=%d\n", FIFO_Size, FIFO_COUNT); - len += sprintf(buffer + len, "DriverCalls=%d, Interrupts=%d, BytesRead=%d, BytesWrite=%d\n\n", TOTAL_INTR, INTR_Processed, Bytes_Read, Bytes_Written); - - if ((len -= offset) <= 0) - return 0; - if (len > length) - len = length; - return len; -} - -static int fd_mcs_select(struct Scsi_Host *shpnt, int target) -{ - int status; - unsigned long timeout; - - outb(0x82, SCSI_Cntl_port); /* Bus Enable + Select */ - outb(adapter_mask | (1 << target), SCSI_Data_NoACK_port); - - /* Stop arbitration and enable parity */ - outb(PARITY_MASK, TMC_Cntl_port); - - timeout = 350; /* 350mS -- because of timeouts - (was 250mS) */ - - do { - status = inb(SCSI_Status_port); /* Read adapter status */ - if (status & 1) { /* Busy asserted */ - /* Enable SCSI Bus (on error, should make bus idle with 0) */ - outb(0x80, SCSI_Cntl_port); - return 0; - } - udelay(1000); /* wait one msec */ - } while (--timeout); - - /* Make bus idle */ - fd_mcs_make_bus_idle(shpnt); -#if EVERY_ACCESS - if (!target) - printk("Selection failed\n"); -#endif -#if ERRORS_ONLY - if (!target) { - static int flag = 0; - - if (!flag) /* Skip first failure for all chips. */ - ++flag; - else - printk("fd_mcs: Selection failed\n"); - } -#endif - return 1; -} - -static void my_done(struct Scsi_Host *shpnt, int error) -{ - if (in_command) { - in_command = 0; - outb(0x00, Interrupt_Cntl_port); - fd_mcs_make_bus_idle(shpnt); - current_SC->result = error; - current_SC->scsi_done(current_SC); - } else { - panic("fd_mcs: my_done() called outside of command\n"); - } -#if DEBUG_RACE - in_interrupt_flag = 0; -#endif -} - -/* only my_done needs to be protected */ -static irqreturn_t fd_mcs_intr(int irq, void *dev_id) -{ - unsigned long flags; - int status; - int done = 0; - unsigned data_count, tmp_count; - - int i = 0; - struct Scsi_Host *shpnt; - - TOTAL_INTR++; - - /* search for one adapter-response on shared interrupt */ - while ((shpnt = hosts[i++])) { - if ((inb(TMC_Status_port)) & 1) - break; - } - - /* return if some other device on this IRQ caused the interrupt */ - if (!shpnt) { - return IRQ_NONE; - } - - INTR_Processed++; - - outb(0x00, Interrupt_Cntl_port); - - /* Abort calls my_done, so we do nothing here. */ - if (current_SC->SCp.phase & aborted) { -#if DEBUG_ABORT - printk("Interrupt after abort, ignoring\n"); -#endif - /* return IRQ_HANDLED; */ - } -#if DEBUG_RACE - ++in_interrupt_flag; -#endif - - if (current_SC->SCp.phase & in_arbitration) { - status = inb(TMC_Status_port); /* Read adapter status */ - if (!(status & 0x02)) { -#if EVERY_ACCESS - printk(" AFAIL "); -#endif - spin_lock_irqsave(shpnt->host_lock, flags); - my_done(shpnt, DID_BUS_BUSY << 16); - spin_unlock_irqrestore(shpnt->host_lock, flags); - return IRQ_HANDLED; - } - current_SC->SCp.phase = in_selection; - - outb(0x40 | FIFO_COUNT, Interrupt_Cntl_port); - - outb(0x82, SCSI_Cntl_port); /* Bus Enable + Select */ - outb(adapter_mask | (1 << scmd_id(current_SC)), SCSI_Data_NoACK_port); - - /* Stop arbitration and enable parity */ - outb(0x10 | PARITY_MASK, TMC_Cntl_port); -#if DEBUG_RACE - in_interrupt_flag = 0; -#endif - return IRQ_HANDLED; - } else if (current_SC->SCp.phase & in_selection) { - status = inb(SCSI_Status_port); - if (!(status & 0x01)) { - /* Try again, for slow devices */ - if (fd_mcs_select(shpnt, scmd_id(current_SC))) { -#if EVERY_ACCESS - printk(" SFAIL "); -#endif - spin_lock_irqsave(shpnt->host_lock, flags); - my_done(shpnt, DID_NO_CONNECT << 16); - spin_unlock_irqrestore(shpnt->host_lock, flags); - return IRQ_HANDLED; - } else { -#if EVERY_ACCESS - printk(" AltSel "); -#endif - /* Stop arbitration and enable parity */ - outb(0x10 | PARITY_MASK, TMC_Cntl_port); - } - } - current_SC->SCp.phase = in_other; - outb(0x90 | FIFO_COUNT, Interrupt_Cntl_port); - outb(0x80, SCSI_Cntl_port); -#if DEBUG_RACE - in_interrupt_flag = 0; -#endif - return IRQ_HANDLED; - } - - /* current_SC->SCp.phase == in_other: this is the body of the routine */ - - status = inb(SCSI_Status_port); - - if (status & 0x10) { /* REQ */ - - switch (status & 0x0e) { - - case 0x08: /* COMMAND OUT */ - outb(current_SC->cmnd[current_SC->SCp.sent_command++], Write_SCSI_Data_port); -#if EVERY_ACCESS - printk("CMD = %x,", current_SC->cmnd[current_SC->SCp.sent_command - 1]); -#endif - break; - case 0x00: /* DATA OUT -- tmc18c50/tmc18c30 only */ - if (chip != tmc1800 && !current_SC->SCp.have_data_in) { - current_SC->SCp.have_data_in = -1; - outb(0xd0 | PARITY_MASK, TMC_Cntl_port); - } - break; - case 0x04: /* DATA IN -- tmc18c50/tmc18c30 only */ - if (chip != tmc1800 && !current_SC->SCp.have_data_in) { - current_SC->SCp.have_data_in = 1; - outb(0x90 | PARITY_MASK, TMC_Cntl_port); - } - break; - case 0x0c: /* STATUS IN */ - current_SC->SCp.Status = inb(Read_SCSI_Data_port); -#if EVERY_ACCESS - printk("Status = %x, ", current_SC->SCp.Status); -#endif -#if ERRORS_ONLY - if (current_SC->SCp.Status && current_SC->SCp.Status != 2 && current_SC->SCp.Status != 8) { - printk("ERROR fd_mcs: target = %d, command = %x, status = %x\n", current_SC->device->id, current_SC->cmnd[0], current_SC->SCp.Status); - } -#endif - break; - case 0x0a: /* MESSAGE OUT */ - outb(MESSAGE_REJECT, Write_SCSI_Data_port); /* Reject */ - break; - case 0x0e: /* MESSAGE IN */ - current_SC->SCp.Message = inb(Read_SCSI_Data_port); -#if EVERY_ACCESS - printk("Message = %x, ", current_SC->SCp.Message); -#endif - if (!current_SC->SCp.Message) - ++done; -#if DEBUG_MESSAGES || EVERY_ACCESS - if (current_SC->SCp.Message) { - printk("fd_mcs: message = %x\n", current_SC->SCp.Message); - } -#endif - break; - } - } - - if (chip == tmc1800 && !current_SC->SCp.have_data_in && (current_SC->SCp.sent_command >= current_SC->cmd_len)) { - /* We have to get the FIFO direction - correct, so I've made a table based - on the SCSI Standard of which commands - appear to require a DATA OUT phase. - */ - /* - p. 94: Command for all device types - CHANGE DEFINITION 40 DATA OUT - COMPARE 39 DATA OUT - COPY 18 DATA OUT - COPY AND VERIFY 3a DATA OUT - INQUIRY 12 - LOG SELECT 4c DATA OUT - LOG SENSE 4d - MODE SELECT (6) 15 DATA OUT - MODE SELECT (10) 55 DATA OUT - MODE SENSE (6) 1a - MODE SENSE (10) 5a - READ BUFFER 3c - RECEIVE DIAGNOSTIC RESULTS 1c - REQUEST SENSE 03 - SEND DIAGNOSTIC 1d DATA OUT - TEST UNIT READY 00 - WRITE BUFFER 3b DATA OUT - - p.178: Commands for direct-access devices (not listed on p. 94) - FORMAT UNIT 04 DATA OUT - LOCK-UNLOCK CACHE 36 - PRE-FETCH 34 - PREVENT-ALLOW MEDIUM REMOVAL 1e - READ (6)/RECEIVE 08 - READ (10) 3c - READ CAPACITY 25 - READ DEFECT DATA (10) 37 - READ LONG 3e - REASSIGN BLOCKS 07 DATA OUT - RELEASE 17 - RESERVE 16 DATA OUT - REZERO UNIT/REWIND 01 - SEARCH DATA EQUAL (10) 31 DATA OUT - SEARCH DATA HIGH (10) 30 DATA OUT - SEARCH DATA LOW (10) 32 DATA OUT - SEEK (6) 0b - SEEK (10) 2b - SET LIMITS (10) 33 - START STOP UNIT 1b - SYNCHRONIZE CACHE 35 - VERIFY (10) 2f - WRITE (6)/PRINT/SEND 0a DATA OUT - WRITE (10)/SEND 2a DATA OUT - WRITE AND VERIFY (10) 2e DATA OUT - WRITE LONG 3f DATA OUT - WRITE SAME 41 DATA OUT ? - - p. 261: Commands for sequential-access devices (not previously listed) - ERASE 19 - LOAD UNLOAD 1b - LOCATE 2b - READ BLOCK LIMITS 05 - READ POSITION 34 - READ REVERSE 0f - RECOVER BUFFERED DATA 14 - SPACE 11 - WRITE FILEMARKS 10 ? - - p. 298: Commands for printer devices (not previously listed) - ****** NOT SUPPORTED BY THIS DRIVER, since 0b is SEEK (6) ***** - SLEW AND PRINT 0b DATA OUT -- same as seek - STOP PRINT 1b - SYNCHRONIZE BUFFER 10 - - p. 315: Commands for processor devices (not previously listed) - - p. 321: Commands for write-once devices (not previously listed) - MEDIUM SCAN 38 - READ (12) a8 - SEARCH DATA EQUAL (12) b1 DATA OUT - SEARCH DATA HIGH (12) b0 DATA OUT - SEARCH DATA LOW (12) b2 DATA OUT - SET LIMITS (12) b3 - VERIFY (12) af - WRITE (12) aa DATA OUT - WRITE AND VERIFY (12) ae DATA OUT - - p. 332: Commands for CD-ROM devices (not previously listed) - PAUSE/RESUME 4b - PLAY AUDIO (10) 45 - PLAY AUDIO (12) a5 - PLAY AUDIO MSF 47 - PLAY TRACK RELATIVE (10) 49 - PLAY TRACK RELATIVE (12) a9 - READ HEADER 44 - READ SUB-CHANNEL 42 - READ TOC 43 - - p. 370: Commands for scanner devices (not previously listed) - GET DATA BUFFER STATUS 34 - GET WINDOW 25 - OBJECT POSITION 31 - SCAN 1b - SET WINDOW 24 DATA OUT - - p. 391: Commands for optical memory devices (not listed) - ERASE (10) 2c - ERASE (12) ac - MEDIUM SCAN 38 DATA OUT - READ DEFECT DATA (12) b7 - READ GENERATION 29 - READ UPDATED BLOCK 2d - UPDATE BLOCK 3d DATA OUT - - p. 419: Commands for medium changer devices (not listed) - EXCHANGE MEDIUM 46 - INITIALIZE ELEMENT STATUS 07 - MOVE MEDIUM a5 - POSITION TO ELEMENT 2b - READ ELEMENT STATUS b8 - REQUEST VOL. ELEMENT ADDRESS b5 - SEND VOLUME TAG b6 DATA OUT - - p. 454: Commands for communications devices (not listed previously) - GET MESSAGE (6) 08 - GET MESSAGE (10) 28 - GET MESSAGE (12) a8 - */ - - switch (current_SC->cmnd[0]) { - case CHANGE_DEFINITION: - case COMPARE: - case COPY: - case COPY_VERIFY: - case LOG_SELECT: - case MODE_SELECT: - case MODE_SELECT_10: - case SEND_DIAGNOSTIC: - case WRITE_BUFFER: - - case FORMAT_UNIT: - case REASSIGN_BLOCKS: - case RESERVE: - case SEARCH_EQUAL: - case SEARCH_HIGH: - case SEARCH_LOW: - case WRITE_6: - case WRITE_10: - case WRITE_VERIFY: - case 0x3f: - case 0x41: - - case 0xb1: - case 0xb0: - case 0xb2: - case 0xaa: - case 0xae: - - case 0x24: - - case 0x38: - case 0x3d: - - case 0xb6: - - case 0xea: /* alternate number for WRITE LONG */ - - current_SC->SCp.have_data_in = -1; - outb(0xd0 | PARITY_MASK, TMC_Cntl_port); - break; - - case 0x00: - default: - - current_SC->SCp.have_data_in = 1; - outb(0x90 | PARITY_MASK, TMC_Cntl_port); - break; - } - } - - if (current_SC->SCp.have_data_in == -1) { /* DATA OUT */ - while ((data_count = FIFO_Size - inw(FIFO_Data_Count_port)) > 512) { -#if EVERY_ACCESS - printk("DC=%d, ", data_count); -#endif - if (data_count > current_SC->SCp.this_residual) - data_count = current_SC->SCp.this_residual; - if (data_count > 0) { -#if EVERY_ACCESS - printk("%d OUT, ", data_count); -#endif - if (data_count == 1) { - Bytes_Written++; - - outb(*current_SC->SCp.ptr++, Write_FIFO_port); - --current_SC->SCp.this_residual; - } else { - data_count >>= 1; - tmp_count = data_count << 1; - outsw(Write_FIFO_port, current_SC->SCp.ptr, data_count); - current_SC->SCp.ptr += tmp_count; - Bytes_Written += tmp_count; - current_SC->SCp.this_residual -= tmp_count; - } - } - if (!current_SC->SCp.this_residual) { - if (current_SC->SCp.buffers_residual) { - --current_SC->SCp.buffers_residual; - ++current_SC->SCp.buffer; - current_SC->SCp.ptr = sg_virt(current_SC->SCp.buffer); - current_SC->SCp.this_residual = current_SC->SCp.buffer->length; - } else - break; - } - } - } else if (current_SC->SCp.have_data_in == 1) { /* DATA IN */ - while ((data_count = inw(FIFO_Data_Count_port)) > 0) { -#if EVERY_ACCESS - printk("DC=%d, ", data_count); -#endif - if (data_count > current_SC->SCp.this_residual) - data_count = current_SC->SCp.this_residual; - if (data_count) { -#if EVERY_ACCESS - printk("%d IN, ", data_count); -#endif - if (data_count == 1) { - Bytes_Read++; - *current_SC->SCp.ptr++ = inb(Read_FIFO_port); - --current_SC->SCp.this_residual; - } else { - data_count >>= 1; /* Number of words */ - tmp_count = data_count << 1; - insw(Read_FIFO_port, current_SC->SCp.ptr, data_count); - current_SC->SCp.ptr += tmp_count; - Bytes_Read += tmp_count; - current_SC->SCp.this_residual -= tmp_count; - } - } - if (!current_SC->SCp.this_residual && current_SC->SCp.buffers_residual) { - --current_SC->SCp.buffers_residual; - ++current_SC->SCp.buffer; - current_SC->SCp.ptr = sg_virt(current_SC->SCp.buffer); - current_SC->SCp.this_residual = current_SC->SCp.buffer->length; - } - } - } - - if (done) { -#if EVERY_ACCESS - printk(" ** IN DONE %d ** ", current_SC->SCp.have_data_in); -#endif - -#if EVERY_ACCESS - printk("BEFORE MY_DONE. . ."); -#endif - spin_lock_irqsave(shpnt->host_lock, flags); - my_done(shpnt, (current_SC->SCp.Status & 0xff) - | ((current_SC->SCp.Message & 0xff) << 8) | (DID_OK << 16)); - spin_unlock_irqrestore(shpnt->host_lock, flags); -#if EVERY_ACCESS - printk("RETURNING.\n"); -#endif - - } else { - if (current_SC->SCp.phase & disconnect) { - outb(0xd0 | FIFO_COUNT, Interrupt_Cntl_port); - outb(0x00, SCSI_Cntl_port); - } else { - outb(0x90 | FIFO_COUNT, Interrupt_Cntl_port); - } - } -#if DEBUG_RACE - in_interrupt_flag = 0; -#endif - return IRQ_HANDLED; -} - -static int fd_mcs_release(struct Scsi_Host *shpnt) -{ - int i, this_host, irq_usage; - - release_region(shpnt->io_port, shpnt->n_io_port); - - this_host = -1; - irq_usage = 0; - for (i = 0; i < found; i++) { - if (shpnt == hosts[i]) - this_host = i; - if (shpnt->irq == hosts[i]->irq) - irq_usage++; - } - - /* only for the last one */ - if (1 == irq_usage) - free_irq(shpnt->irq, hosts); - - found--; - - for (i = this_host; i < found; i++) - hosts[i] = hosts[i + 1]; - - hosts[found] = NULL; - - return 0; -} - -static int fd_mcs_queue_lck(Scsi_Cmnd * SCpnt, void (*done) (Scsi_Cmnd *)) -{ - struct Scsi_Host *shpnt = SCpnt->device->host; - - if (in_command) { - panic("fd_mcs: fd_mcs_queue() NOT REENTRANT!\n"); - } -#if EVERY_ACCESS - printk("queue: target = %d cmnd = 0x%02x pieces = %d size = %u\n", - SCpnt->target, *(unsigned char *) SCpnt->cmnd, - scsi_sg_count(SCpnt), scsi_bufflen(SCpnt)); -#endif - - fd_mcs_make_bus_idle(shpnt); - - SCpnt->scsi_done = done; /* Save this for the done function */ - current_SC = SCpnt; - - /* Initialize static data */ - - if (scsi_bufflen(current_SC)) { - current_SC->SCp.buffer = scsi_sglist(current_SC); - current_SC->SCp.ptr = sg_virt(current_SC->SCp.buffer); - current_SC->SCp.this_residual = current_SC->SCp.buffer->length; - current_SC->SCp.buffers_residual = scsi_sg_count(current_SC) - 1; - } else { - current_SC->SCp.ptr = NULL; - current_SC->SCp.this_residual = 0; - current_SC->SCp.buffer = NULL; - current_SC->SCp.buffers_residual = 0; - } - - - current_SC->SCp.Status = 0; - current_SC->SCp.Message = 0; - current_SC->SCp.have_data_in = 0; - current_SC->SCp.sent_command = 0; - current_SC->SCp.phase = in_arbitration; - - /* Start arbitration */ - outb(0x00, Interrupt_Cntl_port); - outb(0x00, SCSI_Cntl_port); /* Disable data drivers */ - outb(adapter_mask, SCSI_Data_NoACK_port); /* Set our id bit */ - in_command = 1; - outb(0x20, Interrupt_Cntl_port); - outb(0x14 | PARITY_MASK, TMC_Cntl_port); /* Start arbitration */ - - return 0; -} - -static DEF_SCSI_QCMD(fd_mcs_queue) - -#if DEBUG_ABORT || DEBUG_RESET -static void fd_mcs_print_info(Scsi_Cmnd * SCpnt) -{ - unsigned int imr; - unsigned int irr; - unsigned int isr; - struct Scsi_Host *shpnt = SCpnt->host; - - if (!SCpnt || !SCpnt->host) { - printk("fd_mcs: cannot provide detailed information\n"); - } - - printk("%s\n", fd_mcs_info(SCpnt->host)); - print_banner(SCpnt->host); - switch (SCpnt->SCp.phase) { - case in_arbitration: - printk("arbitration "); - break; - case in_selection: - printk("selection "); - break; - case in_other: - printk("other "); - break; - default: - printk("unknown "); - break; - } - - printk("(%d), target = %d cmnd = 0x%02x pieces = %d size = %u\n", - SCpnt->SCp.phase, SCpnt->device->id, *(unsigned char *) SCpnt->cmnd, - scsi_sg_count(SCpnt), scsi_bufflen(SCpnt)); - printk("sent_command = %d, have_data_in = %d, timeout = %d\n", SCpnt->SCp.sent_command, SCpnt->SCp.have_data_in, SCpnt->timeout); -#if DEBUG_RACE - printk("in_interrupt_flag = %d\n", in_interrupt_flag); -#endif - - imr = (inb(0x0a1) << 8) + inb(0x21); - outb(0x0a, 0xa0); - irr = inb(0xa0) << 8; - outb(0x0a, 0x20); - irr += inb(0x20); - outb(0x0b, 0xa0); - isr = inb(0xa0) << 8; - outb(0x0b, 0x20); - isr += inb(0x20); - - /* Print out interesting information */ - printk("IMR = 0x%04x", imr); - if (imr & (1 << shpnt->irq)) - printk(" (masked)"); - printk(", IRR = 0x%04x, ISR = 0x%04x\n", irr, isr); - - printk("SCSI Status = 0x%02x\n", inb(SCSI_Status_port)); - printk("TMC Status = 0x%02x", inb(TMC_Status_port)); - if (inb(TMC_Status_port) & 1) - printk(" (interrupt)"); - printk("\n"); - printk("Interrupt Status = 0x%02x", inb(Interrupt_Status_port)); - if (inb(Interrupt_Status_port) & 0x08) - printk(" (enabled)"); - printk("\n"); - if (chip == tmc18c50 || chip == tmc18c30) { - printk("FIFO Status = 0x%02x\n", inb(shpnt->io_port + FIFO_Status)); - printk("Int. Condition = 0x%02x\n", inb(shpnt->io_port + Interrupt_Cond)); - } - printk("Configuration 1 = 0x%02x\n", inb(shpnt->io_port + Configuration1)); - if (chip == tmc18c50 || chip == tmc18c30) - printk("Configuration 2 = 0x%02x\n", inb(shpnt->io_port + Configuration2)); -} -#endif - -static int fd_mcs_abort(Scsi_Cmnd * SCpnt) -{ - struct Scsi_Host *shpnt = SCpnt->device->host; - - unsigned long flags; -#if EVERY_ACCESS || ERRORS_ONLY || DEBUG_ABORT - printk("fd_mcs: abort "); -#endif - - spin_lock_irqsave(shpnt->host_lock, flags); - if (!in_command) { -#if EVERY_ACCESS || ERRORS_ONLY - printk(" (not in command)\n"); -#endif - spin_unlock_irqrestore(shpnt->host_lock, flags); - return FAILED; - } else - printk("\n"); - -#if DEBUG_ABORT - fd_mcs_print_info(SCpnt); -#endif - - fd_mcs_make_bus_idle(shpnt); - - current_SC->SCp.phase |= aborted; - - current_SC->result = DID_ABORT << 16; - - /* Aborts are not done well. . . */ - my_done(shpnt, DID_ABORT << 16); - - spin_unlock_irqrestore(shpnt->host_lock, flags); - return SUCCESS; -} - -static int fd_mcs_bus_reset(Scsi_Cmnd * SCpnt) { - struct Scsi_Host *shpnt = SCpnt->device->host; - unsigned long flags; - -#if DEBUG_RESET - static int called_once = 0; -#endif - -#if ERRORS_ONLY - if (SCpnt) - printk("fd_mcs: SCSI Bus Reset\n"); -#endif - -#if DEBUG_RESET - if (called_once) - fd_mcs_print_info(current_SC); - called_once = 1; -#endif - - spin_lock_irqsave(shpnt->host_lock, flags); - - outb(1, SCSI_Cntl_port); - do_pause(2); - outb(0, SCSI_Cntl_port); - do_pause(115); - outb(0, SCSI_Mode_Cntl_port); - outb(PARITY_MASK, TMC_Cntl_port); - - spin_unlock_irqrestore(shpnt->host_lock, flags); - - /* Unless this is the very first call (i.e., SCPnt == NULL), everything - is probably hosed at this point. We will, however, try to keep - things going by informing the high-level code that we need help. */ - return SUCCESS; -} - -#include <scsi/scsi_ioctl.h> - -static int fd_mcs_biosparam(struct scsi_device * disk, struct block_device *bdev, - sector_t capacity, int *info_array) -{ - unsigned char *p = scsi_bios_ptable(bdev); - int size = capacity; - - /* BIOS >= 3.4 for MCA cards */ - /* This algorithm was provided by Future Domain (much thanks!). */ - - if (p && p[65] == 0xaa && p[64] == 0x55 /* Partition table valid */ - && p[4]) { /* Partition type */ - /* The partition table layout is as follows: - - Start: 0x1b3h - Offset: 0 = partition status - 1 = starting head - 2 = starting sector and cylinder (word, encoded) - 4 = partition type - 5 = ending head - 6 = ending sector and cylinder (word, encoded) - 8 = starting absolute sector (double word) - c = number of sectors (double word) - Signature: 0x1fe = 0x55aa - - So, this algorithm assumes: - 1) the first partition table is in use, - 2) the data in the first entry is correct, and - 3) partitions never divide cylinders - - Note that (1) may be FALSE for NetBSD (and other BSD flavors), - as well as for Linux. Note also, that Linux doesn't pay any - attention to the fields that are used by this algorithm -- it - only uses the absolute sector data. Recent versions of Linux's - fdisk(1) will fill this data in correctly, and forthcoming - versions will check for consistency. - - Checking for a non-zero partition type is not part of the - Future Domain algorithm, but it seemed to be a reasonable thing - to do, especially in the Linux and BSD worlds. */ - - info_array[0] = p[5] + 1; /* heads */ - info_array[1] = p[6] & 0x3f; /* sectors */ - } else { - /* Note that this new method guarantees that there will always be - less than 1024 cylinders on a platter. This is good for drives - up to approximately 7.85GB (where 1GB = 1024 * 1024 kB). */ - if ((unsigned int) size >= 0x7e0000U) - { - info_array[0] = 0xff; /* heads = 255 */ - info_array[1] = 0x3f; /* sectors = 63 */ - } else if ((unsigned int) size >= 0x200000U) { - info_array[0] = 0x80; /* heads = 128 */ - info_array[1] = 0x3f; /* sectors = 63 */ - } else { - info_array[0] = 0x40; /* heads = 64 */ - info_array[1] = 0x20; /* sectors = 32 */ - } - } - /* For both methods, compute the cylinders */ - info_array[2] = (unsigned int) size / (info_array[0] * info_array[1]); - kfree(p); - return 0; -} - -static struct scsi_host_template driver_template = { - .proc_name = "fd_mcs", - .proc_info = fd_mcs_proc_info, - .detect = fd_mcs_detect, - .release = fd_mcs_release, - .info = fd_mcs_info, - .queuecommand = fd_mcs_queue, - .eh_abort_handler = fd_mcs_abort, - .eh_bus_reset_handler = fd_mcs_bus_reset, - .bios_param = fd_mcs_biosparam, - .can_queue = 1, - .this_id = 7, - .sg_tablesize = 64, - .cmd_per_lun = 1, - .use_clustering = DISABLE_CLUSTERING, -}; -#include "scsi_module.c" - -MODULE_LICENSE("GPL"); diff --git a/drivers/scsi/ibmmca.c b/drivers/scsi/ibmmca.c deleted file mode 100644 index cd09132d5d7d..000000000000 --- a/drivers/scsi/ibmmca.c +++ /dev/null @@ -1,2379 +0,0 @@ -/* - Low Level Linux Driver for the IBM Microchannel SCSI Subsystem for - Linux Kernel >= 2.4.0. - Copyright (c) 1995 Strom Systems, Inc. under the terms of the GNU - General Public License. Written by Martin Kolinek, December 1995. - Further development by: Chris Beauregard, Klaus Kudielka, Michael Lang - See the file Documentation/scsi/ibmmca.txt for a detailed description - of this driver, the commandline arguments and the history of its - development. - See the WWW-page: http://www.uni-mainz.de/~langm000/linux.html for latest - updates, info and ADF-files for adapters supported by this driver. - - Alan Cox <[email protected]> - Updated for Linux 2.5.45 to use the new error handler, cleaned up the - lock macros and did a few unavoidable locking tweaks, plus one locking - fix in the irq and completion path. - - */ - -#include <linux/module.h> -#include <linux/kernel.h> -#include <linux/types.h> -#include <linux/ctype.h> -#include <linux/string.h> -#include <linux/interrupt.h> -#include <linux/ioport.h> -#include <linux/delay.h> -#include <linux/blkdev.h> -#include <linux/proc_fs.h> -#include <linux/stat.h> -#include <linux/mca.h> -#include <linux/spinlock.h> -#include <linux/init.h> - -#include <asm/io.h> - -#include "scsi.h" -#include <scsi/scsi_host.h> - -/* Common forward declarations for all Linux-versions: */ -static int ibmmca_queuecommand (struct Scsi_Host *, struct scsi_cmnd *); -static int ibmmca_abort (Scsi_Cmnd *); -static int ibmmca_host_reset (Scsi_Cmnd *); -static int ibmmca_biosparam (struct scsi_device *, struct block_device *, sector_t, int *); -static int ibmmca_proc_info(struct Scsi_Host *shpnt, char *buffer, char **start, off_t offset, int length, int inout); - - - -/* current version of this driver-source: */ -#define IBMMCA_SCSI_DRIVER_VERSION "4.0b-ac" - -/* driver configuration */ -#define IM_MAX_HOSTS 8 /* maximum number of host adapters */ -#define IM_RESET_DELAY 60 /* seconds allowed for a reset */ - -/* driver debugging - #undef all for normal operation */ -/* if defined: count interrupts and ignore this special one: */ -#undef IM_DEBUG_TIMEOUT //50 -#define TIMEOUT_PUN 0 -#define TIMEOUT_LUN 0 -/* verbose interrupt: */ -#undef IM_DEBUG_INT -/* verbose queuecommand: */ -#undef IM_DEBUG_CMD -/* verbose queucommand for specific SCSI-device type: */ -#undef IM_DEBUG_CMD_SPEC_DEV -/* verbose device probing */ -#undef IM_DEBUG_PROBE - -/* device type that shall be displayed on syslog (only during debugging): */ -#define IM_DEBUG_CMD_DEVICE TYPE_TAPE - -/* relative addresses of hardware registers on a subsystem */ -#define IM_CMD_REG(h) ((h)->io_port) /*Command Interface, (4 bytes long) */ -#define IM_ATTN_REG(h) ((h)->io_port+4) /*Attention (1 byte) */ -#define IM_CTR_REG(h) ((h)->io_port+5) /*Basic Control (1 byte) */ -#define IM_INTR_REG(h) ((h)->io_port+6) /*Interrupt Status (1 byte, r/o) */ -#define IM_STAT_REG(h) ((h)->io_port+7) /*Basic Status (1 byte, read only) */ - -/* basic I/O-port of first adapter */ -#define IM_IO_PORT 0x3540 -/* maximum number of hosts that can be found */ -#define IM_N_IO_PORT 8 - -/*requests going into the upper nibble of the Attention register */ -/*note: the lower nibble specifies the device(0-14), or subsystem(15) */ -#define IM_IMM_CMD 0x10 /*immediate command */ -#define IM_SCB 0x30 /*Subsystem Control Block command */ -#define IM_LONG_SCB 0x40 /*long Subsystem Control Block command */ -#define IM_EOI 0xe0 /*end-of-interrupt request */ - -/*values for bits 7,1,0 of Basic Control reg. (bits 6-2 reserved) */ -#define IM_HW_RESET 0x80 /*hardware reset */ -#define IM_ENABLE_DMA 0x02 /*enable subsystem's busmaster DMA */ -#define IM_ENABLE_INTR 0x01 /*enable interrupts to the system */ - -/*to interpret the upper nibble of Interrupt Status register */ -/*note: the lower nibble specifies the device(0-14), or subsystem(15) */ -#define IM_SCB_CMD_COMPLETED 0x10 -#define IM_SCB_CMD_COMPLETED_WITH_RETRIES 0x50 -#define IM_LOOP_SCATTER_BUFFER_FULL 0x60 -#define IM_ADAPTER_HW_FAILURE 0x70 -#define IM_IMMEDIATE_CMD_COMPLETED 0xa0 -#define IM_CMD_COMPLETED_WITH_FAILURE 0xc0 -#define IM_CMD_ERROR 0xe0 -#define IM_SOFTWARE_SEQUENCING_ERROR 0xf0 - -/*to interpret bits 3-0 of Basic Status register (bits 7-4 reserved) */ -#define IM_CMD_REG_FULL 0x08 -#define IM_CMD_REG_EMPTY 0x04 -#define IM_INTR_REQUEST 0x02 -#define IM_BUSY 0x01 - -/*immediate commands (word written into low 2 bytes of command reg) */ -#define IM_RESET_IMM_CMD 0x0400 -#define IM_FEATURE_CTR_IMM_CMD 0x040c -#define IM_DMA_PACING_IMM_CMD 0x040d -#define IM_ASSIGN_IMM_CMD 0x040e -#define IM_ABORT_IMM_CMD 0x040f -#define IM_FORMAT_PREP_IMM_CMD 0x0417 - -/*SCB (Subsystem Control Block) structure */ -struct im_scb { - unsigned short command; /*command word (read, etc.) */ - unsigned short enable; /*enable word, modifies cmd */ - union { - unsigned long log_blk_adr; /*block address on SCSI device */ - unsigned char scsi_cmd_length; /*6,10,12, for other scsi cmd */ - } u1; - unsigned long sys_buf_adr; /*physical system memory adr */ - unsigned long sys_buf_length; /*size of sys mem buffer */ - unsigned long tsb_adr; /*Termination Status Block adr */ - unsigned long scb_chain_adr; /*optional SCB chain address */ - union { - struct { - unsigned short count; /*block count, on SCSI device */ - unsigned short length; /*block length, on SCSI device */ - } blk; - unsigned char scsi_command[12]; /*other scsi command */ - } u2; -}; - -/*structure scatter-gather element (for list of system memory areas) */ -struct im_sge { - void *address; - unsigned long byte_length; -}; - -/*structure returned by a get_pos_info command: */ -struct im_pos_info { - unsigned short pos_id; /* adapter id */ - unsigned char pos_3a; /* pos 3 (if pos 6 = 0) */ - unsigned char pos_2; /* pos 2 */ - unsigned char int_level; /* interrupt level IRQ 11 or 14 */ - unsigned char pos_4a; /* pos 4 (if pos 6 = 0) */ - unsigned short connector_size; /* MCA connector size: 16 or 32 Bit */ - unsigned char num_luns; /* number of supported luns per device */ - unsigned char num_puns; /* number of supported puns */ - unsigned char pacing_factor; /* pacing factor */ - unsigned char num_ldns; /* number of ldns available */ - unsigned char eoi_off; /* time EOI and interrupt inactive */ - unsigned char max_busy; /* time between reset and busy on */ - unsigned short cache_stat; /* ldn cachestat. Bit=1 = not cached */ - unsigned short retry_stat; /* retry status of ldns. Bit=1=disabled */ - unsigned char pos_4b; /* pos 4 (if pos 6 = 1) */ - unsigned char pos_3b; /* pos 3 (if pos 6 = 1) */ - unsigned char pos_6; /* pos 6 */ - unsigned char pos_5; /* pos 5 */ - unsigned short max_overlap; /* maximum overlapping requests */ - unsigned short num_bus; /* number of SCSI-busses */ -}; - -/*values for SCB command word */ -#define IM_NO_SYNCHRONOUS 0x0040 /*flag for any command */ -#define IM_NO_DISCONNECT 0x0080 /*flag for any command */ -#define IM_READ_DATA_CMD 0x1c01 -#define IM_WRITE_DATA_CMD 0x1c02 -#define IM_READ_VERIFY_CMD 0x1c03 -#define IM_WRITE_VERIFY_CMD 0x1c04 -#define IM_REQUEST_SENSE_CMD 0x1c08 -#define IM_READ_CAPACITY_CMD 0x1c09 -#define IM_DEVICE_INQUIRY_CMD 0x1c0b -#define IM_READ_LOGICAL_CMD 0x1c2a -#define IM_OTHER_SCSI_CMD_CMD 0x241f - -/* unused, but supported, SCB commands */ -#define IM_GET_COMMAND_COMPLETE_STATUS_CMD 0x1c07 /* command status */ -#define IM_GET_POS_INFO_CMD 0x1c0a /* returns neat stuff */ -#define IM_READ_PREFETCH_CMD 0x1c31 /* caching controller only */ -#define IM_FOMAT_UNIT_CMD 0x1c16 /* format unit */ -#define IM_REASSIGN_BLOCK_CMD 0x1c18 /* in case of error */ - -/*values to set bits in the enable word of SCB */ -#define IM_READ_CONTROL 0x8000 -#define IM_REPORT_TSB_ONLY_ON_ERROR 0x4000 -#define IM_RETRY_ENABLE 0x2000 -#define IM_POINTER_TO_LIST 0x1000 -#define IM_SUPRESS_EXCEPTION_SHORT 0x0400 -#define IM_BYPASS_BUFFER 0x0200 -#define IM_CHAIN_ON_NO_ERROR 0x0001 - -/*TSB (Termination Status Block) structure */ -struct im_tsb { - unsigned short end_status; - unsigned short reserved1; - unsigned long residual_byte_count; - unsigned long sg_list_element_adr; - unsigned short status_length; - unsigned char dev_status; - unsigned char cmd_status; - unsigned char dev_error; - unsigned char cmd_error; - unsigned short reserved2; - unsigned short reserved3; - unsigned short low_of_last_scb_adr; - unsigned short high_of_last_scb_adr; -}; - -/*subsystem uses interrupt request level 14 */ -#define IM_IRQ 14 -/*SCSI-2 F/W may evade to interrupt 11 */ -#define IM_IRQ_FW 11 - -/* Model 95 has an additional alphanumeric display, which can be used - to display SCSI-activities. 8595 models do not have any disk led, which - makes this feature quite useful. - The regular PS/2 disk led is turned on/off by bits 6,7 of system - control port. */ - -/* LED display-port (actually, last LED on display) */ -#define MOD95_LED_PORT 0x108 -/* system-control-register of PS/2s with diskindicator */ -#define PS2_SYS_CTR 0x92 -/* activity displaying methods */ -#define LED_DISP 1 -#define LED_ADISP 2 -#define LED_ACTIVITY 4 -/* failed intr */ -#define CMD_FAIL 255 - -/* The SCSI-ID(!) of the accessed SCSI-device is shown on PS/2-95 machines' LED - displays. ldn is no longer displayed here, because the ldn mapping is now - done dynamically and the ldn <-> pun,lun maps can be looked-up at boottime - or during uptime in /proc/scsi/ibmmca/<host_no> in case of trouble, - interest, debugging or just for having fun. The left number gives the - host-adapter number and the right shows the accessed SCSI-ID. */ - -/* display_mode is set by the ibmmcascsi= command line arg */ -static int display_mode = 0; -/* set default adapter timeout */ -static unsigned int adapter_timeout = 45; -/* for probing on feature-command: */ -static unsigned int global_command_error_excuse = 0; -/* global setting by command line for adapter_speed */ -static int global_adapter_speed = 0; /* full speed by default */ - -/* Panel / LED on, do it right for F/W addressin, too. adisplay will - * just ignore ids>7, as the panel has only 7 digits available */ -#define PS2_DISK_LED_ON(ad,id) { if (display_mode & LED_DISP) { if (id>9) \ - outw((ad+48)|((id+55)<<8), MOD95_LED_PORT ); else \ - outw((ad+48)|((id+48)<<8), MOD95_LED_PORT ); } else \ - if (display_mode & LED_ADISP) { if (id<7) outb((char)(id+48),MOD95_LED_PORT+1+id); \ - outb((char)(ad+48), MOD95_LED_PORT); } \ - if ((display_mode & LED_ACTIVITY)||(!display_mode)) \ - outb(inb(PS2_SYS_CTR) | 0xc0, PS2_SYS_CTR); } - -/* Panel / LED off */ -/* bug fixed, Dec 15, 1997, where | was replaced by & here */ -#define PS2_DISK_LED_OFF() { if (display_mode & LED_DISP) \ - outw(0x2020, MOD95_LED_PORT ); else if (display_mode & LED_ADISP) { \ - outl(0x20202020,MOD95_LED_PORT); outl(0x20202020,MOD95_LED_PORT+4); } \ - if ((display_mode & LED_ACTIVITY)||(!display_mode)) \ - outb(inb(PS2_SYS_CTR) & 0x3f, PS2_SYS_CTR); } - -/* types of different supported hardware that goes to hostdata special */ -#define IBM_SCSI2_FW 0 -#define IBM_7568_WCACHE 1 -#define IBM_EXP_UNIT 2 -#define IBM_SCSI_WCACHE 3 -#define IBM_SCSI 4 -#define IBM_INTEGSCSI 5 - -/* other special flags for hostdata structure */ -#define FORCED_DETECTION 100 -#define INTEGRATED_SCSI 101 - -/* List of possible IBM-SCSI-adapters */ -static short ibmmca_id_table[] = { - 0x8efc, - 0x8efd, - 0x8ef8, - 0x8eff, - 0x8efe, - /* No entry for integrated SCSI, that's part of the register */ - 0 -}; - -static const char *ibmmca_description[] = { - "IBM SCSI-2 F/W Adapter", /* special = 0 */ - "IBM 7568 Industrial Computer SCSI Adapter w/Cache", /* special = 1 */ - "IBM Expansion Unit SCSI Controller", /* special = 2 */ - "IBM SCSI Adapter w/Cache", /* special = 3 */ - "IBM SCSI Adapter", /* special = 4 */ - "IBM Integrated SCSI Controller", /* special = 5 */ -}; - -/* Max number of logical devices (can be up from 0 to 14). 15 is the address -of the adapter itself. */ -#define MAX_LOG_DEV 15 - -/*local data for a logical device */ -struct logical_device { - struct im_scb scb; /* SCSI-subsystem-control-block structure */ - struct im_tsb tsb; /* SCSI command complete status block structure */ - struct im_sge sge[16]; /* scatter gather list structure */ - unsigned char buf[256]; /* SCSI command return data buffer */ - Scsi_Cmnd *cmd; /* SCSI-command that is currently in progress */ - int device_type; /* type of the SCSI-device. See include/scsi/scsi.h - for interpretation of the possible values */ - int block_length; /* blocksize of a particular logical SCSI-device */ - int cache_flag; /* 1 if this is uncached, 0 if cache is present for ldn */ - int retry_flag; /* 1 if adapter retry is disabled, 0 if enabled */ -}; - -/* statistics of the driver during operations (for proc_info) */ -struct Driver_Statistics { - /* SCSI statistics on the adapter */ - int ldn_access[MAX_LOG_DEV + 1]; /* total accesses on a ldn */ - int ldn_read_access[MAX_LOG_DEV + 1]; /* total read-access on a ldn */ - int ldn_write_access[MAX_LOG_DEV + 1]; /* total write-access on a ldn */ - int ldn_inquiry_access[MAX_LOG_DEV + 1]; /* total inquiries on a ldn */ - int ldn_modeselect_access[MAX_LOG_DEV + 1]; /* total mode selects on ldn */ - int scbs; /* short SCBs queued */ - int long_scbs; /* long SCBs queued */ - int total_accesses; /* total accesses on all ldns */ - int total_interrupts; /* total interrupts (should be - same as total_accesses) */ - int total_errors; /* command completed with error */ - /* dynamical assignment statistics */ - int total_scsi_devices; /* number of physical pun,lun */ - int dyn_flag; /* flag showing dynamical mode */ - int dynamical_assignments; /* number of remappings of ldns */ - int ldn_assignments[MAX_LOG_DEV + 1]; /* number of remappings of each - ldn */ -}; - -/* data structure for each host adapter */ -struct ibmmca_hostdata { - /* array of logical devices: */ - struct logical_device _ld[MAX_LOG_DEV + 1]; - /* array to convert (pun, lun) into logical device number: */ - unsigned char _get_ldn[16][8]; - /*array that contains the information about the physical SCSI-devices - attached to this host adapter: */ - unsigned char _get_scsi[16][8]; - /* used only when checking logical devices: */ - int _local_checking_phase_flag; - /* report received interrupt: */ - int _got_interrupt; - /* report termination-status of SCSI-command: */ - int _stat_result; - /* reset status (used only when doing reset): */ - int _reset_status; - /* code of the last SCSI command (needed for panic info): */ - int _last_scsi_command[MAX_LOG_DEV + 1]; - /* identifier of the last SCSI-command type */ - int _last_scsi_type[MAX_LOG_DEV + 1]; - /* last blockcount */ - int _last_scsi_blockcount[MAX_LOG_DEV + 1]; - /* last locgical block address */ - unsigned long _last_scsi_logical_block[MAX_LOG_DEV + 1]; - /* Counter that points on the next reassignable ldn for dynamical - remapping. The default value is 7, that is the first reassignable - number in the list at boottime: */ - int _next_ldn; - /* Statistics-structure for this IBM-SCSI-host: */ - struct Driver_Statistics _IBM_DS; - /* This hostadapters pos-registers pos2 until pos6 */ - unsigned int _pos[8]; - /* assign a special variable, that contains dedicated info about the - adaptertype */ - int _special; - /* connector size on the MCA bus */ - int _connector_size; - /* synchronous SCSI transfer rate bitpattern */ - int _adapter_speed; -}; - -/* macros to access host data structure */ -#define subsystem_pun(h) ((h)->this_id) -#define subsystem_maxid(h) ((h)->max_id) -#define ld(h) (((struct ibmmca_hostdata *) (h)->hostdata)->_ld) -#define get_ldn(h) (((struct ibmmca_hostdata *) (h)->hostdata)->_get_ldn) -#define get_scsi(h) (((struct ibmmca_hostdata *) (h)->hostdata)->_get_scsi) -#define local_checking_phase_flag(h) (((struct ibmmca_hostdata *) (h)->hostdata)->_local_checking_phase_flag) -#define got_interrupt(h) (((struct ibmmca_hostdata *) (h)->hostdata)->_got_interrupt) -#define stat_result(h) (((struct ibmmca_hostdata *) (h)->hostdata)->_stat_result) -#define reset_status(h) (((struct ibmmca_hostdata *) (h)->hostdata)->_reset_status) -#define last_scsi_command(h) (((struct ibmmca_hostdata *) (h)->hostdata)->_last_scsi_command) -#define last_scsi_type(h) (((struct ibmmca_hostdata *) (h)->hostdata)->_last_scsi_type) -#define last_scsi_blockcount(h) (((struct ibmmca_hostdata *) (h)->hostdata)->_last_scsi_blockcount) -#define last_scsi_logical_block(h) (((struct ibmmca_hostdata *) (h)->hostdata)->_last_scsi_logical_block) -#define last_scsi_type(h) (((struct ibmmca_hostdata *) (h)->hostdata)->_last_scsi_type) -#define next_ldn(h) (((struct ibmmca_hostdata *) (h)->hostdata)->_next_ldn) -#define IBM_DS(h) (((struct ibmmca_hostdata *) (h)->hostdata)->_IBM_DS) -#define special(h) (((struct ibmmca_hostdata *) (h)->hostdata)->_special) -#define subsystem_connector_size(h) (((struct ibmmca_hostdata *) (h)->hostdata)->_connector_size) -#define adapter_speed(h) (((struct ibmmca_hostdata *) (h)->hostdata)->_adapter_speed) -#define pos2(h) (((struct ibmmca_hostdata *) (h)->hostdata)->_pos[2]) -#define pos3(h) (((struct ibmmca_hostdata *) (h)->hostdata)->_pos[3]) -#define pos4(h) (((struct ibmmca_hostdata *) (h)->hostdata)->_pos[4]) -#define pos5(h) (((struct ibmmca_hostdata *) (h)->hostdata)->_pos[5]) -#define pos6(h) (((struct ibmmca_hostdata *) (h)->hostdata)->_pos[6]) - -/* Define a arbitrary number as subsystem-marker-type. This number is, as - described in the ANSI-SCSI-standard, not occupied by other device-types. */ -#define TYPE_IBM_SCSI_ADAPTER 0x2F - -/* Define 0xFF for no device type, because this type is not defined within - the ANSI-SCSI-standard, therefore, it can be used and should not cause any - harm. */ -#define TYPE_NO_DEVICE 0xFF - -/* define medium-changer. If this is not defined previously, e.g. Linux - 2.0.x, define this type here. */ -#ifndef TYPE_MEDIUM_CHANGER -#define TYPE_MEDIUM_CHANGER 0x08 -#endif - -/* define possible operations for the immediate_assign command */ -#define SET_LDN 0 -#define REMOVE_LDN 1 - -/* ldn which is used to probe the SCSI devices */ -#define PROBE_LDN 0 - -/* reset status flag contents */ -#define IM_RESET_NOT_IN_PROGRESS 0 -#define IM_RESET_IN_PROGRESS 1 -#define IM_RESET_FINISHED_OK 2 -#define IM_RESET_FINISHED_FAIL 3 -#define IM_RESET_NOT_IN_PROGRESS_NO_INT 4 -#define IM_RESET_FINISHED_OK_NO_INT 5 - -/* define undefined SCSI-command */ -#define NO_SCSI 0xffff - -/*-----------------------------------------------------------------------*/ - -/* if this is nonzero, ibmmcascsi option has been passed to the kernel */ -static int io_port[IM_MAX_HOSTS] = { 0, 0, 0, 0, 0, 0, 0, 0 }; -static int scsi_id[IM_MAX_HOSTS] = { 7, 7, 7, 7, 7, 7, 7, 7 }; - -/* fill module-parameters only, when this define is present. - (that is kernel version 2.1.x) */ -#if defined(MODULE) -static char *boot_options = NULL; -module_param(boot_options, charp, 0); -module_param_array(io_port, int, NULL, 0); -module_param_array(scsi_id, int, NULL, 0); - -MODULE_LICENSE("GPL"); -#endif -/*counter of concurrent disk read/writes, to turn on/off disk led */ -static int disk_rw_in_progress = 0; - -static unsigned int pos[8]; /* whole pos register-line for diagnosis */ -/* Taking into account the additions, made by ZP Gu. - * This selects now the preset value from the configfile and - * offers the 'normal' commandline option to be accepted */ -#ifdef CONFIG_IBMMCA_SCSI_ORDER_STANDARD -static char ibm_ansi_order = 1; -#else -static char ibm_ansi_order = 0; -#endif - -static void issue_cmd(struct Scsi_Host *, unsigned long, unsigned char); -static void internal_done(Scsi_Cmnd * cmd); -static void check_devices(struct Scsi_Host *, int); -static int immediate_assign(struct Scsi_Host *, unsigned int, unsigned int, unsigned int, unsigned int); -static int immediate_feature(struct Scsi_Host *, unsigned int, unsigned int); -#ifdef CONFIG_IBMMCA_SCSI_DEV_RESET -static int immediate_reset(struct Scsi_Host *, unsigned int); -#endif -static int device_inquiry(struct Scsi_Host *, int); -static int read_capacity(struct Scsi_Host *, int); -static int get_pos_info(struct Scsi_Host *); -static char *ti_p(int); -static char *ti_l(int); -static char *ibmrate(unsigned int, int); -static int probe_display(int); -static int probe_bus_mode(struct Scsi_Host *); -static int device_exists(struct Scsi_Host *, int, int *, int *); -static int option_setup(char *); -/* local functions needed for proc_info */ -static int ldn_access_load(struct Scsi_Host *, int); -static int ldn_access_total_read_write(struct Scsi_Host *); - -static irqreturn_t interrupt_handler(int irq, void *dev_id) -{ - unsigned int intr_reg; - unsigned int cmd_result; - unsigned int ldn; - unsigned long flags; - Scsi_Cmnd *cmd; - int lastSCSI; - struct device *dev = dev_id; - struct Scsi_Host *shpnt = dev_get_drvdata(dev); - - spin_lock_irqsave(shpnt->host_lock, flags); - - if(!(inb(IM_STAT_REG(shpnt)) & IM_INTR_REQUEST)) { - spin_unlock_irqrestore(shpnt->host_lock, flags); - return IRQ_NONE; - } - - /* the reset-function already did all the job, even ints got - renabled on the subsystem, so just return */ - if ((reset_status(shpnt) == IM_RESET_NOT_IN_PROGRESS_NO_INT) || (reset_status(shpnt) == IM_RESET_FINISHED_OK_NO_INT)) { - reset_status(shpnt) = IM_RESET_NOT_IN_PROGRESS; - spin_unlock_irqrestore(shpnt->host_lock, flags); - return IRQ_HANDLED; - } - - /*must wait for attention reg not busy, then send EOI to subsystem */ - while (1) { - if (!(inb(IM_STAT_REG(shpnt)) & IM_BUSY)) - break; - cpu_relax(); - } - - /*get command result and logical device */ - intr_reg = (unsigned char) (inb(IM_INTR_REG(shpnt))); - cmd_result = intr_reg & 0xf0; - ldn = intr_reg & 0x0f; - /* get the last_scsi_command here */ - lastSCSI = last_scsi_command(shpnt)[ldn]; - outb(IM_EOI | ldn, IM_ATTN_REG(shpnt)); - - /*these should never happen (hw fails, or a local programming bug) */ - if (!global_command_error_excuse) { - switch (cmd_result) { - /* Prevent from Ooopsing on error to show the real reason */ - case IM_ADAPTER_HW_FAILURE: - case IM_SOFTWARE_SEQUENCING_ERROR: - case IM_CMD_ERROR: - printk(KERN_ERR "IBM MCA SCSI: Fatal Subsystem ERROR!\n"); - printk(KERN_ERR " Last cmd=0x%x, ena=%x, len=", lastSCSI, ld(shpnt)[ldn].scb.enable); - if (ld(shpnt)[ldn].cmd) - printk("%ld/%ld,", (long) (scsi_bufflen(ld(shpnt)[ldn].cmd)), (long) (ld(shpnt)[ldn].scb.sys_buf_length)); - else - printk("none,"); - if (ld(shpnt)[ldn].cmd) - printk("Blocksize=%d", ld(shpnt)[ldn].scb.u2.blk.length); - else - printk("Blocksize=none"); - printk(", host=%p, ldn=0x%x\n", shpnt, ldn); - if (ld(shpnt)[ldn].cmd) { - printk(KERN_ERR "Blockcount=%d/%d\n", last_scsi_blockcount(shpnt)[ldn], ld(shpnt)[ldn].scb.u2.blk.count); - printk(KERN_ERR "Logical block=%lx/%lx\n", last_scsi_logical_block(shpnt)[ldn], ld(shpnt)[ldn].scb.u1.log_blk_adr); - } - printk(KERN_ERR "Reason given: %s\n", (cmd_result == IM_ADAPTER_HW_FAILURE) ? "HARDWARE FAILURE" : (cmd_result == IM_SOFTWARE_SEQUENCING_ERROR) ? "SOFTWARE SEQUENCING ERROR" : (cmd_result == IM_CMD_ERROR) ? "COMMAND ERROR" : "UNKNOWN"); - /* if errors appear, enter this section to give detailed info */ - printk(KERN_ERR "IBM MCA SCSI: Subsystem Error-Status follows:\n"); - printk(KERN_ERR " Command Type................: %x\n", last_scsi_type(shpnt)[ldn]); - printk(KERN_ERR " Attention Register..........: %x\n", inb(IM_ATTN_REG(shpnt))); - printk(KERN_ERR " Basic Control Register......: %x\n", inb(IM_CTR_REG(shpnt))); - printk(KERN_ERR " Interrupt Status Register...: %x\n", intr_reg); - printk(KERN_ERR " Basic Status Register.......: %x\n", inb(IM_STAT_REG(shpnt))); - if ((last_scsi_type(shpnt)[ldn] == IM_SCB) || (last_scsi_type(shpnt)[ldn] == IM_LONG_SCB)) { - printk(KERN_ERR " SCB-Command.................: %x\n", ld(shpnt)[ldn].scb.command); - printk(KERN_ERR " SCB-Enable..................: %x\n", ld(shpnt)[ldn].scb.enable); - printk(KERN_ERR " SCB-logical block address...: %lx\n", ld(shpnt)[ldn].scb.u1.log_blk_adr); - printk(KERN_ERR " SCB-system buffer address...: %lx\n", ld(shpnt)[ldn].scb.sys_buf_adr); - printk(KERN_ERR " SCB-system buffer length....: %lx\n", ld(shpnt)[ldn].scb.sys_buf_length); - printk(KERN_ERR " SCB-tsb address.............: %lx\n", ld(shpnt)[ldn].scb.tsb_adr); - printk(KERN_ERR " SCB-Chain address...........: %lx\n", ld(shpnt)[ldn].scb.scb_chain_adr); - printk(KERN_ERR " SCB-block count.............: %x\n", ld(shpnt)[ldn].scb.u2.blk.count); - printk(KERN_ERR " SCB-block length............: %x\n", ld(shpnt)[ldn].scb.u2.blk.length); - } - printk(KERN_ERR " Send this report to the maintainer.\n"); - panic("IBM MCA SCSI: Fatal error message from the subsystem (0x%X,0x%X)!\n", lastSCSI, cmd_result); - break; - } - } else { - /* The command error handling is made silent, but we tell the - * calling function, that there is a reported error from the - * adapter. */ - switch (cmd_result) { - case IM_ADAPTER_HW_FAILURE: - case IM_SOFTWARE_SEQUENCING_ERROR: - case IM_CMD_ERROR: - global_command_error_excuse = CMD_FAIL; - break; - default: - global_command_error_excuse = 0; - break; - } - } - /* if no panic appeared, increase the interrupt-counter */ - IBM_DS(shpnt).total_interrupts++; - /*only for local checking phase */ - if (local_checking_phase_flag(shpnt)) { - stat_result(shpnt) = cmd_result; - got_interrupt(shpnt) = 1; - reset_status(shpnt) = IM_RESET_FINISHED_OK; - last_scsi_command(shpnt)[ldn] = NO_SCSI; - spin_unlock_irqrestore(shpnt->host_lock, flags); - return IRQ_HANDLED; - } - /* handling of commands coming from upper level of scsi driver */ - if (last_scsi_type(shpnt)[ldn] == IM_IMM_CMD) { - /* verify ldn, and may handle rare reset immediate command */ - if ((reset_status(shpnt) == IM_RESET_IN_PROGRESS) && (last_scsi_command(shpnt)[ldn] == IM_RESET_IMM_CMD)) { - if (cmd_result == IM_CMD_COMPLETED_WITH_FAILURE) { - disk_rw_in_progress = 0; - PS2_DISK_LED_OFF(); - reset_status(shpnt) = IM_RESET_FINISHED_FAIL; - } else { - /*reset disk led counter, turn off disk led */ - disk_rw_in_progress = 0; - PS2_DISK_LED_OFF(); - reset_status(shpnt) = IM_RESET_FINISHED_OK; - } - stat_result(shpnt) = cmd_result; - last_scsi_command(shpnt)[ldn] = NO_SCSI; - last_scsi_type(shpnt)[ldn] = 0; - spin_unlock_irqrestore(shpnt->host_lock, flags); - return IRQ_HANDLED; - } else if (last_scsi_command(shpnt)[ldn] == IM_ABORT_IMM_CMD) { - /* react on SCSI abort command */ -#ifdef IM_DEBUG_PROBE - printk("IBM MCA SCSI: Interrupt from SCSI-abort.\n"); -#endif - disk_rw_in_progress = 0; - PS2_DISK_LED_OFF(); - cmd = ld(shpnt)[ldn].cmd; - ld(shpnt)[ldn].cmd = NULL; - if (cmd_result == IM_CMD_COMPLETED_WITH_FAILURE) - cmd->result = DID_NO_CONNECT << 16; - else - cmd->result = DID_ABORT << 16; - stat_result(shpnt) = cmd_result; - last_scsi_command(shpnt)[ldn] = NO_SCSI; - last_scsi_type(shpnt)[ldn] = 0; - if (cmd->scsi_done) - (cmd->scsi_done) (cmd); /* should be the internal_done */ - spin_unlock_irqrestore(shpnt->host_lock, flags); - return IRQ_HANDLED; - } else { - disk_rw_in_progress = 0; - PS2_DISK_LED_OFF(); - reset_status(shpnt) = IM_RESET_FINISHED_OK; - stat_result(shpnt) = cmd_result; - last_scsi_command(shpnt)[ldn] = NO_SCSI; - spin_unlock_irqrestore(shpnt->host_lock, flags); - return IRQ_HANDLED; - } - } - last_scsi_command(shpnt)[ldn] = NO_SCSI; - last_scsi_type(shpnt)[ldn] = 0; - cmd = ld(shpnt)[ldn].cmd; - ld(shpnt)[ldn].cmd = NULL; -#ifdef IM_DEBUG_TIMEOUT - if (cmd) { - if ((cmd->target == TIMEOUT_PUN) && (cmd->device->lun == TIMEOUT_LUN)) { - spin_unlock_irqsave(shpnt->host_lock, flags); - printk("IBM MCA SCSI: Ignoring interrupt from pun=%x, lun=%x.\n", cmd->target, cmd->device->lun); - return IRQ_HANDLED; - } - } -#endif - /*if no command structure, just return, else clear cmd */ - if (!cmd) - { - spin_unlock_irqrestore(shpnt->host_lock, flags); - return IRQ_HANDLED; - } - -#ifdef IM_DEBUG_INT - printk("cmd=%02x ireg=%02x ds=%02x cs=%02x de=%02x ce=%02x\n", cmd->cmnd[0], intr_reg, ld(shpnt)[ldn].tsb.dev_status, ld(shpnt)[ldn].tsb.cmd_status, ld(shpnt)[ldn].tsb.dev_error, ld(shpnt)[ldn].tsb.cmd_error); -#endif - /*if this is end of media read/write, may turn off PS/2 disk led */ - if ((ld(shpnt)[ldn].device_type != TYPE_NO_LUN) && (ld(shpnt)[ldn].device_type != TYPE_NO_DEVICE)) { - /* only access this, if there was a valid device addressed */ - if (--disk_rw_in_progress == 0) - PS2_DISK_LED_OFF(); - } - - /* IBM describes the status-mask to be 0x1e, but this is not conform - * with SCSI-definition, I suppose, the reason for it is that IBM - * adapters do not support CMD_TERMINATED, TASK_SET_FULL and - * ACA_ACTIVE as returning statusbyte information. (ML) */ - if (cmd_result == IM_CMD_COMPLETED_WITH_FAILURE) { - cmd->result = (unsigned char) (ld(shpnt)[ldn].tsb.dev_status & 0x1e); - IBM_DS(shpnt).total_errors++; - } else - cmd->result = 0; - /* write device status into cmd->result, and call done function */ - if (lastSCSI == NO_SCSI) { /* unexpected interrupt :-( */ - cmd->result |= DID_BAD_INTR << 16; - printk("IBM MCA SCSI: WARNING - Interrupt from non-pending SCSI-command!\n"); - } else /* things went right :-) */ - cmd->result |= DID_OK << 16; - if (cmd->scsi_done) - (cmd->scsi_done) (cmd); - spin_unlock_irqrestore(shpnt->host_lock, flags); - return IRQ_HANDLED; -} - -static void issue_cmd(struct Scsi_Host *shpnt, unsigned long cmd_reg, - unsigned char attn_reg) -{ - unsigned long flags; - /* must wait for attention reg not busy */ - while (1) { - spin_lock_irqsave(shpnt->host_lock, flags); - if (!(inb(IM_STAT_REG(shpnt)) & IM_BUSY)) - break; - spin_unlock_irqrestore(shpnt->host_lock, flags); - } - /* write registers and enable system interrupts */ - outl(cmd_reg, IM_CMD_REG(shpnt)); - outb(attn_reg, IM_ATTN_REG(shpnt)); - spin_unlock_irqrestore(shpnt->host_lock, flags); -} - -static void internal_done(Scsi_Cmnd * cmd) -{ - cmd->SCp.Status++; - return; -} - -/* SCSI-SCB-command for device_inquiry */ -static int device_inquiry(struct Scsi_Host *shpnt, int ldn) -{ - int retr; - struct im_scb *scb; - struct im_tsb *tsb; - unsigned char *buf; - - scb = &(ld(shpnt)[ldn].scb); - tsb = &(ld(shpnt)[ldn].tsb); - buf = (unsigned char *) (&(ld(shpnt)[ldn].buf)); - ld(shpnt)[ldn].tsb.dev_status = 0; /* prepare statusblock */ - for (retr = 0; retr < 3; retr++) { - /* fill scb with inquiry command */ - scb->command = IM_DEVICE_INQUIRY_CMD | IM_NO_DISCONNECT; - scb->enable = IM_REPORT_TSB_ONLY_ON_ERROR | IM_READ_CONTROL | IM_SUPRESS_EXCEPTION_SHORT | IM_RETRY_ENABLE | IM_BYPASS_BUFFER; - last_scsi_command(shpnt)[ldn] = IM_DEVICE_INQUIRY_CMD; - last_scsi_type(shpnt)[ldn] = IM_SCB; - scb->sys_buf_adr = isa_virt_to_bus(buf); - scb->sys_buf_length = 255; /* maximum bufferlength gives max info */ - scb->tsb_adr = isa_virt_to_bus(tsb); - /* issue scb to passed ldn, and busy wait for interrupt */ - got_interrupt(shpnt) = 0; - issue_cmd(shpnt, isa_virt_to_bus(scb), IM_SCB | ldn); - while (!got_interrupt(shpnt)) - barrier(); - - /*if command successful, break */ - if ((stat_result(shpnt) == IM_SCB_CMD_COMPLETED) || (stat_result(shpnt) == IM_SCB_CMD_COMPLETED_WITH_RETRIES)) - return 1; - } - /*if all three retries failed, return "no device at this ldn" */ - if (retr >= 3) - return 0; - else - return 1; -} - -static int read_capacity(struct Scsi_Host *shpnt, int ldn) -{ - int retr; - struct im_scb *scb; - struct im_tsb *tsb; - unsigned char *buf; - - scb = &(ld(shpnt)[ldn].scb); - tsb = &(ld(shpnt)[ldn].tsb); - buf = (unsigned char *) (&(ld(shpnt)[ldn].buf)); - ld(shpnt)[ldn].tsb.dev_status = 0; - for (retr = 0; retr < 3; retr++) { - /*fill scb with read capacity command */ - scb->command = IM_READ_CAPACITY_CMD; - scb->enable = IM_REPORT_TSB_ONLY_ON_ERROR | IM_READ_CONTROL | IM_RETRY_ENABLE | IM_BYPASS_BUFFER; - last_scsi_command(shpnt)[ldn] = IM_READ_CAPACITY_CMD; - last_scsi_type(shpnt)[ldn] = IM_SCB; - scb->sys_buf_adr = isa_virt_to_bus(buf); - scb->sys_buf_length = 8; - scb->tsb_adr = isa_virt_to_bus(tsb); - /*issue scb to passed ldn, and busy wait for interrupt */ - got_interrupt(shpnt) = 0; - issue_cmd(shpnt, isa_virt_to_bus(scb), IM_SCB | ldn); - while (!got_interrupt(shpnt)) - barrier(); - - /*if got capacity, get block length and return one device found */ - if ((stat_result(shpnt) == IM_SCB_CMD_COMPLETED) || (stat_result(shpnt) == IM_SCB_CMD_COMPLETED_WITH_RETRIES)) - return 1; - } - /*if all three retries failed, return "no device at this ldn" */ - if (retr >= 3) - return 0; - else - return 1; -} - -static int get_pos_info(struct Scsi_Host *shpnt) -{ - int retr; - struct im_scb *scb; - struct im_tsb *tsb; - unsigned char *buf; - - scb = &(ld(shpnt)[MAX_LOG_DEV].scb); - tsb = &(ld(shpnt)[MAX_LOG_DEV].tsb); - buf = (unsigned char *) (&(ld(shpnt)[MAX_LOG_DEV].buf)); - ld(shpnt)[MAX_LOG_DEV].tsb.dev_status = 0; - for (retr = 0; retr < 3; retr++) { - /*fill scb with get_pos_info command */ - scb->command = IM_GET_POS_INFO_CMD; - scb->enable = IM_READ_CONTROL | IM_REPORT_TSB_ONLY_ON_ERROR | IM_RETRY_ENABLE | IM_BYPASS_BUFFER; - last_scsi_command(shpnt)[MAX_LOG_DEV] = IM_GET_POS_INFO_CMD; - last_scsi_type(shpnt)[MAX_LOG_DEV] = IM_SCB; - scb->sys_buf_adr = isa_virt_to_bus(buf); - if (special(shpnt) == IBM_SCSI2_FW) - scb->sys_buf_length = 256; /* get all info from F/W adapter */ - else - scb->sys_buf_length = 18; /* get exactly 18 bytes for other SCSI */ - scb->tsb_adr = isa_virt_to_bus(tsb); - /*issue scb to ldn=15, and busy wait for interrupt */ - got_interrupt(shpnt) = 0; - issue_cmd(shpnt, isa_virt_to_bus(scb), IM_SCB | MAX_LOG_DEV); - - /* FIXME: timeout */ - while (!got_interrupt(shpnt)) - barrier(); - - /*if got POS-stuff, get block length and return one device found */ - if ((stat_result(shpnt) == IM_SCB_CMD_COMPLETED) || (stat_result(shpnt) == IM_SCB_CMD_COMPLETED_WITH_RETRIES)) - return 1; - } - /* if all three retries failed, return "no device at this ldn" */ - if (retr >= 3) - return 0; - else - return 1; -} - -/* SCSI-immediate-command for assign. This functions maps/unmaps specific - ldn-numbers on SCSI (PUN,LUN). It is needed for presetting of the - subsystem and for dynamical remapping od ldns. */ -static int immediate_assign(struct Scsi_Host *shpnt, unsigned int pun, - unsigned int lun, unsigned int ldn, - unsigned int operation) -{ - int retr; - unsigned long imm_cmd; - - for (retr = 0; retr < 3; retr++) { - /* select mutation level of the SCSI-adapter */ - switch (special(shpnt)) { - case IBM_SCSI2_FW: - imm_cmd = (unsigned long) (IM_ASSIGN_IMM_CMD); - imm_cmd |= (unsigned long) ((lun & 7) << 24); - imm_cmd |= (unsigned long) ((operation & 1) << 23); - imm_cmd |= (unsigned long) ((pun & 7) << 20) | ((pun & 8) << 24); - imm_cmd |= (unsigned long) ((ldn & 15) << 16); - break; - default: - imm_cmd = inl(IM_CMD_REG(shpnt)); - imm_cmd &= (unsigned long) (0xF8000000); /* keep reserved bits */ - imm_cmd |= (unsigned long) (IM_ASSIGN_IMM_CMD); - imm_cmd |= (unsigned long) ((lun & 7) << 24); - imm_cmd |= (unsigned long) ((operation & 1) << 23); - imm_cmd |= (unsigned long) ((pun & 7) << 20); - imm_cmd |= (unsigned long) ((ldn & 15) << 16); - break; - } - last_scsi_command(shpnt)[MAX_LOG_DEV] = IM_ASSIGN_IMM_CMD; - last_scsi_type(shpnt)[MAX_LOG_DEV] = IM_IMM_CMD; - got_interrupt(shpnt) = 0; - issue_cmd(shpnt, (unsigned long) (imm_cmd), IM_IMM_CMD | MAX_LOG_DEV); - while (!got_interrupt(shpnt)) - barrier(); - - /*if command successful, break */ - if (stat_result(shpnt) == IM_IMMEDIATE_CMD_COMPLETED) - return 1; - } - if (retr >= 3) - return 0; - else - return 1; -} - -static int immediate_feature(struct Scsi_Host *shpnt, unsigned int speed, unsigned int timeout) -{ - int retr; - unsigned long imm_cmd; - - for (retr = 0; retr < 3; retr++) { - /* select mutation level of the SCSI-adapter */ - imm_cmd = IM_FEATURE_CTR_IMM_CMD; - imm_cmd |= (unsigned long) ((speed & 0x7) << 29); - imm_cmd |= (unsigned long) ((timeout & 0x1fff) << 16); - last_scsi_command(shpnt)[MAX_LOG_DEV] = IM_FEATURE_CTR_IMM_CMD; - last_scsi_type(shpnt)[MAX_LOG_DEV] = IM_IMM_CMD; - got_interrupt(shpnt) = 0; - /* we need to run into command errors in order to probe for the - * right speed! */ - global_command_error_excuse = 1; - issue_cmd(shpnt, (unsigned long) (imm_cmd), IM_IMM_CMD | MAX_LOG_DEV); - - /* FIXME: timeout */ - while (!got_interrupt(shpnt)) - barrier(); - if (global_command_error_excuse == CMD_FAIL) { - global_command_error_excuse = 0; - return 2; - } else - global_command_error_excuse = 0; - /*if command successful, break */ - if (stat_result(shpnt) == IM_IMMEDIATE_CMD_COMPLETED) - return 1; - } - if (retr >= 3) - return 0; - else - return 1; -} - -#ifdef CONFIG_IBMMCA_SCSI_DEV_RESET -static int immediate_reset(struct Scsi_Host *shpnt, unsigned int ldn) -{ - int retries; - int ticks; - unsigned long imm_command; - - for (retries = 0; retries < 3; retries++) { - imm_command = inl(IM_CMD_REG(shpnt)); - imm_command &= (unsigned long) (0xFFFF0000); /* keep reserved bits */ - imm_command |= (unsigned long) (IM_RESET_IMM_CMD); - last_scsi_command(shpnt)[ldn] = IM_RESET_IMM_CMD; - last_scsi_type(shpnt)[ldn] = IM_IMM_CMD; - got_interrupt(shpnt) = 0; - reset_status(shpnt) = IM_RESET_IN_PROGRESS; - issue_cmd(shpnt, (unsigned long) (imm_command), IM_IMM_CMD | ldn); - ticks = IM_RESET_DELAY * HZ; - while (reset_status(shpnt) == IM_RESET_IN_PROGRESS && --ticks) { - udelay((1 + 999 / HZ) * 1000); - barrier(); - } - /* if reset did not complete, just complain */ - if (!ticks) { - printk(KERN_ERR "IBM MCA SCSI: reset did not complete within %d seconds.\n", IM_RESET_DELAY); - reset_status(shpnt) = IM_RESET_FINISHED_OK; - /* did not work, finish */ - return 1; - } - /*if command successful, break */ - if (stat_result(shpnt) == IM_IMMEDIATE_CMD_COMPLETED) - return 1; - } - if (retries >= 3) - return 0; - else - return 1; -} -#endif - -/* type-interpreter for physical device numbers */ -static char *ti_p(int dev) -{ - switch (dev) { - case TYPE_IBM_SCSI_ADAPTER: - return ("A"); - case TYPE_DISK: - return ("D"); - case TYPE_TAPE: - return ("T"); - case TYPE_PROCESSOR: - return ("P"); - case TYPE_WORM: - return ("W"); - case TYPE_ROM: - return ("R"); - case TYPE_SCANNER: - return ("S"); - case TYPE_MOD: - return ("M"); - case TYPE_MEDIUM_CHANGER: - return ("C"); - case TYPE_NO_LUN: - return ("+"); /* show NO_LUN */ - } - return ("-"); /* TYPE_NO_DEVICE and others */ -} - -/* interpreter for logical device numbers (ldn) */ -static char *ti_l(int val) -{ - const char hex[16] = "0123456789abcdef"; - static char answer[2]; - - answer[1] = (char) (0x0); - if (val <= MAX_LOG_DEV) - answer[0] = hex[val]; - else - answer[0] = '-'; - return (char *) &answer; -} - -/* transfers bitpattern of the feature command to values in MHz */ -static char *ibmrate(unsigned int speed, int i) -{ - switch (speed) { - case 0: - return i ? "5.00" : "10.00"; - case 1: - return i ? "4.00" : "8.00"; - case 2: - return i ? "3.33" : "6.66"; - case 3: - return i ? "2.86" : "5.00"; - case 4: - return i ? "2.50" : "4.00"; - case 5: - return i ? "2.22" : "3.10"; - case 6: - return i ? "2.00" : "2.50"; - case 7: - return i ? "1.82" : "2.00"; - } - return "---"; -} - -static int probe_display(int what) -{ - static int rotator = 0; - const char rotor[] = "|/-\\"; - - if (!(display_mode & LED_DISP)) - return 0; - if (!what) { - outl(0x20202020, MOD95_LED_PORT); - outl(0x20202020, MOD95_LED_PORT + 4); - } else { - outb('S', MOD95_LED_PORT + 7); - outb('C', MOD95_LED_PORT + 6); - outb('S', MOD95_LED_PORT + 5); - outb('I', MOD95_LED_PORT + 4); - outb('i', MOD95_LED_PORT + 3); - outb('n', MOD95_LED_PORT + 2); - outb('i', MOD95_LED_PORT + 1); - outb((char) (rotor[rotator]), MOD95_LED_PORT); - rotator++; - if (rotator > 3) - rotator = 0; - } - return 0; -} - -static int probe_bus_mode(struct Scsi_Host *shpnt) -{ - struct im_pos_info *info; - int num_bus = 0; - int ldn; - - info = (struct im_pos_info *) (&(ld(shpnt)[MAX_LOG_DEV].buf)); - if (get_pos_info(shpnt)) { - if (info->connector_size & 0xf000) - subsystem_connector_size(shpnt) = 16; - else - subsystem_connector_size(shpnt) = 32; - num_bus |= (info->pos_4b & 8) >> 3; - for (ldn = 0; ldn <= MAX_LOG_DEV; ldn++) { - if ((special(shpnt) == IBM_SCSI_WCACHE) || (special(shpnt) == IBM_7568_WCACHE)) { - if (!((info->cache_stat >> ldn) & 1)) - ld(shpnt)[ldn].cache_flag = 0; - } - if (!((info->retry_stat >> ldn) & 1)) - ld(shpnt)[ldn].retry_flag = 0; - } -#ifdef IM_DEBUG_PROBE - printk("IBM MCA SCSI: SCSI-Cache bits: "); - for (ldn = 0; ldn <= MAX_LOG_DEV; ldn++) { - printk("%d", ld(shpnt)[ldn].cache_flag); - } - printk("\nIBM MCA SCSI: SCSI-Retry bits: "); - for (ldn = 0; ldn <= MAX_LOG_DEV; ldn++) { - printk("%d", ld(shpnt)[ldn].retry_flag); - } - printk("\n"); -#endif - } - return num_bus; -} - -/* probing scsi devices */ -static void check_devices(struct Scsi_Host *shpnt, int adaptertype) -{ - int id, lun, ldn, ticks; - int count_devices; /* local counter for connected device */ - int max_pun; - int num_bus; - int speedrun; /* local adapter_speed check variable */ - - /* assign default values to certain variables */ - ticks = 0; - count_devices = 0; - IBM_DS(shpnt).dyn_flag = 0; /* normally no need for dynamical ldn management */ - IBM_DS(shpnt).total_errors = 0; /* set errorcounter to 0 */ - next_ldn(shpnt) = 7; /* next ldn to be assigned is 7, because 0-6 is 'hardwired' */ - - /* initialize the very important driver-informational arrays/structs */ - memset(ld(shpnt), 0, sizeof(ld(shpnt))); - for (ldn = 0; ldn <= MAX_LOG_DEV; ldn++) { - last_scsi_command(shpnt)[ldn] = NO_SCSI; /* emptify last SCSI-command storage */ - last_scsi_type(shpnt)[ldn] = 0; - ld(shpnt)[ldn].cache_flag = 1; - ld(shpnt)[ldn].retry_flag = 1; - } - memset(get_ldn(shpnt), TYPE_NO_DEVICE, sizeof(get_ldn(shpnt))); /* this is essential ! */ - memset(get_scsi(shpnt), TYPE_NO_DEVICE, sizeof(get_scsi(shpnt))); /* this is essential ! */ - for (lun = 0; lun < 8; lun++) { - /* mark the adapter at its pun on all luns */ - get_scsi(shpnt)[subsystem_pun(shpnt)][lun] = TYPE_IBM_SCSI_ADAPTER; - get_ldn(shpnt)[subsystem_pun(shpnt)][lun] = MAX_LOG_DEV; /* make sure, the subsystem - ldn is active for all - luns. */ - } - probe_display(0); /* Supercool display usage during SCSI-probing. */ - /* This makes sense, when booting without any */ - /* monitor connected on model XX95. */ - - /* STEP 1: */ - adapter_speed(shpnt) = global_adapter_speed; - speedrun = adapter_speed(shpnt); - while (immediate_feature(shpnt, speedrun, adapter_timeout) == 2) { - probe_display(1); - if (speedrun == 7) - panic("IBM MCA SCSI: Cannot set Synchronous-Transfer-Rate!\n"); - speedrun++; - if (speedrun > 7) - speedrun = 7; - } - adapter_speed(shpnt) = speedrun; - /* Get detailed information about the current adapter, necessary for - * device operations: */ - num_bus = probe_bus_mode(shpnt); - - /* num_bus contains only valid data for the F/W adapter! */ - if (adaptertype == IBM_SCSI2_FW) { /* F/W SCSI adapter: */ - /* F/W adapter PUN-space extension evaluation: */ - if (num_bus) { - printk(KERN_INFO "IBM MCA SCSI: Separate bus mode (wide-addressing enabled)\n"); - subsystem_maxid(shpnt) = 16; - } else { - printk(KERN_INFO "IBM MCA SCSI: Combined bus mode (wide-addressing disabled)\n"); - subsystem_maxid(shpnt) = 8; - } - printk(KERN_INFO "IBM MCA SCSI: Sync.-Rate (F/W: 20, Int.: 10, Ext.: %s) MBytes/s\n", ibmrate(speedrun, adaptertype)); - } else /* all other IBM SCSI adapters: */ - printk(KERN_INFO "IBM MCA SCSI: Synchronous-SCSI-Transfer-Rate: %s MBytes/s\n", ibmrate(speedrun, adaptertype)); - - /* assign correct PUN device space */ - max_pun = subsystem_maxid(shpnt); - -#ifdef IM_DEBUG_PROBE - printk("IBM MCA SCSI: Current SCSI-host index: %d\n", shpnt); - printk("IBM MCA SCSI: Removing default logical SCSI-device mapping."); -#else - printk(KERN_INFO "IBM MCA SCSI: Dev. Order: %s, Mapping (takes <2min): ", (ibm_ansi_order) ? "ANSI" : "New"); -#endif - for (ldn = 0; ldn < MAX_LOG_DEV; ldn++) { - probe_display(1); -#ifdef IM_DEBUG_PROBE - printk("."); -#endif - immediate_assign(shpnt, 0, 0, ldn, REMOVE_LDN); /* remove ldn (wherever) */ - } - lun = 0; /* default lun is 0 */ -#ifndef IM_DEBUG_PROBE - printk("cleared,"); -#endif - /* STEP 2: */ -#ifdef IM_DEBUG_PROBE - printk("\nIBM MCA SCSI: Scanning SCSI-devices."); -#endif - for (id = 0; id < max_pun; id++) -#ifdef CONFIG_SCSI_MULTI_LUN - for (lun = 0; lun < 8; lun++) -#endif - { - probe_display(1); -#ifdef IM_DEBUG_PROBE - printk("."); -#endif - if (id != subsystem_pun(shpnt)) { - /* if pun is not the adapter: */ - /* set ldn=0 to pun,lun */ - immediate_assign(shpnt, id, lun, PROBE_LDN, SET_LDN); - if (device_inquiry(shpnt, PROBE_LDN)) { /* probe device */ - get_scsi(shpnt)[id][lun] = (unsigned char) (ld(shpnt)[PROBE_LDN].buf[0]); - /* entry, even for NO_LUN */ - if (ld(shpnt)[PROBE_LDN].buf[0] != TYPE_NO_LUN) - count_devices++; /* a existing device is found */ - } - /* remove ldn */ - immediate_assign(shpnt, id, lun, PROBE_LDN, REMOVE_LDN); - } - } -#ifndef IM_DEBUG_PROBE - printk("scanned,"); -#endif - /* STEP 3: */ -#ifdef IM_DEBUG_PROBE - printk("\nIBM MCA SCSI: Mapping SCSI-devices."); -#endif - ldn = 0; - lun = 0; -#ifdef CONFIG_SCSI_MULTI_LUN - for (lun = 0; lun < 8 && ldn < MAX_LOG_DEV; lun++) -#endif - for (id = 0; id < max_pun && ldn < MAX_LOG_DEV; id++) { - probe_display(1); -#ifdef IM_DEBUG_PROBE - printk("."); -#endif - if (id != subsystem_pun(shpnt)) { - if (get_scsi(shpnt)[id][lun] != TYPE_NO_LUN && get_scsi(shpnt)[id][lun] != TYPE_NO_DEVICE) { - /* Only map if accepted type. Always enter for - lun == 0 to get no gaps into ldn-mapping for ldn<7. */ - immediate_assign(shpnt, id, lun, ldn, SET_LDN); - get_ldn(shpnt)[id][lun] = ldn; /* map ldn */ - if (device_exists(shpnt, ldn, &ld(shpnt)[ldn].block_length, &ld(shpnt)[ldn].device_type)) { -#ifdef CONFIG_IBMMCA_SCSI_DEV_RESET - printk("resetting device at ldn=%x ... ", ldn); - immediate_reset(shpnt, ldn); -#endif - ldn++; - } else { - /* device vanished, probably because we don't know how to - * handle it or because it has problems */ - if (lun > 0) { - /* remove mapping */ - get_ldn(shpnt)[id][lun] = TYPE_NO_DEVICE; - immediate_assign(shpnt, 0, 0, ldn, REMOVE_LDN); - } else - ldn++; - } - } else if (lun == 0) { - /* map lun == 0, even if no device exists */ - immediate_assign(shpnt, id, lun, ldn, SET_LDN); - get_ldn(shpnt)[id][lun] = ldn; /* map ldn */ - ldn++; - } - } - } - /* STEP 4: */ - - /* map remaining ldns to non-existing devices */ - for (lun = 1; lun < 8 && ldn < MAX_LOG_DEV; lun++) - for (id = 0; id < max_pun && ldn < MAX_LOG_DEV; id++) { - if (get_scsi(shpnt)[id][lun] == TYPE_NO_LUN || get_scsi(shpnt)[id][lun] == TYPE_NO_DEVICE) { - probe_display(1); - /* Map remaining ldns only to NON-existing pun,lun - combinations to make sure an inquiry will fail. - For MULTI_LUN, it is needed to avoid adapter autonome - SCSI-remapping. */ - immediate_assign(shpnt, id, lun, ldn, SET_LDN); - get_ldn(shpnt)[id][lun] = ldn; - ldn++; - } - } -#ifndef IM_DEBUG_PROBE - printk("mapped."); -#endif - printk("\n"); -#ifdef IM_DEBUG_PROBE - if (ibm_ansi_order) - printk("IBM MCA SCSI: Device order: IBM/ANSI (pun=7 is first).\n"); - else - printk("IBM MCA SCSI: Device order: New Industry Standard (pun=0 is first).\n"); -#endif - -#ifdef IM_DEBUG_PROBE - /* Show the physical and logical mapping during boot. */ - printk("IBM MCA SCSI: Determined SCSI-device-mapping:\n"); - printk(" Physical SCSI-Device Map Logical SCSI-Device Map\n"); - printk("ID\\LUN 0 1 2 3 4 5 6 7 ID\\LUN 0 1 2 3 4 5 6 7\n"); - for (id = 0; id < max_pun; id++) { - printk("%2d ", id); - for (lun = 0; lun < 8; lun++) - printk("%2s ", ti_p(get_scsi(shpnt)[id][lun])); - printk(" %2d ", id); - for (lun = 0; lun < 8; lun++) - printk("%2s ", ti_l(get_ldn(shpnt)[id][lun])); - printk("\n"); - } -#endif - - /* assign total number of found SCSI-devices to the statistics struct */ - IBM_DS(shpnt).total_scsi_devices = count_devices; - - /* decide for output in /proc-filesystem, if the configuration of - SCSI-devices makes dynamical reassignment of devices necessary */ - if (count_devices >= MAX_LOG_DEV) - IBM_DS(shpnt).dyn_flag = 1; /* dynamical assignment is necessary */ - else - IBM_DS(shpnt).dyn_flag = 0; /* dynamical assignment is not necessary */ - - /* If no SCSI-devices are assigned, return 1 in order to cause message. */ - if (ldn == 0) - printk("IBM MCA SCSI: Warning: No SCSI-devices found/assigned!\n"); - - /* reset the counters for statistics on the current adapter */ - IBM_DS(shpnt).scbs = 0; - IBM_DS(shpnt).long_scbs = 0; - IBM_DS(shpnt).total_accesses = 0; - IBM_DS(shpnt).total_interrupts = 0; - IBM_DS(shpnt).dynamical_assignments = 0; - memset(IBM_DS(shpnt).ldn_access, 0x0, sizeof(IBM_DS(shpnt).ldn_access)); - memset(IBM_DS(shpnt).ldn_read_access, 0x0, sizeof(IBM_DS(shpnt).ldn_read_access)); - memset(IBM_DS(shpnt).ldn_write_access, 0x0, sizeof(IBM_DS(shpnt).ldn_write_access)); - memset(IBM_DS(shpnt).ldn_inquiry_access, 0x0, sizeof(IBM_DS(shpnt).ldn_inquiry_access)); - memset(IBM_DS(shpnt).ldn_modeselect_access, 0x0, sizeof(IBM_DS(shpnt).ldn_modeselect_access)); - memset(IBM_DS(shpnt).ldn_assignments, 0x0, sizeof(IBM_DS(shpnt).ldn_assignments)); - probe_display(0); - return; -} - -static int device_exists(struct Scsi_Host *shpnt, int ldn, int *block_length, int *device_type) -{ - unsigned char *buf; - /* if no valid device found, return immediately with 0 */ - if (!(device_inquiry(shpnt, ldn))) - return 0; - buf = (unsigned char *) (&(ld(shpnt)[ldn].buf)); - if (*buf == TYPE_ROM) { - *device_type = TYPE_ROM; - *block_length = 2048; /* (standard blocksize for yellow-/red-book) */ - return 1; - } - if (*buf == TYPE_WORM) { - *device_type = TYPE_WORM; - *block_length = 2048; - return 1; - } - if (*buf == TYPE_DISK) { - *device_type = TYPE_DISK; - if (read_capacity(shpnt, ldn)) { - *block_length = *(buf + 7) + (*(buf + 6) << 8) + (*(buf + 5) << 16) + (*(buf + 4) << 24); - return 1; - } else - return 0; - } - if (*buf == TYPE_MOD) { - *device_type = TYPE_MOD; - if (read_capacity(shpnt, ldn)) { - *block_length = *(buf + 7) + (*(buf + 6) << 8) + (*(buf + 5) << 16) + (*(buf + 4) << 24); - return 1; - } else - return 0; - } - if (*buf == TYPE_TAPE) { - *device_type = TYPE_TAPE; - *block_length = 0; /* not in use (setting by mt and mtst in op.) */ - return 1; - } - if (*buf == TYPE_PROCESSOR) { - *device_type = TYPE_PROCESSOR; - *block_length = 0; /* they set their stuff on drivers */ - return 1; - } - if (*buf == TYPE_SCANNER) { - *device_type = TYPE_SCANNER; - *block_length = 0; /* they set their stuff on drivers */ - return 1; - } - if (*buf == TYPE_MEDIUM_CHANGER) { - *device_type = TYPE_MEDIUM_CHANGER; - *block_length = 0; /* One never knows, what to expect on a medium - changer device. */ - return 1; - } - return 0; -} - -static void internal_ibmmca_scsi_setup(char *str, int *ints) -{ - int i, j, io_base, id_base; - char *token; - - io_base = 0; - id_base = 0; - if (str) { - j = 0; - while ((token = strsep(&str, ",")) != NULL) { - if (!strcmp(token, "activity")) - display_mode |= LED_ACTIVITY; - if (!strcmp(token, "display")) - display_mode |= LED_DISP; - if (!strcmp(token, "adisplay")) - display_mode |= LED_ADISP; - if (!strcmp(token, "normal")) - ibm_ansi_order = 0; - if (!strcmp(token, "ansi")) - ibm_ansi_order = 1; - if (!strcmp(token, "fast")) - global_adapter_speed = 0; - if (!strcmp(token, "medium")) - global_adapter_speed = 4; - if (!strcmp(token, "slow")) - global_adapter_speed = 7; - if ((*token == '-') || (isdigit(*token))) { - if (!(j % 2) && (io_base < IM_MAX_HOSTS)) - io_port[io_base++] = simple_strtoul(token, NULL, 0); - if ((j % 2) && (id_base < IM_MAX_HOSTS)) - scsi_id[id_base++] = simple_strtoul(token, NULL, 0); - j++; - } - } - } else if (ints) { - for (i = 0; i < IM_MAX_HOSTS && 2 * i + 2 < ints[0]; i++) { - io_port[i] = ints[2 * i + 2]; - scsi_id[i] = ints[2 * i + 2]; - } - } - return; -} - -#if 0 - FIXME NEED TO MOVE TO SYSFS - -static int ibmmca_getinfo(char *buf, int slot, void *dev_id) -{ - struct Scsi_Host *shpnt; - int len, speciale, connectore, k; - unsigned int pos[8]; - unsigned long flags; - struct Scsi_Host *dev = dev_id; - - spin_lock_irqsave(dev->host_lock, flags); - - shpnt = dev; /* assign host-structure to local pointer */ - len = 0; /* set filled text-buffer index to 0 */ - /* get the _special contents of the hostdata structure */ - speciale = ((struct ibmmca_hostdata *) shpnt->hostdata)->_special; - connectore = ((struct ibmmca_hostdata *) shpnt->hostdata)->_connector_size; - for (k = 2; k < 4; k++) - pos[k] = ((struct ibmmca_hostdata *) shpnt->hostdata)->_pos[k]; - if (speciale == FORCED_DETECTION) { /* forced detection */ - len += sprintf(buf + len, - "Adapter category: forced detected\n" "***************************************\n" "*** Forced detected SCSI Adapter ***\n" "*** No chip-information available ***\n" "***************************************\n"); - } else if (speciale == INTEGRATED_SCSI) { - /* if the integrated subsystem has been found automatically: */ - len += sprintf(buf + len, - "Adapter category: integrated\n" "Chip revision level: %d\n" "Chip status: %s\n" "8 kByte NVRAM status: %s\n", ((pos[2] & 0xf0) >> 4), (pos[2] & 1) ? "enabled" : "disabled", (pos[2] & 2) ? "locked" : "accessible"); - } else if ((speciale >= 0) && (speciale < ARRAY_SIZE(subsys_list))) { - /* if the subsystem is a slot adapter */ - len += sprintf(buf + len, "Adapter category: slot-card\n" "ROM Segment Address: "); - if ((pos[2] & 0xf0) == 0xf0) - len += sprintf(buf + len, "off\n"); - else - len += sprintf(buf + len, "0x%x\n", ((pos[2] & 0xf0) << 13) + 0xc0000); - len += sprintf(buf + len, "Chip status: %s\n", (pos[2] & 1) ? "enabled" : "disabled"); - len += sprintf(buf + len, "Adapter I/O Offset: 0x%x\n", ((pos[2] & 0x0e) << 2)); - } else { - len += sprintf(buf + len, "Adapter category: unknown\n"); - } - /* common subsystem information to write to the slotn file */ - len += sprintf(buf + len, "Subsystem PUN: %d\n", shpnt->this_id); - len += sprintf(buf + len, "I/O base address range: 0x%x-0x%x\n", (unsigned int) (shpnt->io_port), (unsigned int) (shpnt->io_port + 7)); - len += sprintf(buf + len, "MCA-slot size: %d bits", connectore); - /* Now make sure, the bufferlength is devidable by 4 to avoid - * paging problems of the buffer. */ - while (len % sizeof(int) != (sizeof(int) - 1)) - len += sprintf(buf + len, " "); - len += sprintf(buf + len, "\n"); - - spin_unlock_irqrestore(shpnt->host_lock, flags); - - return len; -} -#endif - -static struct scsi_host_template ibmmca_driver_template = { - .proc_name = "ibmmca", - .proc_info = ibmmca_proc_info, - .name = "IBM SCSI-Subsystem", - .queuecommand = ibmmca_queuecommand, - .eh_abort_handler = ibmmca_abort, - .eh_host_reset_handler = ibmmca_host_reset, - .bios_param = ibmmca_biosparam, - .can_queue = 16, - .this_id = 7, - .sg_tablesize = 16, - .cmd_per_lun = 1, - .use_clustering = ENABLE_CLUSTERING, -}; - -static int ibmmca_probe(struct device *dev) -{ - struct Scsi_Host *shpnt; - int port, id, i, j, k, irq, enabled, ret = -EINVAL; - struct mca_device *mca_dev = to_mca_device(dev); - const char *description = ibmmca_description[mca_dev->index]; - - /* First of all, print the version number of the driver. This is - * important to allow better user bugreports in case of already - * having problems with the MCA_bus probing. */ - printk(KERN_INFO "IBM MCA SCSI: Version %s\n", IBMMCA_SCSI_DRIVER_VERSION); - /* The POS2-register of all PS/2 model SCSI-subsystems has the following - * interpretation of bits: - * Bit 7 - 4 : Chip Revision ID (Release) - * Bit 3 - 2 : Reserved - * Bit 1 : 8k NVRAM Disabled - * Bit 0 : Chip Enable (EN-Signal) - * The POS3-register is interpreted as follows: - * Bit 7 - 5 : SCSI ID - * Bit 4 : Reserved = 0 - * Bit 3 - 0 : Reserved = 0 - * (taken from "IBM, PS/2 Hardware Interface Technical Reference, Common - * Interfaces (1991)"). - * In short words, this means, that IBM PS/2 machines only support - * 1 single subsystem by default. The slot-adapters must have another - * configuration on pos2. Here, one has to assume the following - * things for POS2-register: - * Bit 7 - 4 : Chip Revision ID (Release) - * Bit 3 - 1 : port offset factor - * Bit 0 : Chip Enable (EN-Signal) - * As I found a patch here, setting the IO-registers to 0x3540 forced, - * as there was a 0x05 in POS2 on a model 56, I assume, that the - * port 0x3540 must be fix for integrated SCSI-controllers. - * Ok, this discovery leads to the following implementation: (M.Lang) */ - - /* first look for the IBM SCSI integrated subsystem on the motherboard */ - for (j = 0; j < 8; j++) /* read the pos-information */ - pos[j] = mca_device_read_pos(mca_dev, j); - id = (pos[3] & 0xe0) >> 5; /* this is correct and represents the PUN */ - enabled = (pos[2] &0x01); - if (!enabled) { - printk(KERN_WARNING "IBM MCA SCSI: WARNING - Your SCSI-subsystem is disabled!\n"); - printk(KERN_WARNING " SCSI-operations may not work.\n"); - } - - /* pos2 = pos3 = 0xff if there is no integrated SCSI-subsystem present, but - * if we ignore the settings of all surrounding pos registers, it is not - * completely sufficient to only check pos2 and pos3. */ - /* Therefore, now the following if statement is used to - * make sure, we see a real integrated onboard SCSI-interface and no - * internal system information, which gets mapped to some pos registers - * on models 95xx. */ - if (mca_dev->slot == MCA_INTEGSCSI && - ((!pos[0] && !pos[1] && pos[2] > 0 && - pos[3] > 0 && !pos[4] && !pos[5] && - !pos[6] && !pos[7]) || - (pos[0] == 0xff && pos[1] == 0xff && - pos[2] < 0xff && pos[3] < 0xff && - pos[4] == 0xff && pos[5] == 0xff && - pos[6] == 0xff && pos[7] == 0xff))) { - irq = IM_IRQ; - port = IM_IO_PORT; - } else { - irq = IM_IRQ; - port = IM_IO_PORT + ((pos[2] &0x0e) << 2); - if ((mca_dev->index == IBM_SCSI2_FW) && (pos[6] != 0)) { - printk(KERN_ERR "IBM MCA SCSI: ERROR - Wrong POS(6)-register setting!\n"); - printk(KERN_ERR " Impossible to determine adapter PUN!\n"); - printk(KERN_ERR " Guessing adapter PUN = 7.\n"); - id = 7; - } else { - id = (pos[3] & 0xe0) >> 5; /* get subsystem PUN */ - if (mca_dev->index == IBM_SCSI2_FW) { - id |= (pos[3] & 0x10) >> 1; /* get subsystem PUN high-bit - * for F/W adapters */ - } - } - if ((mca_dev->index == IBM_SCSI2_FW) && - (pos[4] & 0x01) && (pos[6] == 0)) { - /* IRQ11 is used by SCSI-2 F/W Adapter/A */ - printk(KERN_DEBUG "IBM MCA SCSI: SCSI-2 F/W adapter needs IRQ 11.\n"); - irq = IM_IRQ_FW; - } - } - - - - /* give detailed information on the subsystem. This helps me - * additionally during debugging and analyzing bug-reports. */ - printk(KERN_INFO "IBM MCA SCSI: %s found, io=0x%x, scsi id=%d,\n", - description, port, id); - if (mca_dev->slot == MCA_INTEGSCSI) - printk(KERN_INFO " chip rev.=%d, 8K NVRAM=%s, subsystem=%s\n", ((pos[2] & 0xf0) >> 4), (pos[2] & 2) ? "locked" : "accessible", (pos[2] & 1) ? "enabled." : "disabled."); - else { - if ((pos[2] & 0xf0) == 0xf0) - printk(KERN_DEBUG " ROM Addr.=off,"); - else - printk(KERN_DEBUG " ROM Addr.=0x%x,", ((pos[2] & 0xf0) << 13) + 0xc0000); - - printk(KERN_DEBUG " port-offset=0x%x, subsystem=%s\n", ((pos[2] & 0x0e) << 2), (pos[2] & 1) ? "enabled." : "disabled."); - } - - /* check I/O region */ - if (!request_region(port, IM_N_IO_PORT, description)) { - printk(KERN_ERR "IBM MCA SCSI: Unable to get I/O region 0x%x-0x%x (%d ports).\n", port, port + IM_N_IO_PORT - 1, IM_N_IO_PORT); - goto out_fail; - } - - /* register host */ - shpnt = scsi_host_alloc(&ibmmca_driver_template, - sizeof(struct ibmmca_hostdata)); - if (!shpnt) { - printk(KERN_ERR "IBM MCA SCSI: Unable to register host.\n"); - goto out_release; - } - - dev_set_drvdata(dev, shpnt); - if(request_irq(irq, interrupt_handler, IRQF_SHARED, description, dev)) { - printk(KERN_ERR "IBM MCA SCSI: failed to request interrupt %d\n", irq); - goto out_free_host; - } - - /* request I/O region */ - special(shpnt) = mca_dev->index; /* important assignment or else crash! */ - subsystem_connector_size(shpnt) = 0; /* preset slot-size */ - shpnt->irq = irq; /* assign necessary stuff for the adapter */ - shpnt->io_port = port; - shpnt->n_io_port = IM_N_IO_PORT; - shpnt->this_id = id; - shpnt->max_id = 8; /* 8 PUNs are default */ - /* now, the SCSI-subsystem is connected to Linux */ - -#ifdef IM_DEBUG_PROBE - ctrl = (unsigned int) (inb(IM_CTR_REG(found))); /* get control-register status */ - printk("IBM MCA SCSI: Control Register contents: %x, status: %x\n", ctrl, inb(IM_STAT_REG(found))); - printk("IBM MCA SCSI: This adapters' POS-registers: "); - for (i = 0; i < 8; i++) - printk("%x ", pos[i]); - printk("\n"); -#endif - reset_status(shpnt) = IM_RESET_NOT_IN_PROGRESS; - - for (i = 0; i < 16; i++) /* reset the tables */ - for (j = 0; j < 8; j++) - get_ldn(shpnt)[i][j] = MAX_LOG_DEV; - - /* check which logical devices exist */ - /* after this line, local interrupting is possible: */ - local_checking_phase_flag(shpnt) = 1; - check_devices(shpnt, mca_dev->index); /* call by value, using the global variable hosts */ - local_checking_phase_flag(shpnt) = 0; - - /* an ibm mca subsystem has been detected */ - - for (k = 2; k < 7; k++) - ((struct ibmmca_hostdata *) shpnt->hostdata)->_pos[k] = pos[k]; - ((struct ibmmca_hostdata *) shpnt->hostdata)->_special = INTEGRATED_SCSI; - mca_device_set_name(mca_dev, description); - /* FIXME: NEED TO REPLUMB TO SYSFS - mca_set_adapter_procfn(MCA_INTEGSCSI, (MCA_ProcFn) ibmmca_getinfo, shpnt); - */ - mca_device_set_claim(mca_dev, 1); - if (scsi_add_host(shpnt, dev)) { - dev_printk(KERN_ERR, dev, "IBM MCA SCSI: scsi_add_host failed\n"); - goto out_free_host; - } - scsi_scan_host(shpnt); - - return 0; - out_free_host: - scsi_host_put(shpnt); - out_release: - release_region(port, IM_N_IO_PORT); - out_fail: - return ret; -} - -static int __devexit ibmmca_remove(struct device *dev) -{ - struct Scsi_Host *shpnt = dev_get_drvdata(dev); - scsi_remove_host(shpnt); - release_region(shpnt->io_port, shpnt->n_io_port); - free_irq(shpnt->irq, dev); - scsi_host_put(shpnt); - return 0; -} - -/* The following routine is the SCSI command queue for the midlevel driver */ -static int ibmmca_queuecommand_lck(Scsi_Cmnd * cmd, void (*done) (Scsi_Cmnd *)) -{ - unsigned int ldn; - unsigned int scsi_cmd; - struct im_scb *scb; - struct Scsi_Host *shpnt; - int current_ldn; - int id, lun; - int target; - int max_pun; - int i; - struct scatterlist *sg; - - shpnt = cmd->device->host; - - max_pun = subsystem_maxid(shpnt); - if (ibm_ansi_order) { - target = max_pun - 1 - cmd->device->id; - if ((target <= subsystem_pun(shpnt)) && (cmd->device->id <= subsystem_pun(shpnt))) - target--; - else if ((target >= subsystem_pun(shpnt)) && (cmd->device->id >= subsystem_pun(shpnt))) - target++; - } else - target = cmd->device->id; - - /* if (target,lun) is NO LUN or not existing at all, return error */ - if ((get_scsi(shpnt)[target][cmd->device->lun] == TYPE_NO_LUN) || (get_scsi(shpnt)[target][cmd->device->lun] == TYPE_NO_DEVICE)) { - cmd->result = DID_NO_CONNECT << 16; - if (done) - done(cmd); - return 0; - } - - /*if (target,lun) unassigned, do further checks... */ - ldn = get_ldn(shpnt)[target][cmd->device->lun]; - if (ldn >= MAX_LOG_DEV) { /* on invalid ldn do special stuff */ - if (ldn > MAX_LOG_DEV) { /* dynamical remapping if ldn unassigned */ - current_ldn = next_ldn(shpnt); /* stop-value for one circle */ - while (ld(shpnt)[next_ldn(shpnt)].cmd) { /* search for a occupied, but not in */ - /* command-processing ldn. */ - next_ldn(shpnt)++; - if (next_ldn(shpnt) >= MAX_LOG_DEV) - next_ldn(shpnt) = 7; - if (current_ldn == next_ldn(shpnt)) { /* One circle done ? */ - /* no non-processing ldn found */ - scmd_printk(KERN_WARNING, cmd, - "IBM MCA SCSI: Cannot assign SCSI-device dynamically!\n" - " On ldn 7-14 SCSI-commands everywhere in progress.\n" - " Reporting DID_NO_CONNECT for device.\n"); - cmd->result = DID_NO_CONNECT << 16; /* return no connect */ - if (done) - done(cmd); - return 0; - } - } - - /* unmap non-processing ldn */ - for (id = 0; id < max_pun; id++) - for (lun = 0; lun < 8; lun++) { - if (get_ldn(shpnt)[id][lun] == next_ldn(shpnt)) { - get_ldn(shpnt)[id][lun] = TYPE_NO_DEVICE; - get_scsi(shpnt)[id][lun] = TYPE_NO_DEVICE; - /* unmap entry */ - } - } - /* set reduced interrupt_handler-mode for checking */ - local_checking_phase_flag(shpnt) = 1; - /* map found ldn to pun,lun */ - get_ldn(shpnt)[target][cmd->device->lun] = next_ldn(shpnt); - /* change ldn to the right value, that is now next_ldn */ - ldn = next_ldn(shpnt); - /* unassign all ldns (pun,lun,ldn does not matter for remove) */ - immediate_assign(shpnt, 0, 0, 0, REMOVE_LDN); - /* set only LDN for remapped device */ - immediate_assign(shpnt, target, cmd->device->lun, ldn, SET_LDN); - /* get device information for ld[ldn] */ - if (device_exists(shpnt, ldn, &ld(shpnt)[ldn].block_length, &ld(shpnt)[ldn].device_type)) { - ld(shpnt)[ldn].cmd = NULL; /* To prevent panic set 0, because - devices that were not assigned, - should have nothing in progress. */ - get_scsi(shpnt)[target][cmd->device->lun] = ld(shpnt)[ldn].device_type; - /* increase assignment counters for statistics in /proc */ - IBM_DS(shpnt).dynamical_assignments++; - IBM_DS(shpnt).ldn_assignments[ldn]++; - } else - /* panic here, because a device, found at boottime has - vanished */ - panic("IBM MCA SCSI: ldn=0x%x, SCSI-device on (%d,%d) vanished!\n", ldn, target, cmd->device->lun); - /* unassign again all ldns (pun,lun,ldn does not matter for remove) */ - immediate_assign(shpnt, 0, 0, 0, REMOVE_LDN); - /* remap all ldns, as written in the pun/lun table */ - lun = 0; -#ifdef CONFIG_SCSI_MULTI_LUN - for (lun = 0; lun < 8; lun++) -#endif - for (id = 0; id < max_pun; id++) { - if (get_ldn(shpnt)[id][lun] <= MAX_LOG_DEV) - immediate_assign(shpnt, id, lun, get_ldn(shpnt)[id][lun], SET_LDN); - } - /* set back to normal interrupt_handling */ - local_checking_phase_flag(shpnt) = 0; -#ifdef IM_DEBUG_PROBE - /* Information on syslog terminal */ - printk("IBM MCA SCSI: ldn=0x%x dynamically reassigned to (%d,%d).\n", ldn, target, cmd->device->lun); -#endif - /* increase next_ldn for next dynamical assignment */ - next_ldn(shpnt)++; - if (next_ldn(shpnt) >= MAX_LOG_DEV) - next_ldn(shpnt) = 7; - } else { /* wall against Linux accesses to the subsystem adapter */ - cmd->result = DID_BAD_TARGET << 16; - if (done) - done(cmd); - return 0; - } - } - - /*verify there is no command already in progress for this log dev */ - if (ld(shpnt)[ldn].cmd) - panic("IBM MCA SCSI: cmd already in progress for this ldn.\n"); - - /*save done in cmd, and save cmd for the interrupt handler */ - cmd->scsi_done = done; - ld(shpnt)[ldn].cmd = cmd; - - /*fill scb information independent of the scsi command */ - scb = &(ld(shpnt)[ldn].scb); - ld(shpnt)[ldn].tsb.dev_status = 0; - scb->enable = IM_REPORT_TSB_ONLY_ON_ERROR | IM_RETRY_ENABLE; - scb->tsb_adr = isa_virt_to_bus(&(ld(shpnt)[ldn].tsb)); - scsi_cmd = cmd->cmnd[0]; - - if (scsi_sg_count(cmd)) { - BUG_ON(scsi_sg_count(cmd) > 16); - - scsi_for_each_sg(cmd, sg, scsi_sg_count(cmd), i) { - ld(shpnt)[ldn].sge[i].address = (void *) (isa_page_to_bus(sg_page(sg)) + sg->offset); - ld(shpnt)[ldn].sge[i].byte_length = sg->length; - } - scb->enable |= IM_POINTER_TO_LIST; - scb->sys_buf_adr = isa_virt_to_bus(&(ld(shpnt)[ldn].sge[0])); - scb->sys_buf_length = scsi_sg_count(cmd) * sizeof(struct im_sge); - } else { - scb->sys_buf_adr = isa_virt_to_bus(scsi_sglist(cmd)); - /* recent Linux midlevel SCSI places 1024 byte for inquiry - * command. Far too much for old PS/2 hardware. */ - switch (scsi_cmd) { - /* avoid command errors by setting bufferlengths to - * ANSI-standard. Beware of forcing it to 255, - * this could SEGV the kernel!!! */ - case INQUIRY: - case REQUEST_SENSE: - case MODE_SENSE: - case MODE_SELECT: - if (scsi_bufflen(cmd) > 255) - scb->sys_buf_length = 255; - else - scb->sys_buf_length = scsi_bufflen(cmd); - break; - case TEST_UNIT_READY: - scb->sys_buf_length = 0; - break; - default: - scb->sys_buf_length = scsi_bufflen(cmd); - break; - } - } - /*fill scb information dependent on scsi command */ - -#ifdef IM_DEBUG_CMD - printk("issue scsi cmd=%02x to ldn=%d\n", scsi_cmd, ldn); -#endif - - /* for specific device-type debugging: */ -#ifdef IM_DEBUG_CMD_SPEC_DEV - if (ld(shpnt)[ldn].device_type == IM_DEBUG_CMD_DEVICE) - printk("(SCSI-device-type=0x%x) issue scsi cmd=%02x to ldn=%d\n", ld(shpnt)[ldn].device_type, scsi_cmd, ldn); -#endif - - /* for possible panics store current command */ - last_scsi_command(shpnt)[ldn] = scsi_cmd; - last_scsi_type(shpnt)[ldn] = IM_SCB; - /* update statistical info */ - IBM_DS(shpnt).total_accesses++; - IBM_DS(shpnt).ldn_access[ldn]++; - - switch (scsi_cmd) { - case READ_6: - case WRITE_6: - case READ_10: - case WRITE_10: - case READ_12: - case WRITE_12: - /* Distinguish between disk and other devices. Only disks (that are the - most frequently accessed devices) should be supported by the - IBM-SCSI-Subsystem commands. */ - switch (ld(shpnt)[ldn].device_type) { - case TYPE_DISK: /* for harddisks enter here ... */ - case TYPE_MOD: /* ... try it also for MO-drives (send flames as */ - /* you like, if this won't work.) */ - if (scsi_cmd == READ_6 || scsi_cmd == READ_10 || scsi_cmd == READ_12) { - /* read command preparations */ - scb->enable |= IM_READ_CONTROL; - IBM_DS(shpnt).ldn_read_access[ldn]++; /* increase READ-access on ldn stat. */ - scb->command = IM_READ_DATA_CMD | IM_NO_DISCONNECT; - } else { /* write command preparations */ - IBM_DS(shpnt).ldn_write_access[ldn]++; /* increase write-count on ldn stat. */ - scb->command = IM_WRITE_DATA_CMD | IM_NO_DISCONNECT; - } - if (scsi_cmd == READ_6 || scsi_cmd == WRITE_6) { - scb->u1.log_blk_adr = (((unsigned) cmd->cmnd[3]) << 0) | (((unsigned) cmd->cmnd[2]) << 8) | ((((unsigned) cmd->cmnd[1]) & 0x1f) << 16); - scb->u2.blk.count = (unsigned) cmd->cmnd[4]; - } else { - scb->u1.log_blk_adr = (((unsigned) cmd->cmnd[5]) << 0) | (((unsigned) cmd->cmnd[4]) << 8) | (((unsigned) cmd->cmnd[3]) << 16) | (((unsigned) cmd->cmnd[2]) << 24); - scb->u2.blk.count = (((unsigned) cmd->cmnd[8]) << 0) | (((unsigned) cmd->cmnd[7]) << 8); - } - last_scsi_logical_block(shpnt)[ldn] = scb->u1.log_blk_adr; - last_scsi_blockcount(shpnt)[ldn] = scb->u2.blk.count; - scb->u2.blk.length = ld(shpnt)[ldn].block_length; - break; - /* for other devices, enter here. Other types are not known by - Linux! TYPE_NO_LUN is forbidden as valid device. */ - case TYPE_ROM: - case TYPE_TAPE: - case TYPE_PROCESSOR: - case TYPE_WORM: - case TYPE_SCANNER: - case TYPE_MEDIUM_CHANGER: - /* If there is a sequential-device, IBM recommends to use - IM_OTHER_SCSI_CMD_CMD instead of subsystem READ/WRITE. - This includes CD-ROM devices, too, due to the partial sequential - read capabilities. */ - scb->command = IM_OTHER_SCSI_CMD_CMD; - if (scsi_cmd == READ_6 || scsi_cmd == READ_10 || scsi_cmd == READ_12) - /* enable READ */ - scb->enable |= IM_READ_CONTROL; - scb->enable |= IM_BYPASS_BUFFER; - scb->u1.scsi_cmd_length = cmd->cmd_len; - memcpy(scb->u2.scsi_command, cmd->cmnd, cmd->cmd_len); - last_scsi_type(shpnt)[ldn] = IM_LONG_SCB; - /* Read/write on this non-disk devices is also displayworthy, - so flash-up the LED/display. */ - break; - } - break; - case INQUIRY: - IBM_DS(shpnt).ldn_inquiry_access[ldn]++; - scb->command = IM_DEVICE_INQUIRY_CMD; - scb->enable |= IM_READ_CONTROL | IM_SUPRESS_EXCEPTION_SHORT | IM_BYPASS_BUFFER; - scb->u1.log_blk_adr = 0; - break; - case TEST_UNIT_READY: - scb->command = IM_OTHER_SCSI_CMD_CMD; - scb->enable |= IM_READ_CONTROL | IM_SUPRESS_EXCEPTION_SHORT | IM_BYPASS_BUFFER; - scb->u1.log_blk_adr = 0; - scb->u1.scsi_cmd_length = 6; - memcpy(scb->u2.scsi_command, cmd->cmnd, 6); - last_scsi_type(shpnt)[ldn] = IM_LONG_SCB; - break; - case READ_CAPACITY: - /* the length of system memory buffer must be exactly 8 bytes */ - scb->command = IM_READ_CAPACITY_CMD; - scb->enable |= IM_READ_CONTROL | IM_BYPASS_BUFFER; - if (scb->sys_buf_length > 8) - scb->sys_buf_length = 8; - break; - /* Commands that need read-only-mode (system <- device): */ - case REQUEST_SENSE: - scb->command = IM_REQUEST_SENSE_CMD; - scb->enable |= IM_READ_CONTROL | IM_SUPRESS_EXCEPTION_SHORT | IM_BYPASS_BUFFER; - break; - /* Commands that need write-only-mode (system -> device): */ - case MODE_SELECT: - case MODE_SELECT_10: - IBM_DS(shpnt).ldn_modeselect_access[ldn]++; - scb->command = IM_OTHER_SCSI_CMD_CMD; - scb->enable |= IM_SUPRESS_EXCEPTION_SHORT | IM_BYPASS_BUFFER; /*Select needs WRITE-enabled */ - scb->u1.scsi_cmd_length = cmd->cmd_len; - memcpy(scb->u2.scsi_command, cmd->cmnd, cmd->cmd_len); - last_scsi_type(shpnt)[ldn] = IM_LONG_SCB; - break; - /* For other commands, read-only is useful. Most other commands are - running without an input-data-block. */ - default: - scb->command = IM_OTHER_SCSI_CMD_CMD; - scb->enable |= IM_READ_CONTROL | IM_SUPRESS_EXCEPTION_SHORT | IM_BYPASS_BUFFER; - scb->u1.scsi_cmd_length = cmd->cmd_len; - memcpy(scb->u2.scsi_command, cmd->cmnd, cmd->cmd_len); - last_scsi_type(shpnt)[ldn] = IM_LONG_SCB; - break; - } - /*issue scb command, and return */ - if (++disk_rw_in_progress == 1) - PS2_DISK_LED_ON(shpnt->host_no, target); - - if (last_scsi_type(shpnt)[ldn] == IM_LONG_SCB) { - issue_cmd(shpnt, isa_virt_to_bus(scb), IM_LONG_SCB | ldn); - IBM_DS(shpnt).long_scbs++; - } else { - issue_cmd(shpnt, isa_virt_to_bus(scb), IM_SCB | ldn); - IBM_DS(shpnt).scbs++; - } - return 0; -} - -static DEF_SCSI_QCMD(ibmmca_queuecommand) - -static int __ibmmca_abort(Scsi_Cmnd * cmd) -{ - /* Abort does not work, as the adapter never generates an interrupt on - * whatever situation is simulated, even when really pending commands - * are running on the adapters' hardware ! */ - - struct Scsi_Host *shpnt; - unsigned int ldn; - void (*saved_done) (Scsi_Cmnd *); - int target; - int max_pun; - unsigned long imm_command; - -#ifdef IM_DEBUG_PROBE - printk("IBM MCA SCSI: Abort subroutine called...\n"); -#endif - - shpnt = cmd->device->host; - - max_pun = subsystem_maxid(shpnt); - if (ibm_ansi_order) { - target = max_pun - 1 - cmd->device->id; - if ((target <= subsystem_pun(shpnt)) && (cmd->device->id <= subsystem_pun(shpnt))) - target--; - else if ((target >= subsystem_pun(shpnt)) && (cmd->device->id >= subsystem_pun(shpnt))) - target++; - } else - target = cmd->device->id; - - /* get logical device number, and disable system interrupts */ - printk(KERN_WARNING "IBM MCA SCSI: Sending abort to device pun=%d, lun=%d.\n", target, cmd->device->lun); - ldn = get_ldn(shpnt)[target][cmd->device->lun]; - - /*if cmd for this ldn has already finished, no need to abort */ - if (!ld(shpnt)[ldn].cmd) { - return SUCCESS; - } - - /* Clear ld.cmd, save done function, install internal done, - * send abort immediate command (this enables sys. interrupts), - * and wait until the interrupt arrives. - */ - saved_done = cmd->scsi_done; - cmd->scsi_done = internal_done; - cmd->SCp.Status = 0; - last_scsi_command(shpnt)[ldn] = IM_ABORT_IMM_CMD; - last_scsi_type(shpnt)[ldn] = IM_IMM_CMD; - imm_command = inl(IM_CMD_REG(shpnt)); - imm_command &= (unsigned long) (0xffff0000); /* mask reserved stuff */ - imm_command |= (unsigned long) (IM_ABORT_IMM_CMD); - /* must wait for attention reg not busy */ - /* FIXME - timeout, politeness */ - while (1) { - if (!(inb(IM_STAT_REG(shpnt)) & IM_BUSY)) - break; - } - /* write registers and enable system interrupts */ - outl(imm_command, IM_CMD_REG(shpnt)); - outb(IM_IMM_CMD | ldn, IM_ATTN_REG(shpnt)); -#ifdef IM_DEBUG_PROBE - printk("IBM MCA SCSI: Abort queued to adapter...\n"); -#endif - spin_unlock_irq(shpnt->host_lock); - while (!cmd->SCp.Status) - yield(); - spin_lock_irq(shpnt->host_lock); - cmd->scsi_done = saved_done; -#ifdef IM_DEBUG_PROBE - printk("IBM MCA SCSI: Abort returned with adapter response...\n"); -#endif - - /*if abort went well, call saved done, then return success or error */ - if (cmd->result == (DID_ABORT << 16)) - { - cmd->result |= DID_ABORT << 16; - if (cmd->scsi_done) - (cmd->scsi_done) (cmd); - ld(shpnt)[ldn].cmd = NULL; -#ifdef IM_DEBUG_PROBE - printk("IBM MCA SCSI: Abort finished with success.\n"); -#endif - return SUCCESS; - } else { - cmd->result |= DID_NO_CONNECT << 16; - if (cmd->scsi_done) - (cmd->scsi_done) (cmd); - ld(shpnt)[ldn].cmd = NULL; -#ifdef IM_DEBUG_PROBE - printk("IBM MCA SCSI: Abort failed.\n"); -#endif - return FAILED; - } -} - -static int ibmmca_abort(Scsi_Cmnd * cmd) -{ - struct Scsi_Host *shpnt = cmd->device->host; - int rc; - - spin_lock_irq(shpnt->host_lock); - rc = __ibmmca_abort(cmd); - spin_unlock_irq(shpnt->host_lock); - - return rc; -} - -static int __ibmmca_host_reset(Scsi_Cmnd * cmd) -{ - struct Scsi_Host *shpnt; - Scsi_Cmnd *cmd_aid; - int ticks, i; - unsigned long imm_command; - - BUG_ON(cmd == NULL); - - ticks = IM_RESET_DELAY * HZ; - shpnt = cmd->device->host; - - if (local_checking_phase_flag(shpnt)) { - printk(KERN_WARNING "IBM MCA SCSI: unable to reset while checking devices.\n"); - return FAILED; - } - - /* issue reset immediate command to subsystem, and wait for interrupt */ - printk("IBM MCA SCSI: resetting all devices.\n"); - reset_status(shpnt) = IM_RESET_IN_PROGRESS; - last_scsi_command(shpnt)[0xf] = IM_RESET_IMM_CMD; - last_scsi_type(shpnt)[0xf] = IM_IMM_CMD; - imm_command = inl(IM_CMD_REG(shpnt)); - imm_command &= (unsigned long) (0xffff0000); /* mask reserved stuff */ - imm_command |= (unsigned long) (IM_RESET_IMM_CMD); - /* must wait for attention reg not busy */ - while (1) { - if (!(inb(IM_STAT_REG(shpnt)) & IM_BUSY)) - break; - spin_unlock_irq(shpnt->host_lock); - yield(); - spin_lock_irq(shpnt->host_lock); - } - /*write registers and enable system interrupts */ - outl(imm_command, IM_CMD_REG(shpnt)); - outb(IM_IMM_CMD | 0xf, IM_ATTN_REG(shpnt)); - /* wait for interrupt finished or intr_stat register to be set, as the - * interrupt will not be executed, while we are in here! */ - - /* FIXME: This is really really icky we so want a sleeping version of this ! */ - while (reset_status(shpnt) == IM_RESET_IN_PROGRESS && --ticks && ((inb(IM_INTR_REG(shpnt)) & 0x8f) != 0x8f)) { - udelay((1 + 999 / HZ) * 1000); - barrier(); - } - /* if reset did not complete, just return an error */ - if (!ticks) { - printk(KERN_ERR "IBM MCA SCSI: reset did not complete within %d seconds.\n", IM_RESET_DELAY); - reset_status(shpnt) = IM_RESET_FINISHED_FAIL; - return FAILED; - } - - if ((inb(IM_INTR_REG(shpnt)) & 0x8f) == 0x8f) { - /* analysis done by this routine and not by the intr-routine */ - if (inb(IM_INTR_REG(shpnt)) == 0xaf) - reset_status(shpnt) = IM_RESET_FINISHED_OK_NO_INT; - else if (inb(IM_INTR_REG(shpnt)) == 0xcf) - reset_status(shpnt) = IM_RESET_FINISHED_FAIL; - else /* failed, 4get it */ - reset_status(shpnt) = IM_RESET_NOT_IN_PROGRESS_NO_INT; - outb(IM_EOI | 0xf, IM_ATTN_REG(shpnt)); - } - - /* if reset failed, just return an error */ - if (reset_status(shpnt) == IM_RESET_FINISHED_FAIL) { - printk(KERN_ERR "IBM MCA SCSI: reset failed.\n"); - return FAILED; - } - - /* so reset finished ok - call outstanding done's, and return success */ - printk(KERN_INFO "IBM MCA SCSI: Reset successfully completed.\n"); - for (i = 0; i < MAX_LOG_DEV; i++) { - cmd_aid = ld(shpnt)[i].cmd; - if (cmd_aid && cmd_aid->scsi_done) { - ld(shpnt)[i].cmd = NULL; - cmd_aid->result = DID_RESET << 16; - } - } - return SUCCESS; -} - -static int ibmmca_host_reset(Scsi_Cmnd * cmd) -{ - struct Scsi_Host *shpnt = cmd->device->host; - int rc; - - spin_lock_irq(shpnt->host_lock); - rc = __ibmmca_host_reset(cmd); - spin_unlock_irq(shpnt->host_lock); - - return rc; -} - -static int ibmmca_biosparam(struct scsi_device *sdev, struct block_device *bdev, sector_t capacity, int *info) -{ - int size = capacity; - info[0] = 64; - info[1] = 32; - info[2] = size / (info[0] * info[1]); - if (info[2] >= 1024) { - info[0] = 128; - info[1] = 63; - info[2] = size / (info[0] * info[1]); - if (info[2] >= 1024) { - info[0] = 255; - info[1] = 63; - info[2] = size / (info[0] * info[1]); - if (info[2] >= 1024) - info[2] = 1023; - } - } - return 0; -} - -/* calculate percentage of total accesses on a ldn */ -static int ldn_access_load(struct Scsi_Host *shpnt, int ldn) -{ - if (IBM_DS(shpnt).total_accesses == 0) - return (0); - if (IBM_DS(shpnt).ldn_access[ldn] == 0) - return (0); - return (IBM_DS(shpnt).ldn_access[ldn] * 100) / IBM_DS(shpnt).total_accesses; -} - -/* calculate total amount of r/w-accesses */ -static int ldn_access_total_read_write(struct Scsi_Host *shpnt) -{ - int a; - int i; - - a = 0; - for (i = 0; i <= MAX_LOG_DEV; i++) - a += IBM_DS(shpnt).ldn_read_access[i] + IBM_DS(shpnt).ldn_write_access[i]; - return (a); -} - -static int ldn_access_total_inquiry(struct Scsi_Host *shpnt) -{ - int a; - int i; - - a = 0; - for (i = 0; i <= MAX_LOG_DEV; i++) - a += IBM_DS(shpnt).ldn_inquiry_access[i]; - return (a); -} - -static int ldn_access_total_modeselect(struct Scsi_Host *shpnt) -{ - int a; - int i; - - a = 0; - for (i = 0; i <= MAX_LOG_DEV; i++) - a += IBM_DS(shpnt).ldn_modeselect_access[i]; - return (a); -} - -/* routine to display info in the proc-fs-structure (a deluxe feature) */ -static int ibmmca_proc_info(struct Scsi_Host *shpnt, char *buffer, char **start, off_t offset, int length, int inout) -{ - int len = 0; - int i, id, lun; - unsigned long flags; - int max_pun; - - - spin_lock_irqsave(shpnt->host_lock, flags); /* Check it */ - - max_pun = subsystem_maxid(shpnt); - - len += sprintf(buffer + len, "\n IBM-SCSI-Subsystem-Linux-Driver, Version %s\n\n\n", IBMMCA_SCSI_DRIVER_VERSION); - len += sprintf(buffer + len, " SCSI Access-Statistics:\n"); - len += sprintf(buffer + len, " Device Scanning Order....: %s\n", (ibm_ansi_order) ? "IBM/ANSI" : "New Industry Standard"); -#ifdef CONFIG_SCSI_MULTI_LUN - len += sprintf(buffer + len, " Multiple LUN probing.....: Yes\n"); -#else - len += sprintf(buffer + len, " Multiple LUN probing.....: No\n"); -#endif - len += sprintf(buffer + len, " This Hostnumber..........: %d\n", shpnt->host_no); - len += sprintf(buffer + len, " Base I/O-Port............: 0x%x\n", (unsigned int) (IM_CMD_REG(shpnt))); - len += sprintf(buffer + len, " (Shared) IRQ.............: %d\n", IM_IRQ); - len += sprintf(buffer + len, " Total Interrupts.........: %d\n", IBM_DS(shpnt).total_interrupts); - len += sprintf(buffer + len, " Total SCSI Accesses......: %d\n", IBM_DS(shpnt).total_accesses); - len += sprintf(buffer + len, " Total short SCBs.........: %d\n", IBM_DS(shpnt).scbs); - len += sprintf(buffer + len, " Total long SCBs..........: %d\n", IBM_DS(shpnt).long_scbs); - len += sprintf(buffer + len, " Total SCSI READ/WRITE..: %d\n", ldn_access_total_read_write(shpnt)); - len += sprintf(buffer + len, " Total SCSI Inquiries...: %d\n", ldn_access_total_inquiry(shpnt)); - len += sprintf(buffer + len, " Total SCSI Modeselects.: %d\n", ldn_access_total_modeselect(shpnt)); - len += sprintf(buffer + len, " Total SCSI other cmds..: %d\n", IBM_DS(shpnt).total_accesses - ldn_access_total_read_write(shpnt) - - ldn_access_total_modeselect(shpnt) - - ldn_access_total_inquiry(shpnt)); - len += sprintf(buffer + len, " Total SCSI command fails.: %d\n\n", IBM_DS(shpnt).total_errors); - len += sprintf(buffer + len, " Logical-Device-Number (LDN) Access-Statistics:\n"); - len += sprintf(buffer + len, " LDN | Accesses [%%] | READ | WRITE | ASSIGNMENTS\n"); - len += sprintf(buffer + len, " -----|--------------|-----------|-----------|--------------\n"); - for (i = 0; i <= MAX_LOG_DEV; i++) - len += sprintf(buffer + len, " %2X | %3d | %8d | %8d | %8d\n", i, ldn_access_load(shpnt, i), IBM_DS(shpnt).ldn_read_access[i], IBM_DS(shpnt).ldn_write_access[i], IBM_DS(shpnt).ldn_assignments[i]); - len += sprintf(buffer + len, " -----------------------------------------------------------\n\n"); - len += sprintf(buffer + len, " Dynamical-LDN-Assignment-Statistics:\n"); - len += sprintf(buffer + len, " Number of physical SCSI-devices..: %d (+ Adapter)\n", IBM_DS(shpnt).total_scsi_devices); - len += sprintf(buffer + len, " Dynamical Assignment necessary...: %s\n", IBM_DS(shpnt).dyn_flag ? "Yes" : "No "); - len += sprintf(buffer + len, " Next LDN to be assigned..........: 0x%x\n", next_ldn(shpnt)); - len += sprintf(buffer + len, " Dynamical assignments done yet...: %d\n", IBM_DS(shpnt).dynamical_assignments); - len += sprintf(buffer + len, "\n Current SCSI-Device-Mapping:\n"); - len += sprintf(buffer + len, " Physical SCSI-Device Map Logical SCSI-Device Map\n"); - len += sprintf(buffer + len, " ID\\LUN 0 1 2 3 4 5 6 7 ID\\LUN 0 1 2 3 4 5 6 7\n"); - for (id = 0; id < max_pun; id++) { - len += sprintf(buffer + len, " %2d ", id); - for (lun = 0; lun < 8; lun++) - len += sprintf(buffer + len, "%2s ", ti_p(get_scsi(shpnt)[id][lun])); - len += sprintf(buffer + len, " %2d ", id); - for (lun = 0; lun < 8; lun++) - len += sprintf(buffer + len, "%2s ", ti_l(get_ldn(shpnt)[id][lun])); - len += sprintf(buffer + len, "\n"); - } - - len += sprintf(buffer + len, "(A = IBM-Subsystem, D = Harddisk, T = Tapedrive, P = Processor, W = WORM,\n"); - len += sprintf(buffer + len, " R = CD-ROM, S = Scanner, M = MO-Drive, C = Medium-Changer, + = unprovided LUN,\n"); - len += sprintf(buffer + len, " - = nothing found, nothing assigned or unprobed LUN)\n\n"); - - *start = buffer + offset; - len -= offset; - if (len > length) - len = length; - spin_unlock_irqrestore(shpnt->host_lock, flags); - return len; -} - -static int option_setup(char *str) -{ - int ints[IM_MAX_HOSTS]; - char *cur = str; - int i = 1; - - while (cur && isdigit(*cur) && i < IM_MAX_HOSTS) { - ints[i++] = simple_strtoul(cur, NULL, 0); - if ((cur = strchr(cur, ',')) != NULL) - cur++; - } - ints[0] = i - 1; - internal_ibmmca_scsi_setup(cur, ints); - return 1; -} - -__setup("ibmmcascsi=", option_setup); - -static struct mca_driver ibmmca_driver = { - .id_table = ibmmca_id_table, - .driver = { - .name = "ibmmca", - .bus = &mca_bus_type, - .probe = ibmmca_probe, - .remove = __devexit_p(ibmmca_remove), - }, -}; - -static int __init ibmmca_init(void) -{ -#ifdef MODULE - /* If the driver is run as module, read from conf.modules or cmd-line */ - if (boot_options) - option_setup(boot_options); -#endif - - return mca_register_driver_integrated(&ibmmca_driver, MCA_INTEGSCSI); -} - -static void __exit ibmmca_exit(void) -{ - mca_unregister_driver(&ibmmca_driver); -} - -module_init(ibmmca_init); -module_exit(ibmmca_exit); diff --git a/drivers/scsi/sim710.c b/drivers/scsi/sim710.c index 8ac6ce792b69..a318264a4ba1 100644 --- a/drivers/scsi/sim710.c +++ b/drivers/scsi/sim710.c @@ -17,7 +17,7 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *---------------------------------------------------------------------------- * - * MCA card detection code by Trent McNair. + * MCA card detection code by Trent McNair. (now deleted) * Fixes to not explicitly nul bss data from Xavier Bestel. * Some multiboard fixes from Rolf Eike Beer. * Auto probing of EISA config space from Trevor Hemsley. @@ -32,7 +32,6 @@ #include <linux/blkdev.h> #include <linux/device.h> #include <linux/init.h> -#include <linux/mca.h> #include <linux/eisa.h> #include <linux/interrupt.h> #include <scsi/scsi_host.h> @@ -43,7 +42,7 @@ #include "53c700.h" -/* Must be enough for both EISA and MCA */ +/* Must be enough for EISA */ #define MAX_SLOTS 8 static __u8 __initdata id_array[MAX_SLOTS] = { [0 ... MAX_SLOTS-1] = 7 }; @@ -89,7 +88,7 @@ param_setup(char *str) __setup("sim710=", param_setup); static struct scsi_host_template sim710_driver_template = { - .name = "LSI (Symbios) 710 MCA/EISA", + .name = "LSI (Symbios) 710 EISA", .proc_name = "sim710", .this_id = 7, .module = THIS_MODULE, @@ -169,114 +168,6 @@ sim710_device_remove(struct device *dev) return 0; } -#ifdef CONFIG_MCA - -/* CARD ID 01BB and 01BA use the same pos values */ -#define MCA_01BB_IO_PORTS { 0x0000, 0x0000, 0x0800, 0x0C00, 0x1000, 0x1400, \ - 0x1800, 0x1C00, 0x2000, 0x2400, 0x2800, \ - 0x2C00, 0x3000, 0x3400, 0x3800, 0x3C00, \ - 0x4000, 0x4400, 0x4800, 0x4C00, 0x5000 } - -#define MCA_01BB_IRQS { 3, 5, 11, 14 } - -/* CARD ID 004f */ -#define MCA_004F_IO_PORTS { 0x0000, 0x0200, 0x0300, 0x0400, 0x0500, 0x0600 } -#define MCA_004F_IRQS { 5, 9, 14 } - -static short sim710_mca_id_table[] = { 0x01bb, 0x01ba, 0x004f, 0}; - -static __init int -sim710_mca_probe(struct device *dev) -{ - struct mca_device *mca_dev = to_mca_device(dev); - int slot = mca_dev->slot; - int pos[3]; - unsigned int base; - int irq_vector; - short id = sim710_mca_id_table[mca_dev->index]; - static int io_004f_by_pos[] = MCA_004F_IO_PORTS; - static int irq_004f_by_pos[] = MCA_004F_IRQS; - static int io_01bb_by_pos[] = MCA_01BB_IO_PORTS; - static int irq_01bb_by_pos[] = MCA_01BB_IRQS; - char *name; - int clock; - - pos[0] = mca_device_read_stored_pos(mca_dev, 2); - pos[1] = mca_device_read_stored_pos(mca_dev, 3); - pos[2] = mca_device_read_stored_pos(mca_dev, 4); - - /* - * 01BB & 01BA port base by bits 7,6,5,4,3,2 in pos[2] - * - * 000000 <disabled> 001010 0x2800 - * 000001 <invalid> 001011 0x2C00 - * 000010 0x0800 001100 0x3000 - * 000011 0x0C00 001101 0x3400 - * 000100 0x1000 001110 0x3800 - * 000101 0x1400 001111 0x3C00 - * 000110 0x1800 010000 0x4000 - * 000111 0x1C00 010001 0x4400 - * 001000 0x2000 010010 0x4800 - * 001001 0x2400 010011 0x4C00 - * 010100 0x5000 - * - * 00F4 port base by bits 3,2,1 in pos[0] - * - * 000 <disabled> 001 0x200 - * 010 0x300 011 0x400 - * 100 0x500 101 0x600 - * - * 01BB & 01BA IRQ is specified in pos[0] bits 7 and 6: - * - * 00 3 10 11 - * 01 5 11 14 - * - * 00F4 IRQ specified by bits 6,5,4 in pos[0] - * - * 100 5 101 9 - * 110 14 - */ - - if (id == 0x01bb || id == 0x01ba) { - base = io_01bb_by_pos[(pos[2] & 0xFC) >> 2]; - irq_vector = - irq_01bb_by_pos[((pos[0] & 0xC0) >> 6)]; - - clock = 50; - if (id == 0x01bb) - name = "NCR 3360/3430 SCSI SubSystem"; - else - name = "NCR Dual SIOP SCSI Host Adapter Board"; - } else if ( id == 0x004f ) { - base = io_004f_by_pos[((pos[0] & 0x0E) >> 1)]; - irq_vector = - irq_004f_by_pos[((pos[0] & 0x70) >> 4) - 4]; - clock = 50; - name = "NCR 53c710 SCSI Host Adapter Board"; - } else { - return -ENODEV; - } - mca_device_set_name(mca_dev, name); - mca_device_set_claim(mca_dev, 1); - base = mca_device_transform_ioport(mca_dev, base); - irq_vector = mca_device_transform_irq(mca_dev, irq_vector); - - return sim710_probe_common(dev, base, irq_vector, clock, - 0, id_array[slot]); -} - -static struct mca_driver sim710_mca_driver = { - .id_table = sim710_mca_id_table, - .driver = { - .name = "sim710", - .bus = &mca_bus_type, - .probe = sim710_mca_probe, - .remove = __devexit_p(sim710_device_remove), - }, -}; - -#endif /* CONFIG_MCA */ - #ifdef CONFIG_EISA static struct eisa_device_id sim710_eisa_ids[] = { { "CPQ4410" }, @@ -344,10 +235,6 @@ static int __init sim710_init(void) param_setup(sim710); #endif -#ifdef CONFIG_MCA - err = mca_register_driver(&sim710_mca_driver); -#endif - #ifdef CONFIG_EISA err = eisa_driver_register(&sim710_eisa_driver); #endif @@ -361,11 +248,6 @@ static int __init sim710_init(void) static void __exit sim710_exit(void) { -#ifdef CONFIG_MCA - if (MCA_bus) - mca_unregister_driver(&sim710_mca_driver); -#endif - #ifdef CONFIG_EISA eisa_driver_unregister(&sim710_eisa_driver); #endif diff --git a/drivers/sh/clk/cpg.c b/drivers/sh/clk/cpg.c index 91b6d52f74eb..f0d015dd0fef 100644 --- a/drivers/sh/clk/cpg.c +++ b/drivers/sh/clk/cpg.c @@ -2,6 +2,7 @@ * Helper routines for SuperH Clock Pulse Generator blocks (CPG). * * Copyright (C) 2010 Magnus Damm + * Copyright (C) 2010 - 2012 Paul Mundt * * This file is subject to the terms and conditions of the GNU General Public * License. See the file "COPYING" in the main directory of this archive @@ -13,26 +14,44 @@ #include <linux/io.h> #include <linux/sh_clk.h> -static int sh_clk_mstp32_enable(struct clk *clk) +static unsigned int sh_clk_read(struct clk *clk) { - iowrite32(ioread32(clk->mapped_reg) & ~(1 << clk->enable_bit), - clk->mapped_reg); + if (clk->flags & CLK_ENABLE_REG_8BIT) + return ioread8(clk->mapped_reg); + else if (clk->flags & CLK_ENABLE_REG_16BIT) + return ioread16(clk->mapped_reg); + + return ioread32(clk->mapped_reg); +} + +static void sh_clk_write(int value, struct clk *clk) +{ + if (clk->flags & CLK_ENABLE_REG_8BIT) + iowrite8(value, clk->mapped_reg); + else if (clk->flags & CLK_ENABLE_REG_16BIT) + iowrite16(value, clk->mapped_reg); + else + iowrite32(value, clk->mapped_reg); +} + +static int sh_clk_mstp_enable(struct clk *clk) +{ + sh_clk_write(sh_clk_read(clk) & ~(1 << clk->enable_bit), clk); return 0; } -static void sh_clk_mstp32_disable(struct clk *clk) +static void sh_clk_mstp_disable(struct clk *clk) { - iowrite32(ioread32(clk->mapped_reg) | (1 << clk->enable_bit), - clk->mapped_reg); + sh_clk_write(sh_clk_read(clk) | (1 << clk->enable_bit), clk); } -static struct sh_clk_ops sh_clk_mstp32_clk_ops = { - .enable = sh_clk_mstp32_enable, - .disable = sh_clk_mstp32_disable, +static struct sh_clk_ops sh_clk_mstp_clk_ops = { + .enable = sh_clk_mstp_enable, + .disable = sh_clk_mstp_disable, .recalc = followparent_recalc, }; -int __init sh_clk_mstp32_register(struct clk *clks, int nr) +int __init sh_clk_mstp_register(struct clk *clks, int nr) { struct clk *clkp; int ret = 0; @@ -40,7 +59,7 @@ int __init sh_clk_mstp32_register(struct clk *clks, int nr) for (k = 0; !ret && (k < nr); k++) { clkp = clks + k; - clkp->ops = &sh_clk_mstp32_clk_ops; + clkp->ops = &sh_clk_mstp_clk_ops; ret |= clk_register(clkp); } @@ -72,7 +91,7 @@ static unsigned long sh_clk_div6_recalc(struct clk *clk) clk_rate_table_build(clk, clk->freq_table, table->nr_divisors, table, NULL); - idx = ioread32(clk->mapped_reg) & 0x003f; + idx = sh_clk_read(clk) & 0x003f; return clk->freq_table[idx].frequency; } @@ -98,10 +117,10 @@ static int sh_clk_div6_set_parent(struct clk *clk, struct clk *parent) if (ret < 0) return ret; - value = ioread32(clk->mapped_reg) & + value = sh_clk_read(clk) & ~(((1 << clk->src_width) - 1) << clk->src_shift); - iowrite32(value | (i << clk->src_shift), clk->mapped_reg); + sh_clk_write(value | (i << clk->src_shift), clk); /* Rebuild the frequency table */ clk_rate_table_build(clk, clk->freq_table, table->nr_divisors, @@ -119,10 +138,10 @@ static int sh_clk_div6_set_rate(struct clk *clk, unsigned long rate) if (idx < 0) return idx; - value = ioread32(clk->mapped_reg); + value = sh_clk_read(clk); value &= ~0x3f; value |= idx; - iowrite32(value, clk->mapped_reg); + sh_clk_write(value, clk); return 0; } @@ -133,9 +152,9 @@ static int sh_clk_div6_enable(struct clk *clk) ret = sh_clk_div6_set_rate(clk, clk->rate); if (ret == 0) { - value = ioread32(clk->mapped_reg); + value = sh_clk_read(clk); value &= ~0x100; /* clear stop bit to enable clock */ - iowrite32(value, clk->mapped_reg); + sh_clk_write(value, clk); } return ret; } @@ -144,10 +163,10 @@ static void sh_clk_div6_disable(struct clk *clk) { unsigned long value; - value = ioread32(clk->mapped_reg); + value = sh_clk_read(clk); value |= 0x100; /* stop clock */ value |= 0x3f; /* VDIV bits must be non-zero, overwrite divider */ - iowrite32(value, clk->mapped_reg); + sh_clk_write(value, clk); } static struct sh_clk_ops sh_clk_div6_clk_ops = { @@ -182,7 +201,7 @@ static int __init sh_clk_init_parent(struct clk *clk) return -EINVAL; } - val = (ioread32(clk->mapped_reg) >> clk->src_shift); + val = (sh_clk_read(clk) >> clk->src_shift); val &= (1 << clk->src_width) - 1; if (val >= clk->parent_num) { @@ -252,7 +271,7 @@ static unsigned long sh_clk_div4_recalc(struct clk *clk) clk_rate_table_build(clk, clk->freq_table, table->nr_divisors, table, &clk->arch_flags); - idx = (ioread32(clk->mapped_reg) >> clk->enable_bit) & 0x000f; + idx = (sh_clk_read(clk) >> clk->enable_bit) & 0x000f; return clk->freq_table[idx].frequency; } @@ -270,15 +289,15 @@ static int sh_clk_div4_set_parent(struct clk *clk, struct clk *parent) */ if (parent->flags & CLK_ENABLE_ON_INIT) - value = ioread32(clk->mapped_reg) & ~(1 << 7); + value = sh_clk_read(clk) & ~(1 << 7); else - value = ioread32(clk->mapped_reg) | (1 << 7); + value = sh_clk_read(clk) | (1 << 7); ret = clk_reparent(clk, parent); if (ret < 0) return ret; - iowrite32(value, clk->mapped_reg); + sh_clk_write(value, clk); /* Rebiuld the frequency table */ clk_rate_table_build(clk, clk->freq_table, table->nr_divisors, @@ -295,10 +314,10 @@ static int sh_clk_div4_set_rate(struct clk *clk, unsigned long rate) if (idx < 0) return idx; - value = ioread32(clk->mapped_reg); + value = sh_clk_read(clk); value &= ~(0xf << clk->enable_bit); value |= (idx << clk->enable_bit); - iowrite32(value, clk->mapped_reg); + sh_clk_write(value, clk); if (d4t->kick) d4t->kick(clk); @@ -308,13 +327,13 @@ static int sh_clk_div4_set_rate(struct clk *clk, unsigned long rate) static int sh_clk_div4_enable(struct clk *clk) { - iowrite32(ioread32(clk->mapped_reg) & ~(1 << 8), clk->mapped_reg); + sh_clk_write(sh_clk_read(clk) & ~(1 << 8), clk); return 0; } static void sh_clk_div4_disable(struct clk *clk) { - iowrite32(ioread32(clk->mapped_reg) | (1 << 8), clk->mapped_reg); + sh_clk_write(sh_clk_read(clk) | (1 << 8), clk); } static struct sh_clk_ops sh_clk_div4_clk_ops = { diff --git a/drivers/sh/intc/dynamic.c b/drivers/sh/intc/dynamic.c index 5fea1ee8799a..14eb01ef5d72 100644 --- a/drivers/sh/intc/dynamic.c +++ b/drivers/sh/intc/dynamic.c @@ -55,11 +55,3 @@ void destroy_irq(unsigned int irq) { irq_free_desc(irq); } - -void reserve_intc_vectors(struct intc_vect *vectors, unsigned int nr_vecs) -{ - int i; - - for (i = 0; i < nr_vecs; i++) - irq_reserve_irq(evt2irq(vectors[i].vect)); -} diff --git a/drivers/target/Kconfig b/drivers/target/Kconfig index b28794b72125..18303686eb58 100644 --- a/drivers/target/Kconfig +++ b/drivers/target/Kconfig @@ -32,5 +32,6 @@ config TCM_PSCSI source "drivers/target/loopback/Kconfig" source "drivers/target/tcm_fc/Kconfig" source "drivers/target/iscsi/Kconfig" +source "drivers/target/sbp/Kconfig" endif diff --git a/drivers/target/Makefile b/drivers/target/Makefile index 62e54053bcd8..61648d84fbb6 100644 --- a/drivers/target/Makefile +++ b/drivers/target/Makefile @@ -25,3 +25,4 @@ obj-$(CONFIG_TCM_PSCSI) += target_core_pscsi.o obj-$(CONFIG_LOOPBACK_TARGET) += loopback/ obj-$(CONFIG_TCM_FC) += tcm_fc/ obj-$(CONFIG_ISCSI_TARGET) += iscsi/ +obj-$(CONFIG_SBP_TARGET) += sbp/ diff --git a/drivers/target/sbp/Kconfig b/drivers/target/sbp/Kconfig new file mode 100644 index 000000000000..132da544eafc --- /dev/null +++ b/drivers/target/sbp/Kconfig @@ -0,0 +1,11 @@ +config SBP_TARGET + tristate "FireWire SBP-2 fabric module" + depends on FIREWIRE && EXPERIMENTAL + help + Say Y or M here to enable SCSI target functionality over FireWire. + This enables you to expose SCSI devices to other nodes on the FireWire + bus, for example hard disks. Similar to FireWire Target Disk mode on + many Apple computers. + + To compile this driver as a module, say M here: The module will be + called sbp-target. diff --git a/drivers/target/sbp/Makefile b/drivers/target/sbp/Makefile new file mode 100644 index 000000000000..27747ad054c4 --- /dev/null +++ b/drivers/target/sbp/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SBP_TARGET) += sbp_target.o diff --git a/drivers/target/sbp/sbp_target.c b/drivers/target/sbp/sbp_target.c new file mode 100644 index 000000000000..37c609898f84 --- /dev/null +++ b/drivers/target/sbp/sbp_target.c @@ -0,0 +1,2621 @@ +/* + * SBP2 target driver (SCSI over IEEE1394 in target mode) + * + * Copyright (C) 2011 Chris Boot <[email protected]> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#define KMSG_COMPONENT "sbp_target" +#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/string.h> +#include <linux/configfs.h> +#include <linux/ctype.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <scsi/scsi.h> +#include <scsi/scsi_tcq.h> +#include <target/target_core_base.h> +#include <target/target_core_backend.h> +#include <target/target_core_fabric.h> +#include <target/target_core_fabric_configfs.h> +#include <target/target_core_configfs.h> +#include <target/configfs_macros.h> +#include <asm/unaligned.h> + +#include "sbp_target.h" + +/* Local pointer to allocated TCM configfs fabric module */ +static struct target_fabric_configfs *sbp_fabric_configfs; + +/* FireWire address region for management and command block address handlers */ +static const struct fw_address_region sbp_register_region = { + .start = CSR_REGISTER_BASE + 0x10000, + .end = 0x1000000000000ULL, +}; + +static const u32 sbp_unit_directory_template[] = { + 0x1200609e, /* unit_specifier_id: NCITS/T10 */ + 0x13010483, /* unit_sw_version: 1155D Rev 4 */ + 0x3800609e, /* command_set_specifier_id: NCITS/T10 */ + 0x390104d8, /* command_set: SPC-2 */ + 0x3b000000, /* command_set_revision: 0 */ + 0x3c000001, /* firmware_revision: 1 */ +}; + +#define SESSION_MAINTENANCE_INTERVAL HZ + +static atomic_t login_id = ATOMIC_INIT(0); + +static void session_maintenance_work(struct work_struct *); +static int sbp_run_transaction(struct fw_card *, int, int, int, int, + unsigned long long, void *, size_t); + +static int read_peer_guid(u64 *guid, const struct sbp_management_request *req) +{ + int ret; + __be32 high, low; + + ret = sbp_run_transaction(req->card, TCODE_READ_QUADLET_REQUEST, + req->node_addr, req->generation, req->speed, + (CSR_REGISTER_BASE | CSR_CONFIG_ROM) + 3 * 4, + &high, sizeof(high)); + if (ret != RCODE_COMPLETE) + return ret; + + ret = sbp_run_transaction(req->card, TCODE_READ_QUADLET_REQUEST, + req->node_addr, req->generation, req->speed, + (CSR_REGISTER_BASE | CSR_CONFIG_ROM) + 4 * 4, + &low, sizeof(low)); + if (ret != RCODE_COMPLETE) + return ret; + + *guid = (u64)be32_to_cpu(high) << 32 | be32_to_cpu(low); + + return RCODE_COMPLETE; +} + +static struct sbp_session *sbp_session_find_by_guid( + struct sbp_tpg *tpg, u64 guid) +{ + struct se_session *se_sess; + struct sbp_session *sess, *found = NULL; + + spin_lock_bh(&tpg->se_tpg.session_lock); + list_for_each_entry(se_sess, &tpg->se_tpg.tpg_sess_list, sess_list) { + sess = se_sess->fabric_sess_ptr; + if (sess->guid == guid) + found = sess; + } + spin_unlock_bh(&tpg->se_tpg.session_lock); + + return found; +} + +static struct sbp_login_descriptor *sbp_login_find_by_lun( + struct sbp_session *session, struct se_lun *lun) +{ + struct sbp_login_descriptor *login, *found = NULL; + + spin_lock_bh(&session->lock); + list_for_each_entry(login, &session->login_list, link) { + if (login->lun == lun) + found = login; + } + spin_unlock_bh(&session->lock); + + return found; +} + +static int sbp_login_count_all_by_lun( + struct sbp_tpg *tpg, + struct se_lun *lun, + int exclusive) +{ + struct se_session *se_sess; + struct sbp_session *sess; + struct sbp_login_descriptor *login; + int count = 0; + + spin_lock_bh(&tpg->se_tpg.session_lock); + list_for_each_entry(se_sess, &tpg->se_tpg.tpg_sess_list, sess_list) { + sess = se_sess->fabric_sess_ptr; + + spin_lock_bh(&sess->lock); + list_for_each_entry(login, &sess->login_list, link) { + if (login->lun != lun) + continue; + + if (!exclusive || login->exclusive) + count++; + } + spin_unlock_bh(&sess->lock); + } + spin_unlock_bh(&tpg->se_tpg.session_lock); + + return count; +} + +static struct sbp_login_descriptor *sbp_login_find_by_id( + struct sbp_tpg *tpg, int login_id) +{ + struct se_session *se_sess; + struct sbp_session *sess; + struct sbp_login_descriptor *login, *found = NULL; + + spin_lock_bh(&tpg->se_tpg.session_lock); + list_for_each_entry(se_sess, &tpg->se_tpg.tpg_sess_list, sess_list) { + sess = se_sess->fabric_sess_ptr; + + spin_lock_bh(&sess->lock); + list_for_each_entry(login, &sess->login_list, link) { + if (login->login_id == login_id) + found = login; + } + spin_unlock_bh(&sess->lock); + } + spin_unlock_bh(&tpg->se_tpg.session_lock); + + return found; +} + +static struct se_lun *sbp_get_lun_from_tpg(struct sbp_tpg *tpg, int lun) +{ + struct se_portal_group *se_tpg = &tpg->se_tpg; + struct se_lun *se_lun; + + if (lun >= TRANSPORT_MAX_LUNS_PER_TPG) + return ERR_PTR(-EINVAL); + + spin_lock(&se_tpg->tpg_lun_lock); + se_lun = se_tpg->tpg_lun_list[lun]; + + if (se_lun->lun_status != TRANSPORT_LUN_STATUS_ACTIVE) + se_lun = ERR_PTR(-ENODEV); + + spin_unlock(&se_tpg->tpg_lun_lock); + + return se_lun; +} + +static struct sbp_session *sbp_session_create( + struct sbp_tpg *tpg, + u64 guid) +{ + struct sbp_session *sess; + int ret; + char guid_str[17]; + struct se_node_acl *se_nacl; + + sess = kmalloc(sizeof(*sess), GFP_KERNEL); + if (!sess) { + pr_err("failed to allocate session descriptor\n"); + return ERR_PTR(-ENOMEM); + } + + sess->se_sess = transport_init_session(); + if (IS_ERR(sess->se_sess)) { + pr_err("failed to init se_session\n"); + + ret = PTR_ERR(sess->se_sess); + kfree(sess); + return ERR_PTR(ret); + } + + snprintf(guid_str, sizeof(guid_str), "%016llx", guid); + + se_nacl = core_tpg_check_initiator_node_acl(&tpg->se_tpg, guid_str); + if (!se_nacl) { + pr_warn("Node ACL not found for %s\n", guid_str); + + transport_free_session(sess->se_sess); + kfree(sess); + + return ERR_PTR(-EPERM); + } + + sess->se_sess->se_node_acl = se_nacl; + + spin_lock_init(&sess->lock); + INIT_LIST_HEAD(&sess->login_list); + INIT_DELAYED_WORK(&sess->maint_work, session_maintenance_work); + + sess->guid = guid; + + transport_register_session(&tpg->se_tpg, se_nacl, sess->se_sess, sess); + + return sess; +} + +static void sbp_session_release(struct sbp_session *sess, bool cancel_work) +{ + spin_lock_bh(&sess->lock); + if (!list_empty(&sess->login_list)) { + spin_unlock_bh(&sess->lock); + return; + } + spin_unlock_bh(&sess->lock); + + if (cancel_work) + cancel_delayed_work_sync(&sess->maint_work); + + transport_deregister_session_configfs(sess->se_sess); + transport_deregister_session(sess->se_sess); + + if (sess->card) + fw_card_put(sess->card); + + kfree(sess); +} + +static void sbp_target_agent_unregister(struct sbp_target_agent *); + +static void sbp_login_release(struct sbp_login_descriptor *login, + bool cancel_work) +{ + struct sbp_session *sess = login->sess; + + /* FIXME: abort/wait on tasks */ + + sbp_target_agent_unregister(login->tgt_agt); + + if (sess) { + spin_lock_bh(&sess->lock); + list_del(&login->link); + spin_unlock_bh(&sess->lock); + + sbp_session_release(sess, cancel_work); + } + + kfree(login); +} + +static struct sbp_target_agent *sbp_target_agent_register( + struct sbp_login_descriptor *); + +static void sbp_management_request_login( + struct sbp_management_agent *agent, struct sbp_management_request *req, + int *status_data_size) +{ + struct sbp_tport *tport = agent->tport; + struct sbp_tpg *tpg = tport->tpg; + struct se_lun *se_lun; + int ret; + u64 guid; + struct sbp_session *sess; + struct sbp_login_descriptor *login; + struct sbp_login_response_block *response; + int login_response_len; + + se_lun = sbp_get_lun_from_tpg(tpg, + LOGIN_ORB_LUN(be32_to_cpu(req->orb.misc))); + if (IS_ERR(se_lun)) { + pr_notice("login to unknown LUN: %d\n", + LOGIN_ORB_LUN(be32_to_cpu(req->orb.misc))); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_LUN_NOTSUPP)); + return; + } + + ret = read_peer_guid(&guid, req); + if (ret != RCODE_COMPLETE) { + pr_warn("failed to read peer GUID: %d\n", ret); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_TRANSPORT_FAILURE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_UNSPECIFIED_ERROR)); + return; + } + + pr_notice("mgt_agent LOGIN to LUN %d from %016llx\n", + se_lun->unpacked_lun, guid); + + sess = sbp_session_find_by_guid(tpg, guid); + if (sess) { + login = sbp_login_find_by_lun(sess, se_lun); + if (login) { + pr_notice("initiator already logged-in\n"); + + /* + * SBP-2 R4 says we should return access denied, but + * that can confuse initiators. Instead we need to + * treat this like a reconnect, but send the login + * response block like a fresh login. + * + * This is required particularly in the case of Apple + * devices booting off the FireWire target, where + * the firmware has an active login to the target. When + * the OS takes control of the session it issues its own + * LOGIN rather than a RECONNECT. To avoid the machine + * waiting until the reconnect_hold expires, we can skip + * the ACCESS_DENIED errors to speed things up. + */ + + goto already_logged_in; + } + } + + /* + * check exclusive bit in login request + * reject with access_denied if any logins present + */ + if (LOGIN_ORB_EXCLUSIVE(be32_to_cpu(req->orb.misc)) && + sbp_login_count_all_by_lun(tpg, se_lun, 0)) { + pr_warn("refusing exclusive login with other active logins\n"); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_ACCESS_DENIED)); + return; + } + + /* + * check exclusive bit in any existing login descriptor + * reject with access_denied if any exclusive logins present + */ + if (sbp_login_count_all_by_lun(tpg, se_lun, 1)) { + pr_warn("refusing login while another exclusive login present\n"); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_ACCESS_DENIED)); + return; + } + + /* + * check we haven't exceeded the number of allowed logins + * reject with resources_unavailable if we have + */ + if (sbp_login_count_all_by_lun(tpg, se_lun, 0) >= + tport->max_logins_per_lun) { + pr_warn("max number of logins reached\n"); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_RESOURCES_UNAVAIL)); + return; + } + + if (!sess) { + sess = sbp_session_create(tpg, guid); + if (IS_ERR(sess)) { + switch (PTR_ERR(sess)) { + case -EPERM: + ret = SBP_STATUS_ACCESS_DENIED; + break; + default: + ret = SBP_STATUS_RESOURCES_UNAVAIL; + break; + } + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP( + STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(ret)); + return; + } + + sess->node_id = req->node_addr; + sess->card = fw_card_get(req->card); + sess->generation = req->generation; + sess->speed = req->speed; + + schedule_delayed_work(&sess->maint_work, + SESSION_MAINTENANCE_INTERVAL); + } + + /* only take the latest reconnect_hold into account */ + sess->reconnect_hold = min( + 1 << LOGIN_ORB_RECONNECT(be32_to_cpu(req->orb.misc)), + tport->max_reconnect_timeout) - 1; + + login = kmalloc(sizeof(*login), GFP_KERNEL); + if (!login) { + pr_err("failed to allocate login descriptor\n"); + + sbp_session_release(sess, true); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_RESOURCES_UNAVAIL)); + return; + } + + login->sess = sess; + login->lun = se_lun; + login->status_fifo_addr = sbp2_pointer_to_addr(&req->orb.status_fifo); + login->exclusive = LOGIN_ORB_EXCLUSIVE(be32_to_cpu(req->orb.misc)); + login->login_id = atomic_inc_return(&login_id); + + login->tgt_agt = sbp_target_agent_register(login); + if (IS_ERR(login->tgt_agt)) { + ret = PTR_ERR(login->tgt_agt); + pr_err("failed to map command block handler: %d\n", ret); + + sbp_session_release(sess, true); + kfree(login); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_RESOURCES_UNAVAIL)); + return; + } + + spin_lock_bh(&sess->lock); + list_add_tail(&login->link, &sess->login_list); + spin_unlock_bh(&sess->lock); + +already_logged_in: + response = kzalloc(sizeof(*response), GFP_KERNEL); + if (!response) { + pr_err("failed to allocate login response block\n"); + + sbp_login_release(login, true); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_RESOURCES_UNAVAIL)); + return; + } + + login_response_len = clamp_val( + LOGIN_ORB_RESPONSE_LENGTH(be32_to_cpu(req->orb.length)), + 12, sizeof(*response)); + response->misc = cpu_to_be32( + ((login_response_len & 0xffff) << 16) | + (login->login_id & 0xffff)); + response->reconnect_hold = cpu_to_be32(sess->reconnect_hold & 0xffff); + addr_to_sbp2_pointer(login->tgt_agt->handler.offset, + &response->command_block_agent); + + ret = sbp_run_transaction(sess->card, TCODE_WRITE_BLOCK_REQUEST, + sess->node_id, sess->generation, sess->speed, + sbp2_pointer_to_addr(&req->orb.ptr2), response, + login_response_len); + if (ret != RCODE_COMPLETE) { + pr_debug("failed to write login response block: %x\n", ret); + + kfree(response); + sbp_login_release(login, true); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_TRANSPORT_FAILURE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_UNSPECIFIED_ERROR)); + return; + } + + kfree(response); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_OK)); +} + +static void sbp_management_request_query_logins( + struct sbp_management_agent *agent, struct sbp_management_request *req, + int *status_data_size) +{ + pr_notice("QUERY LOGINS not implemented\n"); + /* FIXME: implement */ + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_REQ_TYPE_NOTSUPP)); +} + +static void sbp_management_request_reconnect( + struct sbp_management_agent *agent, struct sbp_management_request *req, + int *status_data_size) +{ + struct sbp_tport *tport = agent->tport; + struct sbp_tpg *tpg = tport->tpg; + int ret; + u64 guid; + struct sbp_login_descriptor *login; + + ret = read_peer_guid(&guid, req); + if (ret != RCODE_COMPLETE) { + pr_warn("failed to read peer GUID: %d\n", ret); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_TRANSPORT_FAILURE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_UNSPECIFIED_ERROR)); + return; + } + + pr_notice("mgt_agent RECONNECT from %016llx\n", guid); + + login = sbp_login_find_by_id(tpg, + RECONNECT_ORB_LOGIN_ID(be32_to_cpu(req->orb.misc))); + + if (!login) { + pr_err("mgt_agent RECONNECT unknown login ID\n"); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_ACCESS_DENIED)); + return; + } + + if (login->sess->guid != guid) { + pr_err("mgt_agent RECONNECT login GUID doesn't match\n"); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_ACCESS_DENIED)); + return; + } + + spin_lock_bh(&login->sess->lock); + if (login->sess->card) + fw_card_put(login->sess->card); + + /* update the node details */ + login->sess->generation = req->generation; + login->sess->node_id = req->node_addr; + login->sess->card = fw_card_get(req->card); + login->sess->speed = req->speed; + spin_unlock_bh(&login->sess->lock); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_OK)); +} + +static void sbp_management_request_logout( + struct sbp_management_agent *agent, struct sbp_management_request *req, + int *status_data_size) +{ + struct sbp_tport *tport = agent->tport; + struct sbp_tpg *tpg = tport->tpg; + int login_id; + struct sbp_login_descriptor *login; + + login_id = LOGOUT_ORB_LOGIN_ID(be32_to_cpu(req->orb.misc)); + + login = sbp_login_find_by_id(tpg, login_id); + if (!login) { + pr_warn("cannot find login: %d\n", login_id); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_LOGIN_ID_UNKNOWN)); + return; + } + + pr_info("mgt_agent LOGOUT from LUN %d session %d\n", + login->lun->unpacked_lun, login->login_id); + + if (req->node_addr != login->sess->node_id) { + pr_warn("logout from different node ID\n"); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_ACCESS_DENIED)); + return; + } + + sbp_login_release(login, true); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_OK)); +} + +static void session_check_for_reset(struct sbp_session *sess) +{ + bool card_valid = false; + + spin_lock_bh(&sess->lock); + + if (sess->card) { + spin_lock_irq(&sess->card->lock); + card_valid = (sess->card->local_node != NULL); + spin_unlock_irq(&sess->card->lock); + + if (!card_valid) { + fw_card_put(sess->card); + sess->card = NULL; + } + } + + if (!card_valid || (sess->generation != sess->card->generation)) { + pr_info("Waiting for reconnect from node: %016llx\n", + sess->guid); + + sess->node_id = -1; + sess->reconnect_expires = get_jiffies_64() + + ((sess->reconnect_hold + 1) * HZ); + } + + spin_unlock_bh(&sess->lock); +} + +static void session_reconnect_expired(struct sbp_session *sess) +{ + struct sbp_login_descriptor *login, *temp; + LIST_HEAD(login_list); + + pr_info("Reconnect timer expired for node: %016llx\n", sess->guid); + + spin_lock_bh(&sess->lock); + list_for_each_entry_safe(login, temp, &sess->login_list, link) { + login->sess = NULL; + list_del(&login->link); + list_add_tail(&login->link, &login_list); + } + spin_unlock_bh(&sess->lock); + + list_for_each_entry_safe(login, temp, &login_list, link) { + list_del(&login->link); + sbp_login_release(login, false); + } + + sbp_session_release(sess, false); +} + +static void session_maintenance_work(struct work_struct *work) +{ + struct sbp_session *sess = container_of(work, struct sbp_session, + maint_work.work); + + /* could be called while tearing down the session */ + spin_lock_bh(&sess->lock); + if (list_empty(&sess->login_list)) { + spin_unlock_bh(&sess->lock); + return; + } + spin_unlock_bh(&sess->lock); + + if (sess->node_id != -1) { + /* check for bus reset and make node_id invalid */ + session_check_for_reset(sess); + + schedule_delayed_work(&sess->maint_work, + SESSION_MAINTENANCE_INTERVAL); + } else if (!time_after64(get_jiffies_64(), sess->reconnect_expires)) { + /* still waiting for reconnect */ + schedule_delayed_work(&sess->maint_work, + SESSION_MAINTENANCE_INTERVAL); + } else { + /* reconnect timeout has expired */ + session_reconnect_expired(sess); + } +} + +static int tgt_agent_rw_agent_state(struct fw_card *card, int tcode, void *data, + struct sbp_target_agent *agent) +{ + __be32 state; + + switch (tcode) { + case TCODE_READ_QUADLET_REQUEST: + pr_debug("tgt_agent AGENT_STATE READ\n"); + + spin_lock_bh(&agent->lock); + state = cpu_to_be32(agent->state); + spin_unlock_bh(&agent->lock); + memcpy(data, &state, sizeof(state)); + + return RCODE_COMPLETE; + + case TCODE_WRITE_QUADLET_REQUEST: + /* ignored */ + return RCODE_COMPLETE; + + default: + return RCODE_TYPE_ERROR; + } +} + +static int tgt_agent_rw_agent_reset(struct fw_card *card, int tcode, void *data, + struct sbp_target_agent *agent) +{ + switch (tcode) { + case TCODE_WRITE_QUADLET_REQUEST: + pr_debug("tgt_agent AGENT_RESET\n"); + spin_lock_bh(&agent->lock); + agent->state = AGENT_STATE_RESET; + spin_unlock_bh(&agent->lock); + return RCODE_COMPLETE; + + default: + return RCODE_TYPE_ERROR; + } +} + +static int tgt_agent_rw_orb_pointer(struct fw_card *card, int tcode, void *data, + struct sbp_target_agent *agent) +{ + struct sbp2_pointer *ptr = data; + + switch (tcode) { + case TCODE_WRITE_BLOCK_REQUEST: + spin_lock_bh(&agent->lock); + if (agent->state != AGENT_STATE_SUSPENDED && + agent->state != AGENT_STATE_RESET) { + spin_unlock_bh(&agent->lock); + pr_notice("Ignoring ORB_POINTER write while active.\n"); + return RCODE_CONFLICT_ERROR; + } + agent->state = AGENT_STATE_ACTIVE; + spin_unlock_bh(&agent->lock); + + agent->orb_pointer = sbp2_pointer_to_addr(ptr); + agent->doorbell = false; + + pr_debug("tgt_agent ORB_POINTER write: 0x%llx\n", + agent->orb_pointer); + + queue_work(system_unbound_wq, &agent->work); + + return RCODE_COMPLETE; + + case TCODE_READ_BLOCK_REQUEST: + pr_debug("tgt_agent ORB_POINTER READ\n"); + spin_lock_bh(&agent->lock); + addr_to_sbp2_pointer(agent->orb_pointer, ptr); + spin_unlock_bh(&agent->lock); + return RCODE_COMPLETE; + + default: + return RCODE_TYPE_ERROR; + } +} + +static int tgt_agent_rw_doorbell(struct fw_card *card, int tcode, void *data, + struct sbp_target_agent *agent) +{ + switch (tcode) { + case TCODE_WRITE_QUADLET_REQUEST: + spin_lock_bh(&agent->lock); + if (agent->state != AGENT_STATE_SUSPENDED) { + spin_unlock_bh(&agent->lock); + pr_debug("Ignoring DOORBELL while active.\n"); + return RCODE_CONFLICT_ERROR; + } + agent->state = AGENT_STATE_ACTIVE; + spin_unlock_bh(&agent->lock); + + agent->doorbell = true; + + pr_debug("tgt_agent DOORBELL\n"); + + queue_work(system_unbound_wq, &agent->work); + + return RCODE_COMPLETE; + + case TCODE_READ_QUADLET_REQUEST: + return RCODE_COMPLETE; + + default: + return RCODE_TYPE_ERROR; + } +} + +static int tgt_agent_rw_unsolicited_status_enable(struct fw_card *card, + int tcode, void *data, struct sbp_target_agent *agent) +{ + switch (tcode) { + case TCODE_WRITE_QUADLET_REQUEST: + pr_debug("tgt_agent UNSOLICITED_STATUS_ENABLE\n"); + /* ignored as we don't send unsolicited status */ + return RCODE_COMPLETE; + + case TCODE_READ_QUADLET_REQUEST: + return RCODE_COMPLETE; + + default: + return RCODE_TYPE_ERROR; + } +} + +static void tgt_agent_rw(struct fw_card *card, struct fw_request *request, + int tcode, int destination, int source, int generation, + unsigned long long offset, void *data, size_t length, + void *callback_data) +{ + struct sbp_target_agent *agent = callback_data; + struct sbp_session *sess = agent->login->sess; + int sess_gen, sess_node, rcode; + + spin_lock_bh(&sess->lock); + sess_gen = sess->generation; + sess_node = sess->node_id; + spin_unlock_bh(&sess->lock); + + if (generation != sess_gen) { + pr_notice("ignoring request with wrong generation\n"); + rcode = RCODE_TYPE_ERROR; + goto out; + } + + if (source != sess_node) { + pr_notice("ignoring request from foreign node (%x != %x)\n", + source, sess_node); + rcode = RCODE_TYPE_ERROR; + goto out; + } + + /* turn offset into the offset from the start of the block */ + offset -= agent->handler.offset; + + if (offset == 0x00 && length == 4) { + /* AGENT_STATE */ + rcode = tgt_agent_rw_agent_state(card, tcode, data, agent); + } else if (offset == 0x04 && length == 4) { + /* AGENT_RESET */ + rcode = tgt_agent_rw_agent_reset(card, tcode, data, agent); + } else if (offset == 0x08 && length == 8) { + /* ORB_POINTER */ + rcode = tgt_agent_rw_orb_pointer(card, tcode, data, agent); + } else if (offset == 0x10 && length == 4) { + /* DOORBELL */ + rcode = tgt_agent_rw_doorbell(card, tcode, data, agent); + } else if (offset == 0x14 && length == 4) { + /* UNSOLICITED_STATUS_ENABLE */ + rcode = tgt_agent_rw_unsolicited_status_enable(card, tcode, + data, agent); + } else { + rcode = RCODE_ADDRESS_ERROR; + } + +out: + fw_send_response(card, request, rcode); +} + +static void sbp_handle_command(struct sbp_target_request *); +static int sbp_send_status(struct sbp_target_request *); +static void sbp_free_request(struct sbp_target_request *); + +static void tgt_agent_process_work(struct work_struct *work) +{ + struct sbp_target_request *req = + container_of(work, struct sbp_target_request, work); + + pr_debug("tgt_orb ptr:0x%llx next_ORB:0x%llx data_descriptor:0x%llx misc:0x%x\n", + req->orb_pointer, + sbp2_pointer_to_addr(&req->orb.next_orb), + sbp2_pointer_to_addr(&req->orb.data_descriptor), + be32_to_cpu(req->orb.misc)); + + if (req->orb_pointer >> 32) + pr_debug("ORB with high bits set\n"); + + switch (ORB_REQUEST_FORMAT(be32_to_cpu(req->orb.misc))) { + case 0:/* Format specified by this standard */ + sbp_handle_command(req); + return; + case 1: /* Reserved for future standardization */ + case 2: /* Vendor-dependent */ + req->status.status |= cpu_to_be32( + STATUS_BLOCK_RESP( + STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_DEAD(0) | + STATUS_BLOCK_LEN(1) | + STATUS_BLOCK_SBP_STATUS( + SBP_STATUS_REQ_TYPE_NOTSUPP)); + sbp_send_status(req); + sbp_free_request(req); + return; + case 3: /* Dummy ORB */ + req->status.status |= cpu_to_be32( + STATUS_BLOCK_RESP( + STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_DEAD(0) | + STATUS_BLOCK_LEN(1) | + STATUS_BLOCK_SBP_STATUS( + SBP_STATUS_DUMMY_ORB_COMPLETE)); + sbp_send_status(req); + sbp_free_request(req); + return; + default: + BUG(); + } +} + +/* used to double-check we haven't been issued an AGENT_RESET */ +static inline bool tgt_agent_check_active(struct sbp_target_agent *agent) +{ + bool active; + + spin_lock_bh(&agent->lock); + active = (agent->state == AGENT_STATE_ACTIVE); + spin_unlock_bh(&agent->lock); + + return active; +} + +static void tgt_agent_fetch_work(struct work_struct *work) +{ + struct sbp_target_agent *agent = + container_of(work, struct sbp_target_agent, work); + struct sbp_session *sess = agent->login->sess; + struct sbp_target_request *req; + int ret; + bool doorbell = agent->doorbell; + u64 next_orb = agent->orb_pointer; + + while (next_orb && tgt_agent_check_active(agent)) { + req = kzalloc(sizeof(*req), GFP_KERNEL); + if (!req) { + spin_lock_bh(&agent->lock); + agent->state = AGENT_STATE_DEAD; + spin_unlock_bh(&agent->lock); + return; + } + + req->login = agent->login; + req->orb_pointer = next_orb; + + req->status.status = cpu_to_be32(STATUS_BLOCK_ORB_OFFSET_HIGH( + req->orb_pointer >> 32)); + req->status.orb_low = cpu_to_be32( + req->orb_pointer & 0xfffffffc); + + /* read in the ORB */ + ret = sbp_run_transaction(sess->card, TCODE_READ_BLOCK_REQUEST, + sess->node_id, sess->generation, sess->speed, + req->orb_pointer, &req->orb, sizeof(req->orb)); + if (ret != RCODE_COMPLETE) { + pr_debug("tgt_orb fetch failed: %x\n", ret); + req->status.status |= cpu_to_be32( + STATUS_BLOCK_SRC( + STATUS_SRC_ORB_FINISHED) | + STATUS_BLOCK_RESP( + STATUS_RESP_TRANSPORT_FAILURE) | + STATUS_BLOCK_DEAD(1) | + STATUS_BLOCK_LEN(1) | + STATUS_BLOCK_SBP_STATUS( + SBP_STATUS_UNSPECIFIED_ERROR)); + spin_lock_bh(&agent->lock); + agent->state = AGENT_STATE_DEAD; + spin_unlock_bh(&agent->lock); + + sbp_send_status(req); + sbp_free_request(req); + return; + } + + /* check the next_ORB field */ + if (be32_to_cpu(req->orb.next_orb.high) & 0x80000000) { + next_orb = 0; + req->status.status |= cpu_to_be32(STATUS_BLOCK_SRC( + STATUS_SRC_ORB_FINISHED)); + } else { + next_orb = sbp2_pointer_to_addr(&req->orb.next_orb); + req->status.status |= cpu_to_be32(STATUS_BLOCK_SRC( + STATUS_SRC_ORB_CONTINUING)); + } + + if (tgt_agent_check_active(agent) && !doorbell) { + INIT_WORK(&req->work, tgt_agent_process_work); + queue_work(system_unbound_wq, &req->work); + } else { + /* don't process this request, just check next_ORB */ + sbp_free_request(req); + } + + spin_lock_bh(&agent->lock); + doorbell = agent->doorbell = false; + + /* check if we should carry on processing */ + if (next_orb) + agent->orb_pointer = next_orb; + else + agent->state = AGENT_STATE_SUSPENDED; + + spin_unlock_bh(&agent->lock); + }; +} + +static struct sbp_target_agent *sbp_target_agent_register( + struct sbp_login_descriptor *login) +{ + struct sbp_target_agent *agent; + int ret; + + agent = kmalloc(sizeof(*agent), GFP_KERNEL); + if (!agent) + return ERR_PTR(-ENOMEM); + + spin_lock_init(&agent->lock); + + agent->handler.length = 0x20; + agent->handler.address_callback = tgt_agent_rw; + agent->handler.callback_data = agent; + + agent->login = login; + agent->state = AGENT_STATE_RESET; + INIT_WORK(&agent->work, tgt_agent_fetch_work); + agent->orb_pointer = 0; + agent->doorbell = false; + + ret = fw_core_add_address_handler(&agent->handler, + &sbp_register_region); + if (ret < 0) { + kfree(agent); + return ERR_PTR(ret); + } + + return agent; +} + +static void sbp_target_agent_unregister(struct sbp_target_agent *agent) +{ + fw_core_remove_address_handler(&agent->handler); + cancel_work_sync(&agent->work); + kfree(agent); +} + +/* + * Simple wrapper around fw_run_transaction that retries the transaction several + * times in case of failure, with an exponential backoff. + */ +static int sbp_run_transaction(struct fw_card *card, int tcode, int destination_id, + int generation, int speed, unsigned long long offset, + void *payload, size_t length) +{ + int attempt, ret, delay; + + for (attempt = 1; attempt <= 5; attempt++) { + ret = fw_run_transaction(card, tcode, destination_id, + generation, speed, offset, payload, length); + + switch (ret) { + case RCODE_COMPLETE: + case RCODE_TYPE_ERROR: + case RCODE_ADDRESS_ERROR: + case RCODE_GENERATION: + return ret; + + default: + delay = 5 * attempt * attempt; + usleep_range(delay, delay * 2); + } + } + + return ret; +} + +/* + * Wrapper around sbp_run_transaction that gets the card, destination, + * generation and speed out of the request's session. + */ +static int sbp_run_request_transaction(struct sbp_target_request *req, + int tcode, unsigned long long offset, void *payload, + size_t length) +{ + struct sbp_login_descriptor *login = req->login; + struct sbp_session *sess = login->sess; + struct fw_card *card; + int node_id, generation, speed, ret; + + spin_lock_bh(&sess->lock); + card = fw_card_get(sess->card); + node_id = sess->node_id; + generation = sess->generation; + speed = sess->speed; + spin_unlock_bh(&sess->lock); + + ret = sbp_run_transaction(card, tcode, node_id, generation, speed, + offset, payload, length); + + fw_card_put(card); + + return ret; +} + +static int sbp_fetch_command(struct sbp_target_request *req) +{ + int ret, cmd_len, copy_len; + + cmd_len = scsi_command_size(req->orb.command_block); + + req->cmd_buf = kmalloc(cmd_len, GFP_KERNEL); + if (!req->cmd_buf) + return -ENOMEM; + + memcpy(req->cmd_buf, req->orb.command_block, + min_t(int, cmd_len, sizeof(req->orb.command_block))); + + if (cmd_len > sizeof(req->orb.command_block)) { + pr_debug("sbp_fetch_command: filling in long command\n"); + copy_len = cmd_len - sizeof(req->orb.command_block); + + ret = sbp_run_request_transaction(req, + TCODE_READ_BLOCK_REQUEST, + req->orb_pointer + sizeof(req->orb), + req->cmd_buf + sizeof(req->orb.command_block), + copy_len); + if (ret != RCODE_COMPLETE) + return -EIO; + } + + return 0; +} + +static int sbp_fetch_page_table(struct sbp_target_request *req) +{ + int pg_tbl_sz, ret; + struct sbp_page_table_entry *pg_tbl; + + if (!CMDBLK_ORB_PG_TBL_PRESENT(be32_to_cpu(req->orb.misc))) + return 0; + + pg_tbl_sz = CMDBLK_ORB_DATA_SIZE(be32_to_cpu(req->orb.misc)) * + sizeof(struct sbp_page_table_entry); + + pg_tbl = kmalloc(pg_tbl_sz, GFP_KERNEL); + if (!pg_tbl) + return -ENOMEM; + + ret = sbp_run_request_transaction(req, TCODE_READ_BLOCK_REQUEST, + sbp2_pointer_to_addr(&req->orb.data_descriptor), + pg_tbl, pg_tbl_sz); + if (ret != RCODE_COMPLETE) { + kfree(pg_tbl); + return -EIO; + } + + req->pg_tbl = pg_tbl; + return 0; +} + +static void sbp_calc_data_length_direction(struct sbp_target_request *req, + u32 *data_len, enum dma_data_direction *data_dir) +{ + int data_size, direction, idx; + + data_size = CMDBLK_ORB_DATA_SIZE(be32_to_cpu(req->orb.misc)); + direction = CMDBLK_ORB_DIRECTION(be32_to_cpu(req->orb.misc)); + + if (!data_size) { + *data_len = 0; + *data_dir = DMA_NONE; + return; + } + + *data_dir = direction ? DMA_FROM_DEVICE : DMA_TO_DEVICE; + + if (req->pg_tbl) { + *data_len = 0; + for (idx = 0; idx < data_size; idx++) { + *data_len += be16_to_cpu( + req->pg_tbl[idx].segment_length); + } + } else { + *data_len = data_size; + } +} + +static void sbp_handle_command(struct sbp_target_request *req) +{ + struct sbp_login_descriptor *login = req->login; + struct sbp_session *sess = login->sess; + int ret, unpacked_lun; + u32 data_length; + enum dma_data_direction data_dir; + + ret = sbp_fetch_command(req); + if (ret) { + pr_debug("sbp_handle_command: fetch command failed: %d\n", ret); + req->status.status |= cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_TRANSPORT_FAILURE) | + STATUS_BLOCK_DEAD(0) | + STATUS_BLOCK_LEN(1) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_UNSPECIFIED_ERROR)); + sbp_send_status(req); + sbp_free_request(req); + return; + } + + ret = sbp_fetch_page_table(req); + if (ret) { + pr_debug("sbp_handle_command: fetch page table failed: %d\n", + ret); + req->status.status |= cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_TRANSPORT_FAILURE) | + STATUS_BLOCK_DEAD(0) | + STATUS_BLOCK_LEN(1) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_UNSPECIFIED_ERROR)); + sbp_send_status(req); + sbp_free_request(req); + return; + } + + unpacked_lun = req->login->lun->unpacked_lun; + sbp_calc_data_length_direction(req, &data_length, &data_dir); + + pr_debug("sbp_handle_command ORB:0x%llx unpacked_lun:%d data_len:%d data_dir:%d\n", + req->orb_pointer, unpacked_lun, data_length, data_dir); + + target_submit_cmd(&req->se_cmd, sess->se_sess, req->cmd_buf, + req->sense_buf, unpacked_lun, data_length, + MSG_SIMPLE_TAG, data_dir, 0); +} + +/* + * DMA_TO_DEVICE = read from initiator (SCSI WRITE) + * DMA_FROM_DEVICE = write to initiator (SCSI READ) + */ +static int sbp_rw_data(struct sbp_target_request *req) +{ + struct sbp_session *sess = req->login->sess; + int tcode, sg_miter_flags, max_payload, pg_size, speed, node_id, + generation, num_pte, length, tfr_length, + rcode = RCODE_COMPLETE; + struct sbp_page_table_entry *pte; + unsigned long long offset; + struct fw_card *card; + struct sg_mapping_iter iter; + + if (req->se_cmd.data_direction == DMA_FROM_DEVICE) { + tcode = TCODE_WRITE_BLOCK_REQUEST; + sg_miter_flags = SG_MITER_FROM_SG; + } else { + tcode = TCODE_READ_BLOCK_REQUEST; + sg_miter_flags = SG_MITER_TO_SG; + } + + max_payload = 4 << CMDBLK_ORB_MAX_PAYLOAD(be32_to_cpu(req->orb.misc)); + speed = CMDBLK_ORB_SPEED(be32_to_cpu(req->orb.misc)); + + pg_size = CMDBLK_ORB_PG_SIZE(be32_to_cpu(req->orb.misc)); + if (pg_size) { + pr_err("sbp_run_transaction: page size ignored\n"); + pg_size = 0x100 << pg_size; + } + + spin_lock_bh(&sess->lock); + card = fw_card_get(sess->card); + node_id = sess->node_id; + generation = sess->generation; + spin_unlock_bh(&sess->lock); + + if (req->pg_tbl) { + pte = req->pg_tbl; + num_pte = CMDBLK_ORB_DATA_SIZE(be32_to_cpu(req->orb.misc)); + + offset = 0; + length = 0; + } else { + pte = NULL; + num_pte = 0; + + offset = sbp2_pointer_to_addr(&req->orb.data_descriptor); + length = req->se_cmd.data_length; + } + + sg_miter_start(&iter, req->se_cmd.t_data_sg, req->se_cmd.t_data_nents, + sg_miter_flags); + + while (length || num_pte) { + if (!length) { + offset = (u64)be16_to_cpu(pte->segment_base_hi) << 32 | + be32_to_cpu(pte->segment_base_lo); + length = be16_to_cpu(pte->segment_length); + + pte++; + num_pte--; + } + + sg_miter_next(&iter); + + tfr_length = min3(length, max_payload, (int)iter.length); + + /* FIXME: take page_size into account */ + + rcode = sbp_run_transaction(card, tcode, node_id, + generation, speed, + offset, iter.addr, tfr_length); + + if (rcode != RCODE_COMPLETE) + break; + + length -= tfr_length; + offset += tfr_length; + iter.consumed = tfr_length; + } + + sg_miter_stop(&iter); + fw_card_put(card); + + if (rcode == RCODE_COMPLETE) { + WARN_ON(length != 0); + return 0; + } else { + return -EIO; + } +} + +static int sbp_send_status(struct sbp_target_request *req) +{ + int ret, length; + struct sbp_login_descriptor *login = req->login; + + length = (((be32_to_cpu(req->status.status) >> 24) & 0x07) + 1) * 4; + + ret = sbp_run_request_transaction(req, TCODE_WRITE_BLOCK_REQUEST, + login->status_fifo_addr, &req->status, length); + if (ret != RCODE_COMPLETE) { + pr_debug("sbp_send_status: write failed: 0x%x\n", ret); + return -EIO; + } + + pr_debug("sbp_send_status: status write complete for ORB: 0x%llx\n", + req->orb_pointer); + + return 0; +} + +static void sbp_sense_mangle(struct sbp_target_request *req) +{ + struct se_cmd *se_cmd = &req->se_cmd; + u8 *sense = req->sense_buf; + u8 *status = req->status.data; + + WARN_ON(se_cmd->scsi_sense_length < 18); + + switch (sense[0] & 0x7f) { /* sfmt */ + case 0x70: /* current, fixed */ + status[0] = 0 << 6; + break; + case 0x71: /* deferred, fixed */ + status[0] = 1 << 6; + break; + case 0x72: /* current, descriptor */ + case 0x73: /* deferred, descriptor */ + default: + /* + * TODO: SBP-3 specifies what we should do with descriptor + * format sense data + */ + pr_err("sbp_send_sense: unknown sense format: 0x%x\n", + sense[0]); + req->status.status |= cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_DEAD(0) | + STATUS_BLOCK_LEN(1) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_REQUEST_ABORTED)); + return; + } + + status[0] |= se_cmd->scsi_status & 0x3f;/* status */ + status[1] = + (sense[0] & 0x80) | /* valid */ + ((sense[2] & 0xe0) >> 1) | /* mark, eom, ili */ + (sense[2] & 0x0f); /* sense_key */ + status[2] = se_cmd->scsi_asc; /* sense_code */ + status[3] = se_cmd->scsi_ascq; /* sense_qualifier */ + + /* information */ + status[4] = sense[3]; + status[5] = sense[4]; + status[6] = sense[5]; + status[7] = sense[6]; + + /* CDB-dependent */ + status[8] = sense[8]; + status[9] = sense[9]; + status[10] = sense[10]; + status[11] = sense[11]; + + /* fru */ + status[12] = sense[14]; + + /* sense_key-dependent */ + status[13] = sense[15]; + status[14] = sense[16]; + status[15] = sense[17]; + + req->status.status |= cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_DEAD(0) | + STATUS_BLOCK_LEN(5) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_OK)); +} + +static int sbp_send_sense(struct sbp_target_request *req) +{ + struct se_cmd *se_cmd = &req->se_cmd; + + if (se_cmd->scsi_sense_length) { + sbp_sense_mangle(req); + } else { + req->status.status |= cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_DEAD(0) | + STATUS_BLOCK_LEN(1) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_OK)); + } + + return sbp_send_status(req); +} + +static void sbp_free_request(struct sbp_target_request *req) +{ + kfree(req->pg_tbl); + kfree(req->cmd_buf); + kfree(req); +} + +static void sbp_mgt_agent_process(struct work_struct *work) +{ + struct sbp_management_agent *agent = + container_of(work, struct sbp_management_agent, work); + struct sbp_management_request *req = agent->request; + int ret; + int status_data_len = 0; + + /* fetch the ORB from the initiator */ + ret = sbp_run_transaction(req->card, TCODE_READ_BLOCK_REQUEST, + req->node_addr, req->generation, req->speed, + agent->orb_offset, &req->orb, sizeof(req->orb)); + if (ret != RCODE_COMPLETE) { + pr_debug("mgt_orb fetch failed: %x\n", ret); + goto out; + } + + pr_debug("mgt_orb ptr1:0x%llx ptr2:0x%llx misc:0x%x len:0x%x status_fifo:0x%llx\n", + sbp2_pointer_to_addr(&req->orb.ptr1), + sbp2_pointer_to_addr(&req->orb.ptr2), + be32_to_cpu(req->orb.misc), be32_to_cpu(req->orb.length), + sbp2_pointer_to_addr(&req->orb.status_fifo)); + + if (!ORB_NOTIFY(be32_to_cpu(req->orb.misc)) || + ORB_REQUEST_FORMAT(be32_to_cpu(req->orb.misc)) != 0) { + pr_err("mgt_orb bad request\n"); + goto out; + } + + switch (MANAGEMENT_ORB_FUNCTION(be32_to_cpu(req->orb.misc))) { + case MANAGEMENT_ORB_FUNCTION_LOGIN: + sbp_management_request_login(agent, req, &status_data_len); + break; + + case MANAGEMENT_ORB_FUNCTION_QUERY_LOGINS: + sbp_management_request_query_logins(agent, req, + &status_data_len); + break; + + case MANAGEMENT_ORB_FUNCTION_RECONNECT: + sbp_management_request_reconnect(agent, req, &status_data_len); + break; + + case MANAGEMENT_ORB_FUNCTION_SET_PASSWORD: + pr_notice("SET PASSWORD not implemented\n"); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_REQ_TYPE_NOTSUPP)); + + break; + + case MANAGEMENT_ORB_FUNCTION_LOGOUT: + sbp_management_request_logout(agent, req, &status_data_len); + break; + + case MANAGEMENT_ORB_FUNCTION_ABORT_TASK: + pr_notice("ABORT TASK not implemented\n"); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_REQ_TYPE_NOTSUPP)); + + break; + + case MANAGEMENT_ORB_FUNCTION_ABORT_TASK_SET: + pr_notice("ABORT TASK SET not implemented\n"); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_REQ_TYPE_NOTSUPP)); + + break; + + case MANAGEMENT_ORB_FUNCTION_LOGICAL_UNIT_RESET: + pr_notice("LOGICAL UNIT RESET not implemented\n"); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_REQ_TYPE_NOTSUPP)); + + break; + + case MANAGEMENT_ORB_FUNCTION_TARGET_RESET: + pr_notice("TARGET RESET not implemented\n"); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_REQ_TYPE_NOTSUPP)); + + break; + + default: + pr_notice("unknown management function 0x%x\n", + MANAGEMENT_ORB_FUNCTION(be32_to_cpu(req->orb.misc))); + + req->status.status = cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_REQUEST_COMPLETE) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_REQ_TYPE_NOTSUPP)); + + break; + } + + req->status.status |= cpu_to_be32( + STATUS_BLOCK_SRC(1) | /* Response to ORB, next_ORB absent */ + STATUS_BLOCK_LEN(DIV_ROUND_UP(status_data_len, 4) + 1) | + STATUS_BLOCK_ORB_OFFSET_HIGH(agent->orb_offset >> 32)); + req->status.orb_low = cpu_to_be32(agent->orb_offset); + + /* write the status block back to the initiator */ + ret = sbp_run_transaction(req->card, TCODE_WRITE_BLOCK_REQUEST, + req->node_addr, req->generation, req->speed, + sbp2_pointer_to_addr(&req->orb.status_fifo), + &req->status, 8 + status_data_len); + if (ret != RCODE_COMPLETE) { + pr_debug("mgt_orb status write failed: %x\n", ret); + goto out; + } + +out: + fw_card_put(req->card); + kfree(req); + + spin_lock_bh(&agent->lock); + agent->state = MANAGEMENT_AGENT_STATE_IDLE; + spin_unlock_bh(&agent->lock); +} + +static void sbp_mgt_agent_rw(struct fw_card *card, + struct fw_request *request, int tcode, int destination, int source, + int generation, unsigned long long offset, void *data, size_t length, + void *callback_data) +{ + struct sbp_management_agent *agent = callback_data; + struct sbp2_pointer *ptr = data; + int rcode = RCODE_ADDRESS_ERROR; + + if (!agent->tport->enable) + goto out; + + if ((offset != agent->handler.offset) || (length != 8)) + goto out; + + if (tcode == TCODE_WRITE_BLOCK_REQUEST) { + struct sbp_management_request *req; + int prev_state; + + spin_lock_bh(&agent->lock); + prev_state = agent->state; + agent->state = MANAGEMENT_AGENT_STATE_BUSY; + spin_unlock_bh(&agent->lock); + + if (prev_state == MANAGEMENT_AGENT_STATE_BUSY) { + pr_notice("ignoring management request while busy\n"); + rcode = RCODE_CONFLICT_ERROR; + goto out; + } + + req = kzalloc(sizeof(*req), GFP_ATOMIC); + if (!req) { + rcode = RCODE_CONFLICT_ERROR; + goto out; + } + + req->card = fw_card_get(card); + req->generation = generation; + req->node_addr = source; + req->speed = fw_get_request_speed(request); + + agent->orb_offset = sbp2_pointer_to_addr(ptr); + agent->request = req; + + queue_work(system_unbound_wq, &agent->work); + rcode = RCODE_COMPLETE; + } else if (tcode == TCODE_READ_BLOCK_REQUEST) { + addr_to_sbp2_pointer(agent->orb_offset, ptr); + rcode = RCODE_COMPLETE; + } else { + rcode = RCODE_TYPE_ERROR; + } + +out: + fw_send_response(card, request, rcode); +} + +static struct sbp_management_agent *sbp_management_agent_register( + struct sbp_tport *tport) +{ + int ret; + struct sbp_management_agent *agent; + + agent = kmalloc(sizeof(*agent), GFP_KERNEL); + if (!agent) + return ERR_PTR(-ENOMEM); + + spin_lock_init(&agent->lock); + agent->tport = tport; + agent->handler.length = 0x08; + agent->handler.address_callback = sbp_mgt_agent_rw; + agent->handler.callback_data = agent; + agent->state = MANAGEMENT_AGENT_STATE_IDLE; + INIT_WORK(&agent->work, sbp_mgt_agent_process); + agent->orb_offset = 0; + agent->request = NULL; + + ret = fw_core_add_address_handler(&agent->handler, + &sbp_register_region); + if (ret < 0) { + kfree(agent); + return ERR_PTR(ret); + } + + return agent; +} + +static void sbp_management_agent_unregister(struct sbp_management_agent *agent) +{ + fw_core_remove_address_handler(&agent->handler); + cancel_work_sync(&agent->work); + kfree(agent); +} + +static int sbp_check_true(struct se_portal_group *se_tpg) +{ + return 1; +} + +static int sbp_check_false(struct se_portal_group *se_tpg) +{ + return 0; +} + +static char *sbp_get_fabric_name(void) +{ + return "sbp"; +} + +static char *sbp_get_fabric_wwn(struct se_portal_group *se_tpg) +{ + struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); + struct sbp_tport *tport = tpg->tport; + + return &tport->tport_name[0]; +} + +static u16 sbp_get_tag(struct se_portal_group *se_tpg) +{ + struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); + return tpg->tport_tpgt; +} + +static u32 sbp_get_default_depth(struct se_portal_group *se_tpg) +{ + return 1; +} + +static struct se_node_acl *sbp_alloc_fabric_acl(struct se_portal_group *se_tpg) +{ + struct sbp_nacl *nacl; + + nacl = kzalloc(sizeof(struct sbp_nacl), GFP_KERNEL); + if (!nacl) { + pr_err("Unable to alocate struct sbp_nacl\n"); + return NULL; + } + + return &nacl->se_node_acl; +} + +static void sbp_release_fabric_acl( + struct se_portal_group *se_tpg, + struct se_node_acl *se_nacl) +{ + struct sbp_nacl *nacl = + container_of(se_nacl, struct sbp_nacl, se_node_acl); + kfree(nacl); +} + +static u32 sbp_tpg_get_inst_index(struct se_portal_group *se_tpg) +{ + return 1; +} + +static void sbp_release_cmd(struct se_cmd *se_cmd) +{ + struct sbp_target_request *req = container_of(se_cmd, + struct sbp_target_request, se_cmd); + + sbp_free_request(req); +} + +static int sbp_shutdown_session(struct se_session *se_sess) +{ + return 0; +} + +static void sbp_close_session(struct se_session *se_sess) +{ + return; +} + +static u32 sbp_sess_get_index(struct se_session *se_sess) +{ + return 0; +} + +static int sbp_write_pending(struct se_cmd *se_cmd) +{ + struct sbp_target_request *req = container_of(se_cmd, + struct sbp_target_request, se_cmd); + int ret; + + ret = sbp_rw_data(req); + if (ret) { + req->status.status |= cpu_to_be32( + STATUS_BLOCK_RESP( + STATUS_RESP_TRANSPORT_FAILURE) | + STATUS_BLOCK_DEAD(0) | + STATUS_BLOCK_LEN(1) | + STATUS_BLOCK_SBP_STATUS( + SBP_STATUS_UNSPECIFIED_ERROR)); + sbp_send_status(req); + return ret; + } + + transport_generic_process_write(se_cmd); + + return 0; +} + +static int sbp_write_pending_status(struct se_cmd *se_cmd) +{ + return 0; +} + +static void sbp_set_default_node_attrs(struct se_node_acl *nacl) +{ + return; +} + +static u32 sbp_get_task_tag(struct se_cmd *se_cmd) +{ + struct sbp_target_request *req = container_of(se_cmd, + struct sbp_target_request, se_cmd); + + /* only used for printk until we do TMRs */ + return (u32)req->orb_pointer; +} + +static int sbp_get_cmd_state(struct se_cmd *se_cmd) +{ + return 0; +} + +static int sbp_queue_data_in(struct se_cmd *se_cmd) +{ + struct sbp_target_request *req = container_of(se_cmd, + struct sbp_target_request, se_cmd); + int ret; + + ret = sbp_rw_data(req); + if (ret) { + req->status.status |= cpu_to_be32( + STATUS_BLOCK_RESP(STATUS_RESP_TRANSPORT_FAILURE) | + STATUS_BLOCK_DEAD(0) | + STATUS_BLOCK_LEN(1) | + STATUS_BLOCK_SBP_STATUS(SBP_STATUS_UNSPECIFIED_ERROR)); + sbp_send_status(req); + return ret; + } + + return sbp_send_sense(req); +} + +/* + * Called after command (no data transfer) or after the write (to device) + * operation is completed + */ +static int sbp_queue_status(struct se_cmd *se_cmd) +{ + struct sbp_target_request *req = container_of(se_cmd, + struct sbp_target_request, se_cmd); + + return sbp_send_sense(req); +} + +static int sbp_queue_tm_rsp(struct se_cmd *se_cmd) +{ + return 0; +} + +static u16 sbp_set_fabric_sense_len(struct se_cmd *se_cmd, u32 sense_length) +{ + return 0; +} + +static u16 sbp_get_fabric_sense_len(void) +{ + return 0; +} + +static int sbp_check_stop_free(struct se_cmd *se_cmd) +{ + struct sbp_target_request *req = container_of(se_cmd, + struct sbp_target_request, se_cmd); + + transport_generic_free_cmd(&req->se_cmd, 0); + return 1; +} + +/* + * Handlers for Serial Bus Protocol 2/3 (SBP-2 / SBP-3) + */ +static u8 sbp_get_fabric_proto_ident(struct se_portal_group *se_tpg) +{ + /* + * Return a IEEE 1394 SCSI Protocol identifier for loopback operations + * This is defined in section 7.5.1 Table 362 in spc4r17 + */ + return SCSI_PROTOCOL_SBP; +} + +static u32 sbp_get_pr_transport_id( + struct se_portal_group *se_tpg, + struct se_node_acl *se_nacl, + struct t10_pr_registration *pr_reg, + int *format_code, + unsigned char *buf) +{ + int ret; + + /* + * Set PROTOCOL IDENTIFIER to 3h for SBP + */ + buf[0] = SCSI_PROTOCOL_SBP; + /* + * From spc4r17, 7.5.4.4 TransportID for initiator ports using SCSI + * over IEEE 1394 + */ + ret = hex2bin(&buf[8], se_nacl->initiatorname, 8); + if (ret < 0) + pr_debug("sbp transport_id: invalid hex string\n"); + + /* + * The IEEE 1394 Transport ID is a hardcoded 24-byte length + */ + return 24; +} + +static u32 sbp_get_pr_transport_id_len( + struct se_portal_group *se_tpg, + struct se_node_acl *se_nacl, + struct t10_pr_registration *pr_reg, + int *format_code) +{ + *format_code = 0; + /* + * From spc4r17, 7.5.4.4 TransportID for initiator ports using SCSI + * over IEEE 1394 + * + * The SBP Transport ID is a hardcoded 24-byte length + */ + return 24; +} + +/* + * Used for handling SCSI fabric dependent TransportIDs in SPC-3 and above + * Persistent Reservation SPEC_I_PT=1 and PROUT REGISTER_AND_MOVE operations. + */ +static char *sbp_parse_pr_out_transport_id( + struct se_portal_group *se_tpg, + const char *buf, + u32 *out_tid_len, + char **port_nexus_ptr) +{ + /* + * Assume the FORMAT CODE 00b from spc4r17, 7.5.4.4 TransportID + * for initiator ports using SCSI over SBP Serial SCSI Protocol + * + * The TransportID for a IEEE 1394 Initiator Port is of fixed size of + * 24 bytes, and IEEE 1394 does not contain a I_T nexus identifier, + * so we return the **port_nexus_ptr set to NULL. + */ + *port_nexus_ptr = NULL; + *out_tid_len = 24; + + return (char *)&buf[8]; +} + +static int sbp_count_se_tpg_luns(struct se_portal_group *tpg) +{ + int i, count = 0; + + spin_lock(&tpg->tpg_lun_lock); + for (i = 0; i < TRANSPORT_MAX_LUNS_PER_TPG; i++) { + struct se_lun *se_lun = tpg->tpg_lun_list[i]; + + if (se_lun->lun_status == TRANSPORT_LUN_STATUS_FREE) + continue; + + count++; + } + spin_unlock(&tpg->tpg_lun_lock); + + return count; +} + +static int sbp_update_unit_directory(struct sbp_tport *tport) +{ + int num_luns, num_entries, idx = 0, mgt_agt_addr, ret, i; + u32 *data; + + if (tport->unit_directory.data) { + fw_core_remove_descriptor(&tport->unit_directory); + kfree(tport->unit_directory.data); + tport->unit_directory.data = NULL; + } + + if (!tport->enable || !tport->tpg) + return 0; + + num_luns = sbp_count_se_tpg_luns(&tport->tpg->se_tpg); + + /* + * Number of entries in the final unit directory: + * - all of those in the template + * - management_agent + * - unit_characteristics + * - reconnect_timeout + * - unit unique ID + * - one for each LUN + * + * MUST NOT include leaf or sub-directory entries + */ + num_entries = ARRAY_SIZE(sbp_unit_directory_template) + 4 + num_luns; + + if (tport->directory_id != -1) + num_entries++; + + /* allocate num_entries + 4 for the header and unique ID leaf */ + data = kcalloc((num_entries + 4), sizeof(u32), GFP_KERNEL); + if (!data) + return -ENOMEM; + + /* directory_length */ + data[idx++] = num_entries << 16; + + /* directory_id */ + if (tport->directory_id != -1) + data[idx++] = (CSR_DIRECTORY_ID << 24) | tport->directory_id; + + /* unit directory template */ + memcpy(&data[idx], sbp_unit_directory_template, + sizeof(sbp_unit_directory_template)); + idx += ARRAY_SIZE(sbp_unit_directory_template); + + /* management_agent */ + mgt_agt_addr = (tport->mgt_agt->handler.offset - CSR_REGISTER_BASE) / 4; + data[idx++] = 0x54000000 | (mgt_agt_addr & 0x00ffffff); + + /* unit_characteristics */ + data[idx++] = 0x3a000000 | + (((tport->mgt_orb_timeout * 2) << 8) & 0xff00) | + SBP_ORB_FETCH_SIZE; + + /* reconnect_timeout */ + data[idx++] = 0x3d000000 | (tport->max_reconnect_timeout & 0xffff); + + /* unit unique ID (leaf is just after LUNs) */ + data[idx++] = 0x8d000000 | (num_luns + 1); + + spin_lock(&tport->tpg->se_tpg.tpg_lun_lock); + for (i = 0; i < TRANSPORT_MAX_LUNS_PER_TPG; i++) { + struct se_lun *se_lun = tport->tpg->se_tpg.tpg_lun_list[i]; + struct se_device *dev; + int type; + + if (se_lun->lun_status == TRANSPORT_LUN_STATUS_FREE) + continue; + + spin_unlock(&tport->tpg->se_tpg.tpg_lun_lock); + + dev = se_lun->lun_se_dev; + type = dev->transport->get_device_type(dev); + + /* logical_unit_number */ + data[idx++] = 0x14000000 | + ((type << 16) & 0x1f0000) | + (se_lun->unpacked_lun & 0xffff); + + spin_lock(&tport->tpg->se_tpg.tpg_lun_lock); + } + spin_unlock(&tport->tpg->se_tpg.tpg_lun_lock); + + /* unit unique ID leaf */ + data[idx++] = 2 << 16; + data[idx++] = tport->guid >> 32; + data[idx++] = tport->guid; + + tport->unit_directory.length = idx; + tport->unit_directory.key = (CSR_DIRECTORY | CSR_UNIT) << 24; + tport->unit_directory.data = data; + + ret = fw_core_add_descriptor(&tport->unit_directory); + if (ret < 0) { + kfree(tport->unit_directory.data); + tport->unit_directory.data = NULL; + } + + return ret; +} + +static ssize_t sbp_parse_wwn(const char *name, u64 *wwn, int strict) +{ + const char *cp; + char c, nibble; + int pos = 0, err; + + *wwn = 0; + for (cp = name; cp < &name[SBP_NAMELEN - 1]; cp++) { + c = *cp; + if (c == '\n' && cp[1] == '\0') + continue; + if (c == '\0') { + err = 2; + if (pos != 16) + goto fail; + return cp - name; + } + err = 3; + if (isdigit(c)) + nibble = c - '0'; + else if (isxdigit(c) && (islower(c) || !strict)) + nibble = tolower(c) - 'a' + 10; + else + goto fail; + *wwn = (*wwn << 4) | nibble; + pos++; + } + err = 4; +fail: + printk(KERN_INFO "err %u len %zu pos %u\n", + err, cp - name, pos); + return -1; +} + +static ssize_t sbp_format_wwn(char *buf, size_t len, u64 wwn) +{ + return snprintf(buf, len, "%016llx", wwn); +} + +static struct se_node_acl *sbp_make_nodeacl( + struct se_portal_group *se_tpg, + struct config_group *group, + const char *name) +{ + struct se_node_acl *se_nacl, *se_nacl_new; + struct sbp_nacl *nacl; + u64 guid = 0; + u32 nexus_depth = 1; + + if (sbp_parse_wwn(name, &guid, 1) < 0) + return ERR_PTR(-EINVAL); + + se_nacl_new = sbp_alloc_fabric_acl(se_tpg); + if (!se_nacl_new) + return ERR_PTR(-ENOMEM); + + /* + * se_nacl_new may be released by core_tpg_add_initiator_node_acl() + * when converting a NodeACL from demo mode -> explict + */ + se_nacl = core_tpg_add_initiator_node_acl(se_tpg, se_nacl_new, + name, nexus_depth); + if (IS_ERR(se_nacl)) { + sbp_release_fabric_acl(se_tpg, se_nacl_new); + return se_nacl; + } + + nacl = container_of(se_nacl, struct sbp_nacl, se_node_acl); + nacl->guid = guid; + sbp_format_wwn(nacl->iport_name, SBP_NAMELEN, guid); + + return se_nacl; +} + +static void sbp_drop_nodeacl(struct se_node_acl *se_acl) +{ + struct sbp_nacl *nacl = + container_of(se_acl, struct sbp_nacl, se_node_acl); + + core_tpg_del_initiator_node_acl(se_acl->se_tpg, se_acl, 1); + kfree(nacl); +} + +static int sbp_post_link_lun( + struct se_portal_group *se_tpg, + struct se_lun *se_lun) +{ + struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); + + return sbp_update_unit_directory(tpg->tport); +} + +static void sbp_pre_unlink_lun( + struct se_portal_group *se_tpg, + struct se_lun *se_lun) +{ + struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); + struct sbp_tport *tport = tpg->tport; + int ret; + + if (sbp_count_se_tpg_luns(&tpg->se_tpg) == 0) + tport->enable = 0; + + ret = sbp_update_unit_directory(tport); + if (ret < 0) + pr_err("unlink LUN: failed to update unit directory\n"); +} + +static struct se_portal_group *sbp_make_tpg( + struct se_wwn *wwn, + struct config_group *group, + const char *name) +{ + struct sbp_tport *tport = + container_of(wwn, struct sbp_tport, tport_wwn); + + struct sbp_tpg *tpg; + unsigned long tpgt; + int ret; + + if (strstr(name, "tpgt_") != name) + return ERR_PTR(-EINVAL); + if (kstrtoul(name + 5, 10, &tpgt) || tpgt > UINT_MAX) + return ERR_PTR(-EINVAL); + + if (tport->tpg) { + pr_err("Only one TPG per Unit is possible.\n"); + return ERR_PTR(-EBUSY); + } + + tpg = kzalloc(sizeof(*tpg), GFP_KERNEL); + if (!tpg) { + pr_err("Unable to allocate struct sbp_tpg\n"); + return ERR_PTR(-ENOMEM); + } + + tpg->tport = tport; + tpg->tport_tpgt = tpgt; + tport->tpg = tpg; + + /* default attribute values */ + tport->enable = 0; + tport->directory_id = -1; + tport->mgt_orb_timeout = 15; + tport->max_reconnect_timeout = 5; + tport->max_logins_per_lun = 1; + + tport->mgt_agt = sbp_management_agent_register(tport); + if (IS_ERR(tport->mgt_agt)) { + ret = PTR_ERR(tport->mgt_agt); + kfree(tpg); + return ERR_PTR(ret); + } + + ret = core_tpg_register(&sbp_fabric_configfs->tf_ops, wwn, + &tpg->se_tpg, (void *)tpg, + TRANSPORT_TPG_TYPE_NORMAL); + if (ret < 0) { + sbp_management_agent_unregister(tport->mgt_agt); + kfree(tpg); + return ERR_PTR(ret); + } + + return &tpg->se_tpg; +} + +static void sbp_drop_tpg(struct se_portal_group *se_tpg) +{ + struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); + struct sbp_tport *tport = tpg->tport; + + core_tpg_deregister(se_tpg); + sbp_management_agent_unregister(tport->mgt_agt); + tport->tpg = NULL; + kfree(tpg); +} + +static struct se_wwn *sbp_make_tport( + struct target_fabric_configfs *tf, + struct config_group *group, + const char *name) +{ + struct sbp_tport *tport; + u64 guid = 0; + + if (sbp_parse_wwn(name, &guid, 1) < 0) + return ERR_PTR(-EINVAL); + + tport = kzalloc(sizeof(*tport), GFP_KERNEL); + if (!tport) { + pr_err("Unable to allocate struct sbp_tport\n"); + return ERR_PTR(-ENOMEM); + } + + tport->guid = guid; + sbp_format_wwn(tport->tport_name, SBP_NAMELEN, guid); + + return &tport->tport_wwn; +} + +static void sbp_drop_tport(struct se_wwn *wwn) +{ + struct sbp_tport *tport = + container_of(wwn, struct sbp_tport, tport_wwn); + + kfree(tport); +} + +static ssize_t sbp_wwn_show_attr_version( + struct target_fabric_configfs *tf, + char *page) +{ + return sprintf(page, "FireWire SBP fabric module %s\n", SBP_VERSION); +} + +TF_WWN_ATTR_RO(sbp, version); + +static struct configfs_attribute *sbp_wwn_attrs[] = { + &sbp_wwn_version.attr, + NULL, +}; + +static ssize_t sbp_tpg_show_directory_id( + struct se_portal_group *se_tpg, + char *page) +{ + struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); + struct sbp_tport *tport = tpg->tport; + + if (tport->directory_id == -1) + return sprintf(page, "implicit\n"); + else + return sprintf(page, "%06x\n", tport->directory_id); +} + +static ssize_t sbp_tpg_store_directory_id( + struct se_portal_group *se_tpg, + const char *page, + size_t count) +{ + struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); + struct sbp_tport *tport = tpg->tport; + unsigned long val; + + if (tport->enable) { + pr_err("Cannot change the directory_id on an active target.\n"); + return -EBUSY; + } + + if (strstr(page, "implicit") == page) { + tport->directory_id = -1; + } else { + if (kstrtoul(page, 16, &val) < 0) + return -EINVAL; + if (val > 0xffffff) + return -EINVAL; + + tport->directory_id = val; + } + + return count; +} + +static ssize_t sbp_tpg_show_enable( + struct se_portal_group *se_tpg, + char *page) +{ + struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); + struct sbp_tport *tport = tpg->tport; + return sprintf(page, "%d\n", tport->enable); +} + +static ssize_t sbp_tpg_store_enable( + struct se_portal_group *se_tpg, + const char *page, + size_t count) +{ + struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); + struct sbp_tport *tport = tpg->tport; + unsigned long val; + int ret; + + if (kstrtoul(page, 0, &val) < 0) + return -EINVAL; + if ((val != 0) && (val != 1)) + return -EINVAL; + + if (tport->enable == val) + return count; + + if (val) { + if (sbp_count_se_tpg_luns(&tpg->se_tpg) == 0) { + pr_err("Cannot enable a target with no LUNs!\n"); + return -EINVAL; + } + } else { + /* XXX: force-shutdown sessions instead? */ + spin_lock_bh(&se_tpg->session_lock); + if (!list_empty(&se_tpg->tpg_sess_list)) { + spin_unlock_bh(&se_tpg->session_lock); + return -EBUSY; + } + spin_unlock_bh(&se_tpg->session_lock); + } + + tport->enable = val; + + ret = sbp_update_unit_directory(tport); + if (ret < 0) { + pr_err("Could not update Config ROM\n"); + return ret; + } + + return count; +} + +TF_TPG_BASE_ATTR(sbp, directory_id, S_IRUGO | S_IWUSR); +TF_TPG_BASE_ATTR(sbp, enable, S_IRUGO | S_IWUSR); + +static struct configfs_attribute *sbp_tpg_base_attrs[] = { + &sbp_tpg_directory_id.attr, + &sbp_tpg_enable.attr, + NULL, +}; + +static ssize_t sbp_tpg_attrib_show_mgt_orb_timeout( + struct se_portal_group *se_tpg, + char *page) +{ + struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); + struct sbp_tport *tport = tpg->tport; + return sprintf(page, "%d\n", tport->mgt_orb_timeout); +} + +static ssize_t sbp_tpg_attrib_store_mgt_orb_timeout( + struct se_portal_group *se_tpg, + const char *page, + size_t count) +{ + struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); + struct sbp_tport *tport = tpg->tport; + unsigned long val; + int ret; + + if (kstrtoul(page, 0, &val) < 0) + return -EINVAL; + if ((val < 1) || (val > 127)) + return -EINVAL; + + if (tport->mgt_orb_timeout == val) + return count; + + tport->mgt_orb_timeout = val; + + ret = sbp_update_unit_directory(tport); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t sbp_tpg_attrib_show_max_reconnect_timeout( + struct se_portal_group *se_tpg, + char *page) +{ + struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); + struct sbp_tport *tport = tpg->tport; + return sprintf(page, "%d\n", tport->max_reconnect_timeout); +} + +static ssize_t sbp_tpg_attrib_store_max_reconnect_timeout( + struct se_portal_group *se_tpg, + const char *page, + size_t count) +{ + struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); + struct sbp_tport *tport = tpg->tport; + unsigned long val; + int ret; + + if (kstrtoul(page, 0, &val) < 0) + return -EINVAL; + if ((val < 1) || (val > 32767)) + return -EINVAL; + + if (tport->max_reconnect_timeout == val) + return count; + + tport->max_reconnect_timeout = val; + + ret = sbp_update_unit_directory(tport); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t sbp_tpg_attrib_show_max_logins_per_lun( + struct se_portal_group *se_tpg, + char *page) +{ + struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); + struct sbp_tport *tport = tpg->tport; + return sprintf(page, "%d\n", tport->max_logins_per_lun); +} + +static ssize_t sbp_tpg_attrib_store_max_logins_per_lun( + struct se_portal_group *se_tpg, + const char *page, + size_t count) +{ + struct sbp_tpg *tpg = container_of(se_tpg, struct sbp_tpg, se_tpg); + struct sbp_tport *tport = tpg->tport; + unsigned long val; + + if (kstrtoul(page, 0, &val) < 0) + return -EINVAL; + if ((val < 1) || (val > 127)) + return -EINVAL; + + /* XXX: also check against current count? */ + + tport->max_logins_per_lun = val; + + return count; +} + +TF_TPG_ATTRIB_ATTR(sbp, mgt_orb_timeout, S_IRUGO | S_IWUSR); +TF_TPG_ATTRIB_ATTR(sbp, max_reconnect_timeout, S_IRUGO | S_IWUSR); +TF_TPG_ATTRIB_ATTR(sbp, max_logins_per_lun, S_IRUGO | S_IWUSR); + +static struct configfs_attribute *sbp_tpg_attrib_attrs[] = { + &sbp_tpg_attrib_mgt_orb_timeout.attr, + &sbp_tpg_attrib_max_reconnect_timeout.attr, + &sbp_tpg_attrib_max_logins_per_lun.attr, + NULL, +}; + +static struct target_core_fabric_ops sbp_ops = { + .get_fabric_name = sbp_get_fabric_name, + .get_fabric_proto_ident = sbp_get_fabric_proto_ident, + .tpg_get_wwn = sbp_get_fabric_wwn, + .tpg_get_tag = sbp_get_tag, + .tpg_get_default_depth = sbp_get_default_depth, + .tpg_get_pr_transport_id = sbp_get_pr_transport_id, + .tpg_get_pr_transport_id_len = sbp_get_pr_transport_id_len, + .tpg_parse_pr_out_transport_id = sbp_parse_pr_out_transport_id, + .tpg_check_demo_mode = sbp_check_true, + .tpg_check_demo_mode_cache = sbp_check_true, + .tpg_check_demo_mode_write_protect = sbp_check_false, + .tpg_check_prod_mode_write_protect = sbp_check_false, + .tpg_alloc_fabric_acl = sbp_alloc_fabric_acl, + .tpg_release_fabric_acl = sbp_release_fabric_acl, + .tpg_get_inst_index = sbp_tpg_get_inst_index, + .release_cmd = sbp_release_cmd, + .shutdown_session = sbp_shutdown_session, + .close_session = sbp_close_session, + .sess_get_index = sbp_sess_get_index, + .write_pending = sbp_write_pending, + .write_pending_status = sbp_write_pending_status, + .set_default_node_attributes = sbp_set_default_node_attrs, + .get_task_tag = sbp_get_task_tag, + .get_cmd_state = sbp_get_cmd_state, + .queue_data_in = sbp_queue_data_in, + .queue_status = sbp_queue_status, + .queue_tm_rsp = sbp_queue_tm_rsp, + .get_fabric_sense_len = sbp_get_fabric_sense_len, + .set_fabric_sense_len = sbp_set_fabric_sense_len, + .check_stop_free = sbp_check_stop_free, + + .fabric_make_wwn = sbp_make_tport, + .fabric_drop_wwn = sbp_drop_tport, + .fabric_make_tpg = sbp_make_tpg, + .fabric_drop_tpg = sbp_drop_tpg, + .fabric_post_link = sbp_post_link_lun, + .fabric_pre_unlink = sbp_pre_unlink_lun, + .fabric_make_np = NULL, + .fabric_drop_np = NULL, + .fabric_make_nodeacl = sbp_make_nodeacl, + .fabric_drop_nodeacl = sbp_drop_nodeacl, +}; + +static int sbp_register_configfs(void) +{ + struct target_fabric_configfs *fabric; + int ret; + + fabric = target_fabric_configfs_init(THIS_MODULE, "sbp"); + if (!fabric) { + pr_err("target_fabric_configfs_init() failed\n"); + return -ENOMEM; + } + + fabric->tf_ops = sbp_ops; + + /* + * Setup default attribute lists for various fabric->tf_cit_tmpl + */ + TF_CIT_TMPL(fabric)->tfc_wwn_cit.ct_attrs = sbp_wwn_attrs; + TF_CIT_TMPL(fabric)->tfc_tpg_base_cit.ct_attrs = sbp_tpg_base_attrs; + TF_CIT_TMPL(fabric)->tfc_tpg_attrib_cit.ct_attrs = sbp_tpg_attrib_attrs; + TF_CIT_TMPL(fabric)->tfc_tpg_param_cit.ct_attrs = NULL; + TF_CIT_TMPL(fabric)->tfc_tpg_np_base_cit.ct_attrs = NULL; + TF_CIT_TMPL(fabric)->tfc_tpg_nacl_base_cit.ct_attrs = NULL; + TF_CIT_TMPL(fabric)->tfc_tpg_nacl_attrib_cit.ct_attrs = NULL; + TF_CIT_TMPL(fabric)->tfc_tpg_nacl_auth_cit.ct_attrs = NULL; + TF_CIT_TMPL(fabric)->tfc_tpg_nacl_param_cit.ct_attrs = NULL; + + ret = target_fabric_configfs_register(fabric); + if (ret < 0) { + pr_err("target_fabric_configfs_register() failed for SBP\n"); + return ret; + } + + sbp_fabric_configfs = fabric; + + return 0; +}; + +static void sbp_deregister_configfs(void) +{ + if (!sbp_fabric_configfs) + return; + + target_fabric_configfs_deregister(sbp_fabric_configfs); + sbp_fabric_configfs = NULL; +}; + +static int __init sbp_init(void) +{ + int ret; + + ret = sbp_register_configfs(); + if (ret < 0) + return ret; + + return 0; +}; + +static void sbp_exit(void) +{ + sbp_deregister_configfs(); +}; + +MODULE_DESCRIPTION("FireWire SBP fabric driver"); +MODULE_LICENSE("GPL"); +module_init(sbp_init); +module_exit(sbp_exit); diff --git a/drivers/target/sbp/sbp_target.h b/drivers/target/sbp/sbp_target.h new file mode 100644 index 000000000000..6d0d74a2c545 --- /dev/null +++ b/drivers/target/sbp/sbp_target.h @@ -0,0 +1,251 @@ +#ifndef _SBP_BASE_H +#define _SBP_BASE_H + +#include <linux/firewire.h> +#include <linux/spinlock.h> +#include <linux/types.h> +#include <linux/workqueue.h> +#include <target/target_core_base.h> + +#define SBP_VERSION "v0.1" +#define SBP_NAMELEN 32 + +#define SBP_ORB_FETCH_SIZE 8 + +#define MANAGEMENT_AGENT_STATE_IDLE 0 +#define MANAGEMENT_AGENT_STATE_BUSY 1 + +#define ORB_NOTIFY(v) (((v) >> 31) & 0x01) +#define ORB_REQUEST_FORMAT(v) (((v) >> 29) & 0x03) + +#define MANAGEMENT_ORB_FUNCTION(v) (((v) >> 16) & 0x0f) + +#define MANAGEMENT_ORB_FUNCTION_LOGIN 0x0 +#define MANAGEMENT_ORB_FUNCTION_QUERY_LOGINS 0x1 +#define MANAGEMENT_ORB_FUNCTION_RECONNECT 0x3 +#define MANAGEMENT_ORB_FUNCTION_SET_PASSWORD 0x4 +#define MANAGEMENT_ORB_FUNCTION_LOGOUT 0x7 +#define MANAGEMENT_ORB_FUNCTION_ABORT_TASK 0xb +#define MANAGEMENT_ORB_FUNCTION_ABORT_TASK_SET 0xc +#define MANAGEMENT_ORB_FUNCTION_LOGICAL_UNIT_RESET 0xe +#define MANAGEMENT_ORB_FUNCTION_TARGET_RESET 0xf + +#define LOGIN_ORB_EXCLUSIVE(v) (((v) >> 28) & 0x01) +#define LOGIN_ORB_RESERVED(v) (((v) >> 24) & 0x0f) +#define LOGIN_ORB_RECONNECT(v) (((v) >> 20) & 0x0f) +#define LOGIN_ORB_LUN(v) (((v) >> 0) & 0xffff) +#define LOGIN_ORB_PASSWORD_LENGTH(v) (((v) >> 16) & 0xffff) +#define LOGIN_ORB_RESPONSE_LENGTH(v) (((v) >> 0) & 0xffff) + +#define RECONNECT_ORB_LOGIN_ID(v) (((v) >> 0) & 0xffff) +#define LOGOUT_ORB_LOGIN_ID(v) (((v) >> 0) & 0xffff) + +#define CMDBLK_ORB_DIRECTION(v) (((v) >> 27) & 0x01) +#define CMDBLK_ORB_SPEED(v) (((v) >> 24) & 0x07) +#define CMDBLK_ORB_MAX_PAYLOAD(v) (((v) >> 20) & 0x0f) +#define CMDBLK_ORB_PG_TBL_PRESENT(v) (((v) >> 19) & 0x01) +#define CMDBLK_ORB_PG_SIZE(v) (((v) >> 16) & 0x07) +#define CMDBLK_ORB_DATA_SIZE(v) (((v) >> 0) & 0xffff) + +#define STATUS_BLOCK_SRC(v) (((v) & 0x03) << 30) +#define STATUS_BLOCK_RESP(v) (((v) & 0x03) << 28) +#define STATUS_BLOCK_DEAD(v) (((v) ? 1 : 0) << 27) +#define STATUS_BLOCK_LEN(v) (((v) & 0x07) << 24) +#define STATUS_BLOCK_SBP_STATUS(v) (((v) & 0xff) << 16) +#define STATUS_BLOCK_ORB_OFFSET_HIGH(v) (((v) & 0xffff) << 0) + +#define STATUS_SRC_ORB_CONTINUING 0 +#define STATUS_SRC_ORB_FINISHED 1 +#define STATUS_SRC_UNSOLICITED 2 + +#define STATUS_RESP_REQUEST_COMPLETE 0 +#define STATUS_RESP_TRANSPORT_FAILURE 1 +#define STATUS_RESP_ILLEGAL_REQUEST 2 +#define STATUS_RESP_VENDOR_DEPENDENT 3 + +#define SBP_STATUS_OK 0 +#define SBP_STATUS_REQ_TYPE_NOTSUPP 1 +#define SBP_STATUS_SPEED_NOTSUPP 2 +#define SBP_STATUS_PAGE_SIZE_NOTSUPP 3 +#define SBP_STATUS_ACCESS_DENIED 4 +#define SBP_STATUS_LUN_NOTSUPP 5 +#define SBP_STATUS_PAYLOAD_TOO_SMALL 6 +/* 7 is reserved */ +#define SBP_STATUS_RESOURCES_UNAVAIL 8 +#define SBP_STATUS_FUNCTION_REJECTED 9 +#define SBP_STATUS_LOGIN_ID_UNKNOWN 10 +#define SBP_STATUS_DUMMY_ORB_COMPLETE 11 +#define SBP_STATUS_REQUEST_ABORTED 12 +#define SBP_STATUS_UNSPECIFIED_ERROR 0xff + +#define AGENT_STATE_RESET 0 +#define AGENT_STATE_ACTIVE 1 +#define AGENT_STATE_SUSPENDED 2 +#define AGENT_STATE_DEAD 3 + +struct sbp2_pointer { + __be32 high; + __be32 low; +}; + +struct sbp_command_block_orb { + struct sbp2_pointer next_orb; + struct sbp2_pointer data_descriptor; + __be32 misc; + u8 command_block[12]; +}; + +struct sbp_page_table_entry { + __be16 segment_length; + __be16 segment_base_hi; + __be32 segment_base_lo; +}; + +struct sbp_management_orb { + struct sbp2_pointer ptr1; + struct sbp2_pointer ptr2; + __be32 misc; + __be32 length; + struct sbp2_pointer status_fifo; +}; + +struct sbp_status_block { + __be32 status; + __be32 orb_low; + u8 data[24]; +}; + +struct sbp_login_response_block { + __be32 misc; + struct sbp2_pointer command_block_agent; + __be32 reconnect_hold; +}; + +struct sbp_login_descriptor { + struct sbp_session *sess; + struct list_head link; + + struct se_lun *lun; + + u64 status_fifo_addr; + int exclusive; + u16 login_id; + + struct sbp_target_agent *tgt_agt; +}; + +struct sbp_session { + spinlock_t lock; + struct se_session *se_sess; + struct list_head login_list; + struct delayed_work maint_work; + + u64 guid; /* login_owner_EUI_64 */ + int node_id; /* login_owner_ID */ + + struct fw_card *card; + int generation; + int speed; + + int reconnect_hold; + u64 reconnect_expires; +}; + +struct sbp_nacl { + /* Initiator EUI-64 */ + u64 guid; + /* ASCII formatted GUID for SBP Initiator port */ + char iport_name[SBP_NAMELEN]; + /* Returned by sbp_make_nodeacl() */ + struct se_node_acl se_node_acl; +}; + +struct sbp_tpg { + /* Target portal group tag for TCM */ + u16 tport_tpgt; + /* Pointer back to sbp_tport */ + struct sbp_tport *tport; + /* Returned by sbp_make_tpg() */ + struct se_portal_group se_tpg; +}; + +struct sbp_tport { + /* Target Unit Identifier (EUI-64) */ + u64 guid; + /* Target port name */ + char tport_name[SBP_NAMELEN]; + /* Returned by sbp_make_tport() */ + struct se_wwn tport_wwn; + + struct sbp_tpg *tpg; + + /* FireWire unit directory */ + struct fw_descriptor unit_directory; + + /* SBP Management Agent */ + struct sbp_management_agent *mgt_agt; + + /* Parameters */ + int enable; + s32 directory_id; + int mgt_orb_timeout; + int max_reconnect_timeout; + int max_logins_per_lun; +}; + +static inline u64 sbp2_pointer_to_addr(const struct sbp2_pointer *ptr) +{ + return (u64)(be32_to_cpu(ptr->high) & 0x0000ffff) << 32 | + (be32_to_cpu(ptr->low) & 0xfffffffc); +} + +static inline void addr_to_sbp2_pointer(u64 addr, struct sbp2_pointer *ptr) +{ + ptr->high = cpu_to_be32(addr >> 32); + ptr->low = cpu_to_be32(addr); +} + +struct sbp_target_agent { + spinlock_t lock; + struct fw_address_handler handler; + struct sbp_login_descriptor *login; + int state; + struct work_struct work; + u64 orb_pointer; + bool doorbell; +}; + +struct sbp_target_request { + struct sbp_login_descriptor *login; + u64 orb_pointer; + struct sbp_command_block_orb orb; + struct sbp_status_block status; + struct work_struct work; + + struct se_cmd se_cmd; + struct sbp_page_table_entry *pg_tbl; + void *cmd_buf; + + unsigned char sense_buf[TRANSPORT_SENSE_BUFFER]; +}; + +struct sbp_management_agent { + spinlock_t lock; + struct sbp_tport *tport; + struct fw_address_handler handler; + int state; + struct work_struct work; + u64 orb_offset; + struct sbp_management_request *request; +}; + +struct sbp_management_request { + struct sbp_management_orb orb; + struct sbp_status_block status; + struct fw_card *card; + int generation; + int node_addr; + int speed; +}; + +#endif diff --git a/drivers/tty/serial/8250/8250_mca.c b/drivers/tty/serial/8250/8250_mca.c deleted file mode 100644 index d20abf04541e..000000000000 --- a/drivers/tty/serial/8250/8250_mca.c +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2005 Russell King. - * Data taken from include/asm-i386/serial.h - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ -#include <linux/module.h> -#include <linux/init.h> -#include <linux/mca.h> -#include <linux/serial_8250.h> - -/* - * FIXME: Should we be doing AUTO_IRQ here? - */ -#ifdef CONFIG_SERIAL_8250_DETECT_IRQ -#define MCA_FLAGS UPF_BOOT_AUTOCONF | UPF_SKIP_TEST | UPF_AUTO_IRQ -#else -#define MCA_FLAGS UPF_BOOT_AUTOCONF | UPF_SKIP_TEST -#endif - -#define PORT(_base,_irq) \ - { \ - .iobase = _base, \ - .irq = _irq, \ - .uartclk = 1843200, \ - .iotype = UPIO_PORT, \ - .flags = MCA_FLAGS, \ - } - -static struct plat_serial8250_port mca_data[] = { - PORT(0x3220, 3), - PORT(0x3228, 3), - PORT(0x4220, 3), - PORT(0x4228, 3), - PORT(0x5220, 3), - PORT(0x5228, 3), - { }, -}; - -static struct platform_device mca_device = { - .name = "serial8250", - .id = PLAT8250_DEV_MCA, - .dev = { - .platform_data = mca_data, - }, -}; - -static int __init mca_init(void) -{ - if (!MCA_bus) - return -ENODEV; - return platform_device_register(&mca_device); -} - -module_init(mca_init); - -MODULE_AUTHOR("Russell King"); -MODULE_DESCRIPTION("8250 serial probe module for MCA ports"); -MODULE_LICENSE("GPL"); diff --git a/drivers/tty/serial/8250/Kconfig b/drivers/tty/serial/8250/Kconfig index 8bc7ecbf6bea..a27dd0569bd7 100644 --- a/drivers/tty/serial/8250/Kconfig +++ b/drivers/tty/serial/8250/Kconfig @@ -241,15 +241,6 @@ config SERIAL_8250_RSA help ::: To be written ::: -config SERIAL_8250_MCA - tristate "Support 8250-type ports on MCA buses" - depends on SERIAL_8250 != n && MCA - help - Say Y here if you have a MCA serial ports. - - To compile this driver as a module, choose M here: the module - will be called 8250_mca. - config SERIAL_8250_ACORN tristate "Acorn expansion card serial port support" depends on ARCH_ACORN && SERIAL_8250 diff --git a/drivers/tty/serial/8250/Makefile b/drivers/tty/serial/8250/Makefile index 3f35eacdf673..d7533c7d2c1a 100644 --- a/drivers/tty/serial/8250/Makefile +++ b/drivers/tty/serial/8250/Makefile @@ -15,7 +15,6 @@ obj-$(CONFIG_SERIAL_8250_ACCENT) += 8250_accent.o obj-$(CONFIG_SERIAL_8250_BOCA) += 8250_boca.o obj-$(CONFIG_SERIAL_8250_EXAR_ST16C554) += 8250_exar_st16c554.o obj-$(CONFIG_SERIAL_8250_HUB6) += 8250_hub6.o -obj-$(CONFIG_SERIAL_8250_MCA) += 8250_mca.o obj-$(CONFIG_SERIAL_8250_FSL) += 8250_fsl.o obj-$(CONFIG_SERIAL_8250_DW) += 8250_dw.o obj-$(CONFIG_SERIAL_8250_EM) += 8250_em.o diff --git a/drivers/tty/serial/sh-sci.c b/drivers/tty/serial/sh-sci.c index 3158e17b665c..4604153b7954 100644 --- a/drivers/tty/serial/sh-sci.c +++ b/drivers/tty/serial/sh-sci.c @@ -1052,9 +1052,17 @@ static int sci_request_irq(struct sci_port *port) if (SCIx_IRQ_IS_MUXED(port)) { i = SCIx_MUX_IRQ; irq = up->irq; - } else + } else { irq = port->cfg->irqs[i]; + /* + * Certain port types won't support all of the + * available interrupt sources. + */ + if (unlikely(!irq)) + continue; + } + desc = sci_irq_desc + i; port->irqstr[j] = kasprintf(GFP_KERNEL, "%s:%s", dev_name(up->dev), desc->desc); @@ -1094,6 +1102,15 @@ static void sci_free_irq(struct sci_port *port) * IRQ first. */ for (i = 0; i < SCIx_NR_IRQS; i++) { + unsigned int irq = port->cfg->irqs[i]; + + /* + * Certain port types won't support all of the available + * interrupt sources. + */ + if (unlikely(!irq)) + continue; + free_irq(port->cfg->irqs[i], port); kfree(port->irqstr[i]); @@ -1564,10 +1581,32 @@ static void sci_enable_ms(struct uart_port *port) static void sci_break_ctl(struct uart_port *port, int break_state) { - /* - * Not supported by hardware. Most parts couple break and rx - * interrupts together, with break detection always enabled. - */ + struct sci_port *s = to_sci_port(port); + struct plat_sci_reg *reg = sci_regmap[s->cfg->regtype] + SCSPTR; + unsigned short scscr, scsptr; + + /* check wheter the port has SCSPTR */ + if (!reg->size) { + /* + * Not supported by hardware. Most parts couple break and rx + * interrupts together, with break detection always enabled. + */ + return; + } + + scsptr = serial_port_in(port, SCSPTR); + scscr = serial_port_in(port, SCSCR); + + if (break_state == -1) { + scsptr = (scsptr | SCSPTR_SPB2IO) & ~SCSPTR_SPB2DT; + scscr &= ~SCSCR_TE; + } else { + scsptr = (scsptr | SCSPTR_SPB2DT) & ~SCSPTR_SPB2IO; + scscr |= SCSCR_TE; + } + + serial_port_out(port, SCSPTR, scsptr); + serial_port_out(port, SCSCR, scscr); } #ifdef CONFIG_SERIAL_SH_SCI_DMA diff --git a/drivers/tty/vt/keyboard.c b/drivers/tty/vt/keyboard.c index 3b0c4e32ed7b..48cc6f25cfd3 100644 --- a/drivers/tty/vt/keyboard.c +++ b/drivers/tty/vt/keyboard.c @@ -53,17 +53,13 @@ extern void ctrl_alt_del(void); #define KBD_DEFMODE ((1 << VC_REPEAT) | (1 << VC_META)) -/* - * Some laptops take the 789uiojklm,. keys as number pad when NumLock is on. - * This seems a good reason to start with NumLock off. On HIL keyboards - * of PARISC machines however there is no NumLock key and everyone expects the - * keypad to be used for numbers. - */ - -#if defined(CONFIG_PARISC) && (defined(CONFIG_KEYBOARD_HIL) || defined(CONFIG_KEYBOARD_HIL_OLD)) -#define KBD_DEFLEDS (1 << VC_NUMLOCK) +#if defined(CONFIG_X86) || defined(CONFIG_PARISC) +#include <asm/kbdleds.h> #else -#define KBD_DEFLEDS 0 +static inline int kbd_defleds(void) +{ + return 0; +} #endif #define KBD_DEFLOCK 0 @@ -1524,8 +1520,8 @@ int __init kbd_init(void) int error; for (i = 0; i < MAX_NR_CONSOLES; i++) { - kbd_table[i].ledflagstate = KBD_DEFLEDS; - kbd_table[i].default_ledflagstate = KBD_DEFLEDS; + kbd_table[i].ledflagstate = kbd_defleds(); + kbd_table[i].default_ledflagstate = kbd_defleds(); kbd_table[i].ledmode = LED_SHOW_FLAGS; kbd_table[i].lockstate = KBD_DEFLOCK; kbd_table[i].slockstate = 0; diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index eeea76f4dccb..a18bf6358eb8 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -129,17 +129,6 @@ config 977_WATCHDOG Not sure? It's safe to say N. -config IXP2000_WATCHDOG - tristate "IXP2000 Watchdog" - depends on ARCH_IXP2000 - help - Say Y here if to include support for the watchdog timer - in the Intel IXP2000(2400, 2800, 2850) network processors. - This driver can be built as a module by choosing M. The module - will be called ixp2000_wdt. - - Say N if you are unsure. - config IXP4XX_WATCHDOG tristate "IXP4xx Watchdog" depends on ARCH_IXP4XX @@ -543,7 +532,7 @@ config WAFER_WDT config I6300ESB_WDT tristate "Intel 6300ESB Timer/Watchdog" - depends on X86 && PCI + depends on PCI ---help--- Hardware driver for the watchdog timer built into the Intel 6300ESB controller hub. @@ -551,6 +540,19 @@ config I6300ESB_WDT To compile this driver as a module, choose M here: the module will be called i6300esb. +config IE6XX_WDT + tristate "Intel Atom E6xx Watchdog" + depends on X86 && PCI + select WATCHDOG_CORE + select MFD_CORE + select LPC_SCH + ---help--- + Hardware driver for the watchdog timer built into the Intel + Atom E6XX (TunnelCreek) processor. + + To compile this driver as a module, choose M here: the + module will be called ie6xx_wdt. + config INTEL_SCU_WATCHDOG bool "Intel SCU Watchdog for Mobile Platforms" depends on X86_MRST @@ -607,7 +609,12 @@ config IT87_WDT depends on X86 && EXPERIMENTAL ---help--- This is the driver for the hardware watchdog on the ITE IT8702, - IT8712, IT8716, IT8718, IT8720, IT8721, IT8726 Super I/O chips. + IT8712, IT8716, IT8718, IT8720, IT8721, IT8726 and IT8728 + Super I/O chips. + + If the driver does not work, then make sure that the game port in + the BIOS is enabled. + This watchdog simply watches your kernel to make sure it doesn't freeze, and if it does, it reboots your computer after a certain amount of time. @@ -780,7 +787,7 @@ config SMSC37B787_WDT config VIA_WDT tristate "VIA Watchdog Timer" - depends on X86 + depends on X86 && PCI select WATCHDOG_CORE ---help--- This is the driver for the hardware watchdog timer on VIA @@ -1138,6 +1145,7 @@ config ZVM_WATCHDOG config SH_WDT tristate "SuperH Watchdog" depends on SUPERH && (CPU_SH3 || CPU_SH4) + select WATCHDOG_CORE help This driver adds watchdog support for the integrated watchdog in the SuperH processors. If you have one of these processors and wish diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index e8f479a16402..442bfbe0882a 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -36,7 +36,6 @@ obj-$(CONFIG_OMAP_WATCHDOG) += omap_wdt.o obj-$(CONFIG_TWL4030_WATCHDOG) += twl4030_wdt.o obj-$(CONFIG_21285_WATCHDOG) += wdt285.o obj-$(CONFIG_977_WATCHDOG) += wdt977.o -obj-$(CONFIG_IXP2000_WATCHDOG) += ixp2000_wdt.o obj-$(CONFIG_IXP4XX_WATCHDOG) += ixp4xx_wdt.o obj-$(CONFIG_KS8695_WATCHDOG) += ks8695_wdt.o obj-$(CONFIG_S3C2410_WATCHDOG) += s3c2410_wdt.o @@ -81,6 +80,7 @@ obj-$(CONFIG_IB700_WDT) += ib700wdt.o obj-$(CONFIG_IBMASR) += ibmasr.o obj-$(CONFIG_WAFER_WDT) += wafer5823wdt.o obj-$(CONFIG_I6300ESB_WDT) += i6300esb.o +obj-$(CONFIG_IE6XX_WDT) += ie6xx_wdt.o obj-$(CONFIG_ITCO_WDT) += iTCO_wdt.o ifeq ($(CONFIG_ITCO_VENDOR_SUPPORT),y) obj-$(CONFIG_ITCO_WDT) += iTCO_vendor_support.o diff --git a/drivers/watchdog/ar7_wdt.c b/drivers/watchdog/ar7_wdt.c index 639ae9a23fbc..dc30dbd21cf1 100644 --- a/drivers/watchdog/ar7_wdt.c +++ b/drivers/watchdog/ar7_wdt.c @@ -282,29 +282,19 @@ static int __devinit ar7_wdt_probe(struct platform_device *pdev) platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs"); if (!ar7_regs_wdt) { pr_err("could not get registers resource\n"); - rc = -ENODEV; - goto out; - } - - if (!request_mem_region(ar7_regs_wdt->start, - resource_size(ar7_regs_wdt), LONGNAME)) { - pr_warn("watchdog I/O region busy\n"); - rc = -EBUSY; - goto out; + return -ENODEV; } - ar7_wdt = ioremap(ar7_regs_wdt->start, resource_size(ar7_regs_wdt)); + ar7_wdt = devm_request_and_ioremap(&pdev->dev, ar7_regs_wdt); if (!ar7_wdt) { pr_err("could not ioremap registers\n"); - rc = -ENXIO; - goto out_mem_region; + return -ENXIO; } vbus_clk = clk_get(NULL, "vbus"); if (IS_ERR(vbus_clk)) { pr_err("could not get vbus clock\n"); - rc = PTR_ERR(vbus_clk); - goto out_mem_region; + return PTR_ERR(vbus_clk); } ar7_wdt_disable_wdt(); @@ -314,24 +304,21 @@ static int __devinit ar7_wdt_probe(struct platform_device *pdev) rc = misc_register(&ar7_wdt_miscdev); if (rc) { pr_err("unable to register misc device\n"); - goto out_alloc; + goto out; } - goto out; + return 0; -out_alloc: - iounmap(ar7_wdt); -out_mem_region: - release_mem_region(ar7_regs_wdt->start, resource_size(ar7_regs_wdt)); out: + clk_put(vbus_clk); + vbus_clk = NULL; return rc; } static int __devexit ar7_wdt_remove(struct platform_device *pdev) { misc_deregister(&ar7_wdt_miscdev); - iounmap(ar7_wdt); - release_mem_region(ar7_regs_wdt->start, resource_size(ar7_regs_wdt)); - + clk_put(vbus_clk); + vbus_clk = NULL; return 0; } diff --git a/drivers/watchdog/hpwdt.c b/drivers/watchdog/hpwdt.c index 23885f2d56a0..2b763815aeec 100644 --- a/drivers/watchdog/hpwdt.c +++ b/drivers/watchdog/hpwdt.c @@ -861,16 +861,6 @@ static struct pci_driver hpwdt_driver = { .remove = __devexit_p(hpwdt_exit), }; -static void __exit hpwdt_cleanup(void) -{ - pci_unregister_driver(&hpwdt_driver); -} - -static int __init hpwdt_init(void) -{ - return pci_register_driver(&hpwdt_driver); -} - MODULE_AUTHOR("Tom Mingarelli"); MODULE_DESCRIPTION("hp watchdog driver"); MODULE_LICENSE("GPL"); @@ -889,5 +879,4 @@ module_param(allow_kdump, int, 0); MODULE_PARM_DESC(allow_kdump, "Start a kernel dump after NMI occurs"); #endif /* !CONFIG_HPWDT_NMI_DECODING */ -module_init(hpwdt_init); -module_exit(hpwdt_cleanup); +module_pci_driver(hpwdt_driver); diff --git a/drivers/watchdog/i6300esb.c b/drivers/watchdog/i6300esb.c index 738032a36bcf..276877d5b6a3 100644 --- a/drivers/watchdog/i6300esb.c +++ b/drivers/watchdog/i6300esb.c @@ -492,19 +492,7 @@ static struct pci_driver esb_driver = { .shutdown = esb_shutdown, }; -static int __init watchdog_init(void) -{ - return pci_register_driver(&esb_driver); -} - -static void __exit watchdog_cleanup(void) -{ - pci_unregister_driver(&esb_driver); - pr_info("Watchdog Module Unloaded\n"); -} - -module_init(watchdog_init); -module_exit(watchdog_cleanup); +module_pci_driver(esb_driver); MODULE_AUTHOR("Ross Biro and David Härdeman"); MODULE_DESCRIPTION("Watchdog driver for Intel 6300ESB chipsets"); diff --git a/drivers/watchdog/ie6xx_wdt.c b/drivers/watchdog/ie6xx_wdt.c new file mode 100644 index 000000000000..5f0d776f902c --- /dev/null +++ b/drivers/watchdog/ie6xx_wdt.c @@ -0,0 +1,348 @@ +/* + * Intel Atom E6xx Watchdog driver + * + * Copyright (C) 2011 Alexander Stein + * <[email protected]> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General + * Public License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * The full GNU General Public License is included in this + * distribution in the file called COPYING. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/watchdog.h> +#include <linux/miscdevice.h> +#include <linux/seq_file.h> +#include <linux/debugfs.h> +#include <linux/uaccess.h> +#include <linux/spinlock.h> + +#define DRIVER_NAME "ie6xx_wdt" + +#define PV1 0x00 +#define PV2 0x04 + +#define RR0 0x0c +#define RR1 0x0d +#define WDT_RELOAD 0x01 +#define WDT_TOUT 0x02 + +#define WDTCR 0x10 +#define WDT_PRE_SEL 0x04 +#define WDT_RESET_SEL 0x08 +#define WDT_RESET_EN 0x10 +#define WDT_TOUT_EN 0x20 + +#define DCR 0x14 + +#define WDTLR 0x18 +#define WDT_LOCK 0x01 +#define WDT_ENABLE 0x02 +#define WDT_TOUT_CNF 0x03 + +#define MIN_TIME 1 +#define MAX_TIME (10 * 60) /* 10 minutes */ +#define DEFAULT_TIME 60 + +static unsigned int timeout = DEFAULT_TIME; +module_param(timeout, uint, 0); +MODULE_PARM_DESC(timeout, + "Default Watchdog timer setting (" + __MODULE_STRING(DEFAULT_TIME) "s)." + "The range is from 1 to 600"); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +static u8 resetmode = 0x10; +module_param(resetmode, byte, 0); +MODULE_PARM_DESC(resetmode, + "Resetmode bits: 0x08 warm reset (cold reset otherwise), " + "0x10 reset enable, 0x20 disable toggle GPIO[4] (default=0x10)"); + +static struct { + unsigned short sch_wdtba; + struct spinlock unlock_sequence; +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs; +#endif +} ie6xx_wdt_data; + +/* + * This is needed to write to preload and reload registers + * struct ie6xx_wdt_data.unlock_sequence must be used + * to prevent sequence interrupts + */ +static void ie6xx_wdt_unlock_registers(void) +{ + outb(0x80, ie6xx_wdt_data.sch_wdtba + RR0); + outb(0x86, ie6xx_wdt_data.sch_wdtba + RR0); +} + +static int ie6xx_wdt_ping(struct watchdog_device *wdd) +{ + spin_lock(&ie6xx_wdt_data.unlock_sequence); + ie6xx_wdt_unlock_registers(); + outb(WDT_RELOAD, ie6xx_wdt_data.sch_wdtba + RR1); + spin_unlock(&ie6xx_wdt_data.unlock_sequence); + return 0; +} + +static int ie6xx_wdt_set_timeout(struct watchdog_device *wdd, unsigned int t) +{ + u32 preload; + u64 clock; + u8 wdtcr; + + /* Watchdog clock is PCI Clock (33MHz) */ + clock = 33000000; + /* and the preload value is loaded into [34:15] of the down counter */ + preload = (t * clock) >> 15; + /* + * Manual states preload must be one less. + * Does not wrap as t is at least 1 + */ + preload -= 1; + + spin_lock(&ie6xx_wdt_data.unlock_sequence); + + /* Set ResetMode & Enable prescaler for range 10ms to 10 min */ + wdtcr = resetmode & 0x38; + outb(wdtcr, ie6xx_wdt_data.sch_wdtba + WDTCR); + + ie6xx_wdt_unlock_registers(); + outl(0, ie6xx_wdt_data.sch_wdtba + PV1); + + ie6xx_wdt_unlock_registers(); + outl(preload, ie6xx_wdt_data.sch_wdtba + PV2); + + ie6xx_wdt_unlock_registers(); + outb(WDT_RELOAD | WDT_TOUT, ie6xx_wdt_data.sch_wdtba + RR1); + + spin_unlock(&ie6xx_wdt_data.unlock_sequence); + + wdd->timeout = t; + return 0; +} + +static int ie6xx_wdt_start(struct watchdog_device *wdd) +{ + ie6xx_wdt_set_timeout(wdd, wdd->timeout); + + /* Enable the watchdog timer */ + spin_lock(&ie6xx_wdt_data.unlock_sequence); + outb(WDT_ENABLE, ie6xx_wdt_data.sch_wdtba + WDTLR); + spin_unlock(&ie6xx_wdt_data.unlock_sequence); + + return 0; +} + +static int ie6xx_wdt_stop(struct watchdog_device *wdd) +{ + if (inb(ie6xx_wdt_data.sch_wdtba + WDTLR) & WDT_LOCK) + return -1; + + /* Disable the watchdog timer */ + spin_lock(&ie6xx_wdt_data.unlock_sequence); + outb(0, ie6xx_wdt_data.sch_wdtba + WDTLR); + spin_unlock(&ie6xx_wdt_data.unlock_sequence); + + return 0; +} + +static const struct watchdog_info ie6xx_wdt_info = { + .identity = "Intel Atom E6xx Watchdog", + .options = WDIOF_SETTIMEOUT | + WDIOF_MAGICCLOSE | + WDIOF_KEEPALIVEPING, +}; + +static const struct watchdog_ops ie6xx_wdt_ops = { + .owner = THIS_MODULE, + .start = ie6xx_wdt_start, + .stop = ie6xx_wdt_stop, + .ping = ie6xx_wdt_ping, + .set_timeout = ie6xx_wdt_set_timeout, +}; + +static struct watchdog_device ie6xx_wdt_dev = { + .info = &ie6xx_wdt_info, + .ops = &ie6xx_wdt_ops, + .min_timeout = MIN_TIME, + .max_timeout = MAX_TIME, +}; + +#ifdef CONFIG_DEBUG_FS + +static int ie6xx_wdt_dbg_show(struct seq_file *s, void *unused) +{ + seq_printf(s, "PV1 = 0x%08x\n", + inl(ie6xx_wdt_data.sch_wdtba + PV1)); + seq_printf(s, "PV2 = 0x%08x\n", + inl(ie6xx_wdt_data.sch_wdtba + PV2)); + seq_printf(s, "RR = 0x%08x\n", + inw(ie6xx_wdt_data.sch_wdtba + RR0)); + seq_printf(s, "WDTCR = 0x%08x\n", + inw(ie6xx_wdt_data.sch_wdtba + WDTCR)); + seq_printf(s, "DCR = 0x%08x\n", + inl(ie6xx_wdt_data.sch_wdtba + DCR)); + seq_printf(s, "WDTLR = 0x%08x\n", + inw(ie6xx_wdt_data.sch_wdtba + WDTLR)); + + seq_printf(s, "\n"); + return 0; +} + +static int ie6xx_wdt_dbg_open(struct inode *inode, struct file *file) +{ + return single_open(file, ie6xx_wdt_dbg_show, NULL); +} + +static const struct file_operations ie6xx_wdt_dbg_operations = { + .open = ie6xx_wdt_dbg_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void __devinit ie6xx_wdt_debugfs_init(void) +{ + /* /sys/kernel/debug/ie6xx_wdt */ + ie6xx_wdt_data.debugfs = debugfs_create_file("ie6xx_wdt", + S_IFREG | S_IRUGO, NULL, NULL, &ie6xx_wdt_dbg_operations); +} + +static void __devexit ie6xx_wdt_debugfs_exit(void) +{ + debugfs_remove(ie6xx_wdt_data.debugfs); +} + +#else +static void __devinit ie6xx_wdt_debugfs_init(void) +{ +} + +static void __devexit ie6xx_wdt_debugfs_exit(void) +{ +} +#endif + +static int __devinit ie6xx_wdt_probe(struct platform_device *pdev) +{ + struct resource *res; + u8 wdtlr; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!res) + return -ENODEV; + + if (!request_region(res->start, resource_size(res), pdev->name)) { + dev_err(&pdev->dev, "Watchdog region 0x%llx already in use!\n", + (u64)res->start); + return -EBUSY; + } + + ie6xx_wdt_data.sch_wdtba = res->start; + dev_dbg(&pdev->dev, "WDT = 0x%X\n", ie6xx_wdt_data.sch_wdtba); + + ie6xx_wdt_dev.timeout = timeout; + watchdog_set_nowayout(&ie6xx_wdt_dev, nowayout); + + spin_lock_init(&ie6xx_wdt_data.unlock_sequence); + + wdtlr = inb(ie6xx_wdt_data.sch_wdtba + WDTLR); + if (wdtlr & WDT_LOCK) + dev_warn(&pdev->dev, + "Watchdog Timer is Locked (Reg=0x%x)\n", wdtlr); + + ie6xx_wdt_debugfs_init(); + + ret = watchdog_register_device(&ie6xx_wdt_dev); + if (ret) { + dev_err(&pdev->dev, + "Watchdog timer: cannot register device (err =%d)\n", + ret); + goto misc_register_error; + } + + return 0; + +misc_register_error: + ie6xx_wdt_debugfs_exit(); + release_region(res->start, resource_size(res)); + ie6xx_wdt_data.sch_wdtba = 0; + return ret; +} + +static int __devexit ie6xx_wdt_remove(struct platform_device *pdev) +{ + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + ie6xx_wdt_stop(NULL); + watchdog_unregister_device(&ie6xx_wdt_dev); + ie6xx_wdt_debugfs_exit(); + release_region(res->start, resource_size(res)); + ie6xx_wdt_data.sch_wdtba = 0; + + return 0; +} + +static struct platform_driver ie6xx_wdt_driver = { + .probe = ie6xx_wdt_probe, + .remove = __devexit_p(ie6xx_wdt_remove), + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init ie6xx_wdt_init(void) +{ + /* Check boot parameters to verify that their initial values */ + /* are in range. */ + if ((timeout < MIN_TIME) || + (timeout > MAX_TIME)) { + pr_err("Watchdog timer: value of timeout %d (dec) " + "is out of range from %d to %d (dec)\n", + timeout, MIN_TIME, MAX_TIME); + return -EINVAL; + } + + return platform_driver_register(&ie6xx_wdt_driver); +} + +static void __exit ie6xx_wdt_exit(void) +{ + platform_driver_unregister(&ie6xx_wdt_driver); +} + +late_initcall(ie6xx_wdt_init); +module_exit(ie6xx_wdt_exit); + +MODULE_AUTHOR("Alexander Stein <[email protected]>"); +MODULE_DESCRIPTION("Intel Atom E6xx Watchdog Device Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/watchdog/it87_wdt.c b/drivers/watchdog/it87_wdt.c index 8a741bcb5124..d3dcc6988b5f 100644 --- a/drivers/watchdog/it87_wdt.c +++ b/drivers/watchdog/it87_wdt.c @@ -12,7 +12,8 @@ * http://www.ite.com.tw/ * * Support of the watchdog timers, which are available on - * IT8702, IT8712, IT8716, IT8718, IT8720, IT8721 and IT8726. + * IT8702, IT8712, IT8716, IT8718, IT8720, IT8721, IT8726 + * and IT8728. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -84,6 +85,7 @@ #define IT8720_ID 0x8720 #define IT8721_ID 0x8721 #define IT8726_ID 0x8726 /* the data sheet suggest wrongly 0x8716 */ +#define IT8728_ID 0x8728 /* GPIO Configuration Registers LDN=0x07 */ #define WDTCTRL 0x71 @@ -95,7 +97,7 @@ #define WDT_CIRINT 0x80 #define WDT_MOUSEINT 0x40 #define WDT_KYBINT 0x20 -#define WDT_GAMEPORT 0x10 /* not in it8718, it8720, it8721 */ +#define WDT_GAMEPORT 0x10 /* not in it8718, it8720, it8721, it8728 */ #define WDT_FORCE 0x02 #define WDT_ZERO 0x01 @@ -616,6 +618,7 @@ static int __init it87_wdt_init(void) case IT8718_ID: case IT8720_ID: case IT8721_ID: + case IT8728_ID: max_units = 65535; try_gameport = 0; break; diff --git a/drivers/watchdog/ixp2000_wdt.c b/drivers/watchdog/ixp2000_wdt.c deleted file mode 100644 index 3f047a58d3ae..000000000000 --- a/drivers/watchdog/ixp2000_wdt.c +++ /dev/null @@ -1,215 +0,0 @@ -/* - * drivers/char/watchdog/ixp2000_wdt.c - * - * Watchdog driver for Intel IXP2000 network processors - * - * Adapted from the IXP4xx watchdog driver by Lennert Buytenhek. - * The original version carries these notices: - * - * Author: Deepak Saxena <[email protected]> - * - * Copyright 2004 (c) MontaVista, Software, Inc. - * Based on sa1100 driver, Copyright (C) 2000 Oleg Drokin <[email protected]> - * - * This file is licensed under the terms of the GNU General Public - * License version 2. This program is licensed "as is" without any - * warranty of any kind, whether express or implied. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/types.h> -#include <linux/timer.h> -#include <linux/kernel.h> -#include <linux/fs.h> -#include <linux/miscdevice.h> -#include <linux/watchdog.h> -#include <linux/init.h> -#include <linux/bitops.h> -#include <linux/uaccess.h> -#include <mach/hardware.h> - -static bool nowayout = WATCHDOG_NOWAYOUT; -static unsigned int heartbeat = 60; /* (secs) Default is 1 minute */ -static unsigned long wdt_status; -static DEFINE_SPINLOCK(wdt_lock); - -#define WDT_IN_USE 0 -#define WDT_OK_TO_CLOSE 1 - -static unsigned long wdt_tick_rate; - -static void wdt_enable(void) -{ - spin_lock(&wdt_lock); - ixp2000_reg_write(IXP2000_RESET0, *(IXP2000_RESET0) | WDT_RESET_ENABLE); - ixp2000_reg_write(IXP2000_TWDE, WDT_ENABLE); - ixp2000_reg_write(IXP2000_T4_CLD, heartbeat * wdt_tick_rate); - ixp2000_reg_write(IXP2000_T4_CTL, TIMER_DIVIDER_256 | TIMER_ENABLE); - spin_unlock(&wdt_lock); -} - -static void wdt_disable(void) -{ - spin_lock(&wdt_lock); - ixp2000_reg_write(IXP2000_T4_CTL, 0); - spin_unlock(&wdt_lock); -} - -static void wdt_keepalive(void) -{ - spin_lock(&wdt_lock); - ixp2000_reg_write(IXP2000_T4_CLD, heartbeat * wdt_tick_rate); - spin_unlock(&wdt_lock); -} - -static int ixp2000_wdt_open(struct inode *inode, struct file *file) -{ - if (test_and_set_bit(WDT_IN_USE, &wdt_status)) - return -EBUSY; - - clear_bit(WDT_OK_TO_CLOSE, &wdt_status); - - wdt_enable(); - - return nonseekable_open(inode, file); -} - -static ssize_t ixp2000_wdt_write(struct file *file, const char *data, - size_t len, loff_t *ppos) -{ - if (len) { - if (!nowayout) { - size_t i; - - clear_bit(WDT_OK_TO_CLOSE, &wdt_status); - - for (i = 0; i != len; i++) { - char c; - - if (get_user(c, data + i)) - return -EFAULT; - if (c == 'V') - set_bit(WDT_OK_TO_CLOSE, &wdt_status); - } - } - wdt_keepalive(); - } - - return len; -} - - -static const struct watchdog_info ident = { - .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | - WDIOF_KEEPALIVEPING, - .identity = "IXP2000 Watchdog", -}; - -static long ixp2000_wdt_ioctl(struct file *file, unsigned int cmd, - unsigned long arg) -{ - int ret = -ENOTTY; - int time; - - switch (cmd) { - case WDIOC_GETSUPPORT: - ret = copy_to_user((struct watchdog_info *)arg, &ident, - sizeof(ident)) ? -EFAULT : 0; - break; - - case WDIOC_GETSTATUS: - ret = put_user(0, (int *)arg); - break; - - case WDIOC_GETBOOTSTATUS: - ret = put_user(0, (int *)arg); - break; - - case WDIOC_KEEPALIVE: - wdt_enable(); - ret = 0; - break; - - case WDIOC_SETTIMEOUT: - ret = get_user(time, (int *)arg); - if (ret) - break; - - if (time <= 0 || time > 60) { - ret = -EINVAL; - break; - } - - heartbeat = time; - wdt_keepalive(); - /* Fall through */ - - case WDIOC_GETTIMEOUT: - ret = put_user(heartbeat, (int *)arg); - break; - } - - return ret; -} - -static int ixp2000_wdt_release(struct inode *inode, struct file *file) -{ - if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) - wdt_disable(); - else - pr_crit("Device closed unexpectedly - timer will not stop\n"); - clear_bit(WDT_IN_USE, &wdt_status); - clear_bit(WDT_OK_TO_CLOSE, &wdt_status); - - return 0; -} - - -static const struct file_operations ixp2000_wdt_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .write = ixp2000_wdt_write, - .unlocked_ioctl = ixp2000_wdt_ioctl, - .open = ixp2000_wdt_open, - .release = ixp2000_wdt_release, -}; - -static struct miscdevice ixp2000_wdt_miscdev = { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &ixp2000_wdt_fops, -}; - -static int __init ixp2000_wdt_init(void) -{ - if ((*IXP2000_PRODUCT_ID & 0x001ffef0) == 0x00000000) { - pr_info("Unable to use IXP2000 watchdog due to IXP2800 erratum #25\n"); - return -EIO; - } - wdt_tick_rate = (*IXP2000_T1_CLD * HZ) / 256; - return misc_register(&ixp2000_wdt_miscdev); -} - -static void __exit ixp2000_wdt_exit(void) -{ - misc_deregister(&ixp2000_wdt_miscdev); -} - -module_init(ixp2000_wdt_init); -module_exit(ixp2000_wdt_exit); - -MODULE_AUTHOR("Deepak Saxena <[email protected]>"); -MODULE_DESCRIPTION("IXP2000 Network Processor Watchdog"); - -module_param(heartbeat, int, 0); -MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds (default 60s)"); - -module_param(nowayout, bool, 0); -MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); - -MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); - diff --git a/drivers/watchdog/pcwd_pci.c b/drivers/watchdog/pcwd_pci.c index c891399bed6a..ee6900da8678 100644 --- a/drivers/watchdog/pcwd_pci.c +++ b/drivers/watchdog/pcwd_pci.c @@ -707,6 +707,7 @@ static int __devinit pcipcwd_card_init(struct pci_dev *pdev, goto err_out_disable_device; } + spin_lock_init(&pcipcwd_private.io_lock); pcipcwd_private.pdev = pdev; pcipcwd_private.io_addr = pci_resource_start(pdev, 0); @@ -814,22 +815,7 @@ static struct pci_driver pcipcwd_driver = { .remove = __devexit_p(pcipcwd_card_exit), }; -static int __init pcipcwd_init_module(void) -{ - spin_lock_init(&pcipcwd_private.io_lock); - - return pci_register_driver(&pcipcwd_driver); -} - -static void __exit pcipcwd_cleanup_module(void) -{ - pci_unregister_driver(&pcipcwd_driver); - - pr_info("Watchdog Module Unloaded\n"); -} - -module_init(pcipcwd_init_module); -module_exit(pcipcwd_cleanup_module); +module_pci_driver(pcipcwd_driver); MODULE_AUTHOR("Wim Van Sebroeck <[email protected]>"); MODULE_DESCRIPTION("Berkshire PCI-PC Watchdog driver"); diff --git a/drivers/watchdog/pnx4008_wdt.c b/drivers/watchdog/pnx4008_wdt.c index 6b8432f61d05..87722e126058 100644 --- a/drivers/watchdog/pnx4008_wdt.c +++ b/drivers/watchdog/pnx4008_wdt.c @@ -32,6 +32,7 @@ #include <linux/io.h> #include <linux/slab.h> #include <linux/err.h> +#include <linux/of.h> #include <mach/hardware.h> /* WatchDog Timer - Chapter 23 Page 207 */ @@ -201,10 +202,19 @@ static int __devexit pnx4008_wdt_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_OF +static const struct of_device_id pnx4008_wdt_match[] = { + { .compatible = "nxp,pnx4008-wdt" }, + { } +}; +MODULE_DEVICE_TABLE(of, pnx4008_wdt_match); +#endif + static struct platform_driver platform_wdt_driver = { .driver = { .name = "pnx4008-watchdog", .owner = THIS_MODULE, + .of_match_table = of_match_ptr(pnx4008_wdt_match), }, .probe = pnx4008_wdt_probe, .remove = __devexit_p(pnx4008_wdt_remove), diff --git a/drivers/watchdog/s3c2410_wdt.c b/drivers/watchdog/s3c2410_wdt.c index 04e5a6de47d7..200ece5e2a22 100644 --- a/drivers/watchdog/s3c2410_wdt.c +++ b/drivers/watchdog/s3c2410_wdt.c @@ -40,6 +40,7 @@ #include <linux/cpufreq.h> #include <linux/slab.h> #include <linux/err.h> +#include <linux/of.h> #include <mach/map.h> @@ -201,7 +202,7 @@ static int s3c2410wdt_set_heartbeat(struct watchdog_device *wdd, unsigned timeou writel(count, wdt_base + S3C2410_WTDAT); writel(wtcon, wdt_base + S3C2410_WTCON); - wdd->timeout = timeout; + wdd->timeout = (count * divisor) / freq; return 0; } @@ -503,8 +504,6 @@ static const struct of_device_id s3c2410_wdt_match[] = { {}, }; MODULE_DEVICE_TABLE(of, s3c2410_wdt_match); -#else -#define s3c2410_wdt_match NULL #endif static struct platform_driver s3c2410wdt_driver = { @@ -516,7 +515,7 @@ static struct platform_driver s3c2410wdt_driver = { .driver = { .owner = THIS_MODULE, .name = "s3c2410-wdt", - .of_match_table = s3c2410_wdt_match, + .of_match_table = of_match_ptr(s3c2410_wdt_match), }, }; diff --git a/drivers/watchdog/sch311x_wdt.c b/drivers/watchdog/sch311x_wdt.c index bd86f32d63ab..f8477002b728 100644 --- a/drivers/watchdog/sch311x_wdt.c +++ b/drivers/watchdog/sch311x_wdt.c @@ -41,7 +41,6 @@ #define DRV_NAME "sch311x_wdt" /* Runtime registers */ -#define RESGEN 0x1d #define GP60 0x47 #define WDT_TIME_OUT 0x65 #define WDT_VAL 0x66 @@ -69,10 +68,6 @@ static unsigned short force_id; module_param(force_id, ushort, 0); MODULE_PARM_DESC(force_id, "Override the detected device ID"); -static unsigned short therm_trip; -module_param(therm_trip, ushort, 0); -MODULE_PARM_DESC(therm_trip, "Should a ThermTrip trigger the reset generator"); - #define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */ static int timeout = WATCHDOG_TIMEOUT; /* in seconds */ module_param(timeout, int, 0); @@ -358,26 +353,16 @@ static struct miscdevice sch311x_wdt_miscdev = { static int __devinit sch311x_wdt_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - unsigned char val; int err; spin_lock_init(&sch311x_wdt_data.io_lock); - if (!request_region(sch311x_wdt_data.runtime_reg + RESGEN, 1, - DRV_NAME)) { - dev_err(dev, "Failed to request region 0x%04x-0x%04x.\n", - sch311x_wdt_data.runtime_reg + RESGEN, - sch311x_wdt_data.runtime_reg + RESGEN); - err = -EBUSY; - goto exit; - } - if (!request_region(sch311x_wdt_data.runtime_reg + GP60, 1, DRV_NAME)) { dev_err(dev, "Failed to request region 0x%04x-0x%04x.\n", sch311x_wdt_data.runtime_reg + GP60, sch311x_wdt_data.runtime_reg + GP60); err = -EBUSY; - goto exit_release_region; + goto exit; } if (!request_region(sch311x_wdt_data.runtime_reg + WDT_TIME_OUT, 4, @@ -386,7 +371,7 @@ static int __devinit sch311x_wdt_probe(struct platform_device *pdev) sch311x_wdt_data.runtime_reg + WDT_TIME_OUT, sch311x_wdt_data.runtime_reg + WDT_CTRL); err = -EBUSY; - goto exit_release_region2; + goto exit_release_region; } /* Make sure that the watchdog is not running */ @@ -414,24 +399,13 @@ static int __devinit sch311x_wdt_probe(struct platform_device *pdev) /* Get status at boot */ sch311x_wdt_get_status(&sch311x_wdt_data.boot_status); - /* enable watchdog */ - /* -- Reset Generator -- - * Bit 0 Enable Watchdog Timer Generation: 0* = Enabled, 1 = Disabled - * Bit 1 Thermtrip Source Select: O* = No Source, 1 = Source - * Bit 2 WDT2_CTL: WDT input bit - * Bit 3-7 Reserved - */ - outb(0, sch311x_wdt_data.runtime_reg + RESGEN); - val = therm_trip ? 0x06 : 0x04; - outb(val, sch311x_wdt_data.runtime_reg + RESGEN); - sch311x_wdt_miscdev.parent = dev; err = misc_register(&sch311x_wdt_miscdev); if (err != 0) { dev_err(dev, "cannot register miscdev on minor=%d (err=%d)\n", WATCHDOG_MINOR, err); - goto exit_release_region3; + goto exit_release_region2; } dev_info(dev, @@ -440,12 +414,10 @@ static int __devinit sch311x_wdt_probe(struct platform_device *pdev) return 0; -exit_release_region3: - release_region(sch311x_wdt_data.runtime_reg + WDT_TIME_OUT, 4); exit_release_region2: - release_region(sch311x_wdt_data.runtime_reg + GP60, 1); + release_region(sch311x_wdt_data.runtime_reg + WDT_TIME_OUT, 4); exit_release_region: - release_region(sch311x_wdt_data.runtime_reg + RESGEN, 1); + release_region(sch311x_wdt_data.runtime_reg + GP60, 1); sch311x_wdt_data.runtime_reg = 0; exit: return err; @@ -461,7 +433,6 @@ static int __devexit sch311x_wdt_remove(struct platform_device *pdev) misc_deregister(&sch311x_wdt_miscdev); release_region(sch311x_wdt_data.runtime_reg + WDT_TIME_OUT, 4); release_region(sch311x_wdt_data.runtime_reg + GP60, 1); - release_region(sch311x_wdt_data.runtime_reg + RESGEN, 1); sch311x_wdt_data.runtime_reg = 0; return 0; } diff --git a/drivers/watchdog/shwdt.c b/drivers/watchdog/shwdt.c index 93958a7763e6..e5b59bebcdb1 100644 --- a/drivers/watchdog/shwdt.c +++ b/drivers/watchdog/shwdt.c @@ -3,7 +3,7 @@ * * Watchdog driver for integrated watchdog in the SuperH processors. * - * Copyright (C) 2001 - 2010 Paul Mundt <[email protected]> + * Copyright (C) 2001 - 2012 Paul Mundt <[email protected]> * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -25,16 +25,15 @@ #include <linux/platform_device.h> #include <linux/init.h> #include <linux/types.h> +#include <linux/spinlock.h> #include <linux/miscdevice.h> #include <linux/watchdog.h> -#include <linux/reboot.h> -#include <linux/notifier.h> -#include <linux/ioport.h> +#include <linux/pm_runtime.h> #include <linux/fs.h> #include <linux/mm.h> #include <linux/slab.h> #include <linux/io.h> -#include <linux/uaccess.h> +#include <linux/clk.h> #include <asm/watchdog.h> #define DRV_NAME "sh-wdt" @@ -69,10 +68,6 @@ static int clock_division_ratio = WTCSR_CKS_4096; #define next_ping_period(cks) (jiffies + msecs_to_jiffies(cks - 4)) -static const struct watchdog_info sh_wdt_info; -static struct platform_device *sh_wdt_dev; -static DEFINE_SPINLOCK(shwdt_lock); - #define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat */ static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */ static bool nowayout = WATCHDOG_NOWAYOUT; @@ -81,19 +76,22 @@ static unsigned long next_heartbeat; struct sh_wdt { void __iomem *base; struct device *dev; + struct clk *clk; + spinlock_t lock; struct timer_list timer; - - unsigned long enabled; - char expect_close; }; -static void sh_wdt_start(struct sh_wdt *wdt) +static int sh_wdt_start(struct watchdog_device *wdt_dev) { + struct sh_wdt *wdt = watchdog_get_drvdata(wdt_dev); unsigned long flags; u8 csr; - spin_lock_irqsave(&shwdt_lock, flags); + pm_runtime_get_sync(wdt->dev); + clk_enable(wdt->clk); + + spin_lock_irqsave(&wdt->lock, flags); next_heartbeat = jiffies + (heartbeat * HZ); mod_timer(&wdt->timer, next_ping_period(clock_division_ratio)); @@ -122,15 +120,18 @@ static void sh_wdt_start(struct sh_wdt *wdt) csr &= ~RSTCSR_RSTS; sh_wdt_write_rstcsr(csr); #endif - spin_unlock_irqrestore(&shwdt_lock, flags); + spin_unlock_irqrestore(&wdt->lock, flags); + + return 0; } -static void sh_wdt_stop(struct sh_wdt *wdt) +static int sh_wdt_stop(struct watchdog_device *wdt_dev) { + struct sh_wdt *wdt = watchdog_get_drvdata(wdt_dev); unsigned long flags; u8 csr; - spin_lock_irqsave(&shwdt_lock, flags); + spin_lock_irqsave(&wdt->lock, flags); del_timer(&wdt->timer); @@ -138,28 +139,39 @@ static void sh_wdt_stop(struct sh_wdt *wdt) csr &= ~WTCSR_TME; sh_wdt_write_csr(csr); - spin_unlock_irqrestore(&shwdt_lock, flags); + spin_unlock_irqrestore(&wdt->lock, flags); + + clk_disable(wdt->clk); + pm_runtime_put_sync(wdt->dev); + + return 0; } -static inline void sh_wdt_keepalive(struct sh_wdt *wdt) +static int sh_wdt_keepalive(struct watchdog_device *wdt_dev) { + struct sh_wdt *wdt = watchdog_get_drvdata(wdt_dev); unsigned long flags; - spin_lock_irqsave(&shwdt_lock, flags); + spin_lock_irqsave(&wdt->lock, flags); next_heartbeat = jiffies + (heartbeat * HZ); - spin_unlock_irqrestore(&shwdt_lock, flags); + spin_unlock_irqrestore(&wdt->lock, flags); + + return 0; } -static int sh_wdt_set_heartbeat(int t) +static int sh_wdt_set_heartbeat(struct watchdog_device *wdt_dev, unsigned t) { + struct sh_wdt *wdt = watchdog_get_drvdata(wdt_dev); unsigned long flags; if (unlikely(t < 1 || t > 3600)) /* arbitrary upper limit */ return -EINVAL; - spin_lock_irqsave(&shwdt_lock, flags); + spin_lock_irqsave(&wdt->lock, flags); heartbeat = t; - spin_unlock_irqrestore(&shwdt_lock, flags); + wdt_dev->timeout = t; + spin_unlock_irqrestore(&wdt->lock, flags); + return 0; } @@ -168,7 +180,7 @@ static void sh_wdt_ping(unsigned long data) struct sh_wdt *wdt = (struct sh_wdt *)data; unsigned long flags; - spin_lock_irqsave(&shwdt_lock, flags); + spin_lock_irqsave(&wdt->lock, flags); if (time_before(jiffies, next_heartbeat)) { u8 csr; @@ -182,137 +194,9 @@ static void sh_wdt_ping(unsigned long data) } else dev_warn(wdt->dev, "Heartbeat lost! Will not ping " "the watchdog\n"); - spin_unlock_irqrestore(&shwdt_lock, flags); -} - -static int sh_wdt_open(struct inode *inode, struct file *file) -{ - struct sh_wdt *wdt = platform_get_drvdata(sh_wdt_dev); - - if (test_and_set_bit(0, &wdt->enabled)) - return -EBUSY; - if (nowayout) - __module_get(THIS_MODULE); - - file->private_data = wdt; - - sh_wdt_start(wdt); - - return nonseekable_open(inode, file); -} - -static int sh_wdt_close(struct inode *inode, struct file *file) -{ - struct sh_wdt *wdt = file->private_data; - - if (wdt->expect_close == 42) { - sh_wdt_stop(wdt); - } else { - dev_crit(wdt->dev, "Unexpected close, not " - "stopping watchdog!\n"); - sh_wdt_keepalive(wdt); - } - - clear_bit(0, &wdt->enabled); - wdt->expect_close = 0; - - return 0; -} - -static ssize_t sh_wdt_write(struct file *file, const char *buf, - size_t count, loff_t *ppos) -{ - struct sh_wdt *wdt = file->private_data; - - if (count) { - if (!nowayout) { - size_t i; - - wdt->expect_close = 0; - - for (i = 0; i != count; i++) { - char c; - if (get_user(c, buf + i)) - return -EFAULT; - if (c == 'V') - wdt->expect_close = 42; - } - } - sh_wdt_keepalive(wdt); - } - - return count; + spin_unlock_irqrestore(&wdt->lock, flags); } -static long sh_wdt_ioctl(struct file *file, unsigned int cmd, - unsigned long arg) -{ - struct sh_wdt *wdt = file->private_data; - int new_heartbeat; - int options, retval = -EINVAL; - - switch (cmd) { - case WDIOC_GETSUPPORT: - return copy_to_user((struct watchdog_info *)arg, - &sh_wdt_info, sizeof(sh_wdt_info)) ? -EFAULT : 0; - case WDIOC_GETSTATUS: - case WDIOC_GETBOOTSTATUS: - return put_user(0, (int *)arg); - case WDIOC_SETOPTIONS: - if (get_user(options, (int *)arg)) - return -EFAULT; - - if (options & WDIOS_DISABLECARD) { - sh_wdt_stop(wdt); - retval = 0; - } - - if (options & WDIOS_ENABLECARD) { - sh_wdt_start(wdt); - retval = 0; - } - - return retval; - case WDIOC_KEEPALIVE: - sh_wdt_keepalive(wdt); - return 0; - case WDIOC_SETTIMEOUT: - if (get_user(new_heartbeat, (int *)arg)) - return -EFAULT; - - if (sh_wdt_set_heartbeat(new_heartbeat)) - return -EINVAL; - - sh_wdt_keepalive(wdt); - /* Fall */ - case WDIOC_GETTIMEOUT: - return put_user(heartbeat, (int *)arg); - default: - return -ENOTTY; - } - return 0; -} - -static int sh_wdt_notify_sys(struct notifier_block *this, - unsigned long code, void *unused) -{ - struct sh_wdt *wdt = platform_get_drvdata(sh_wdt_dev); - - if (code == SYS_DOWN || code == SYS_HALT) - sh_wdt_stop(wdt); - - return NOTIFY_DONE; -} - -static const struct file_operations sh_wdt_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .write = sh_wdt_write, - .unlocked_ioctl = sh_wdt_ioctl, - .open = sh_wdt_open, - .release = sh_wdt_close, -}; - static const struct watchdog_info sh_wdt_info = { .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, @@ -320,14 +204,17 @@ static const struct watchdog_info sh_wdt_info = { .identity = "SH WDT", }; -static struct notifier_block sh_wdt_notifier = { - .notifier_call = sh_wdt_notify_sys, +static const struct watchdog_ops sh_wdt_ops = { + .owner = THIS_MODULE, + .start = sh_wdt_start, + .stop = sh_wdt_stop, + .ping = sh_wdt_keepalive, + .set_timeout = sh_wdt_set_heartbeat, }; -static struct miscdevice sh_wdt_miscdev = { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &sh_wdt_fops, +static struct watchdog_device sh_wdt_dev = { + .info = &sh_wdt_info, + .ops = &sh_wdt_ops, }; static int __devinit sh_wdt_probe(struct platform_device *pdev) @@ -347,39 +234,49 @@ static int __devinit sh_wdt_probe(struct platform_device *pdev) if (unlikely(!res)) return -EINVAL; - if (!devm_request_mem_region(&pdev->dev, res->start, - resource_size(res), DRV_NAME)) - return -EBUSY; - wdt = devm_kzalloc(&pdev->dev, sizeof(struct sh_wdt), GFP_KERNEL); - if (unlikely(!wdt)) { - rc = -ENOMEM; - goto out_release; - } + if (unlikely(!wdt)) + return -ENOMEM; wdt->dev = &pdev->dev; - wdt->base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + wdt->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(wdt->clk)) { + /* + * Clock framework support is optional, continue on + * anyways if we don't find a matching clock. + */ + wdt->clk = NULL; + } + + wdt->base = devm_request_and_ioremap(wdt->dev, res); if (unlikely(!wdt->base)) { - rc = -ENXIO; - goto out_err; + rc = -EADDRNOTAVAIL; + goto err; } - rc = register_reboot_notifier(&sh_wdt_notifier); + watchdog_set_nowayout(&sh_wdt_dev, nowayout); + watchdog_set_drvdata(&sh_wdt_dev, wdt); + + spin_lock_init(&wdt->lock); + + rc = sh_wdt_set_heartbeat(&sh_wdt_dev, heartbeat); if (unlikely(rc)) { - dev_err(&pdev->dev, - "Can't register reboot notifier (err=%d)\n", rc); - goto out_unmap; + /* Default timeout if invalid */ + sh_wdt_set_heartbeat(&sh_wdt_dev, WATCHDOG_HEARTBEAT); + + dev_warn(&pdev->dev, + "heartbeat value must be 1<=x<=3600, using %d\n", + sh_wdt_dev.timeout); } - sh_wdt_miscdev.parent = wdt->dev; + dev_info(&pdev->dev, "configured with heartbeat=%d sec (nowayout=%d)\n", + sh_wdt_dev.timeout, nowayout); - rc = misc_register(&sh_wdt_miscdev); + rc = watchdog_register_device(&sh_wdt_dev); if (unlikely(rc)) { - dev_err(&pdev->dev, - "Can't register miscdev on minor=%d (err=%d)\n", - sh_wdt_miscdev.minor, rc); - goto out_unreg; + dev_err(&pdev->dev, "Can't register watchdog (err=%d)\n", rc); + goto err; } init_timer(&wdt->timer); @@ -388,20 +285,15 @@ static int __devinit sh_wdt_probe(struct platform_device *pdev) wdt->timer.expires = next_ping_period(clock_division_ratio); platform_set_drvdata(pdev, wdt); - sh_wdt_dev = pdev; dev_info(&pdev->dev, "initialized.\n"); + pm_runtime_enable(&pdev->dev); + return 0; -out_unreg: - unregister_reboot_notifier(&sh_wdt_notifier); -out_unmap: - devm_iounmap(&pdev->dev, wdt->base); -out_err: - devm_kfree(&pdev->dev, wdt); -out_release: - devm_release_mem_region(&pdev->dev, res->start, resource_size(res)); +err: + clk_put(wdt->clk); return rc; } @@ -409,36 +301,35 @@ out_release: static int __devexit sh_wdt_remove(struct platform_device *pdev) { struct sh_wdt *wdt = platform_get_drvdata(pdev); - struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); platform_set_drvdata(pdev, NULL); - misc_deregister(&sh_wdt_miscdev); + watchdog_unregister_device(&sh_wdt_dev); - sh_wdt_dev = NULL; - - unregister_reboot_notifier(&sh_wdt_notifier); - devm_release_mem_region(&pdev->dev, res->start, resource_size(res)); - devm_iounmap(&pdev->dev, wdt->base); - devm_kfree(&pdev->dev, wdt); + pm_runtime_disable(&pdev->dev); + clk_put(wdt->clk); return 0; } +static void sh_wdt_shutdown(struct platform_device *pdev) +{ + sh_wdt_stop(&sh_wdt_dev); +} + static struct platform_driver sh_wdt_driver = { .driver = { .name = DRV_NAME, .owner = THIS_MODULE, }, - .probe = sh_wdt_probe, - .remove = __devexit_p(sh_wdt_remove), + .probe = sh_wdt_probe, + .remove = __devexit_p(sh_wdt_remove), + .shutdown = sh_wdt_shutdown, }; static int __init sh_wdt_init(void) { - int rc; - if (unlikely(clock_division_ratio < 0x5 || clock_division_ratio > 0x7)) { clock_division_ratio = WTCSR_CKS_4096; @@ -447,17 +338,6 @@ static int __init sh_wdt_init(void) clock_division_ratio); } - rc = sh_wdt_set_heartbeat(heartbeat); - if (unlikely(rc)) { - heartbeat = WATCHDOG_HEARTBEAT; - - pr_info("heartbeat value must be 1<=x<=3600, using %d\n", - heartbeat); - } - - pr_info("configured with heartbeat=%d sec (nowayout=%d)\n", - heartbeat, nowayout); - return platform_driver_register(&sh_wdt_driver); } diff --git a/drivers/watchdog/sp5100_tco.c b/drivers/watchdog/sp5100_tco.c index 59108e48ada3..ae5e82cb83fa 100644 --- a/drivers/watchdog/sp5100_tco.c +++ b/drivers/watchdog/sp5100_tco.c @@ -313,7 +313,7 @@ static unsigned char __devinit sp5100_tco_setupdevice(void) tcobase_phys = val; tcobase = ioremap(val, SP5100_WDT_MEM_MAP_SIZE); - if (tcobase == 0) { + if (!tcobase) { pr_err("failed to get tcobase address\n"); goto unreg_mem_region; } diff --git a/drivers/watchdog/via_wdt.c b/drivers/watchdog/via_wdt.c index 465e08273c97..5603e31afdab 100644 --- a/drivers/watchdog/via_wdt.c +++ b/drivers/watchdog/via_wdt.c @@ -202,6 +202,9 @@ static int __devinit wdt_probe(struct pci_dev *pdev, goto err_out_release; } + if (timeout < 1 || timeout > WDT_TIMEOUT_MAX) + timeout = WDT_TIMEOUT; + wdt_dev.timeout = timeout; watchdog_set_nowayout(&wdt_dev, nowayout); if (readl(wdt_mem) & VIA_WDT_FIRED) @@ -250,20 +253,7 @@ static struct pci_driver wdt_driver = { .remove = __devexit_p(wdt_remove), }; -static int __init wdt_init(void) -{ - if (timeout < 1 || timeout > WDT_TIMEOUT_MAX) - timeout = WDT_TIMEOUT; - return pci_register_driver(&wdt_driver); -} - -static void __exit wdt_exit(void) -{ - pci_unregister_driver(&wdt_driver); -} - -module_init(wdt_init); -module_exit(wdt_exit); +module_pci_driver(wdt_driver); MODULE_AUTHOR("Marc Vertes"); MODULE_DESCRIPTION("Driver for watchdog timer on VIA chipset"); diff --git a/drivers/watchdog/wdt_pci.c b/drivers/watchdog/wdt_pci.c index 1c888c7d4cce..e32654efdbb6 100644 --- a/drivers/watchdog/wdt_pci.c +++ b/drivers/watchdog/wdt_pci.c @@ -739,39 +739,7 @@ static struct pci_driver wdtpci_driver = { .remove = __devexit_p(wdtpci_remove_one), }; - -/** - * wdtpci_cleanup: - * - * Unload the watchdog. You cannot do this with any file handles open. - * If your watchdog is set to continue ticking on close and you unload - * it, well it keeps ticking. We won't get the interrupt but the board - * will not touch PC memory so all is fine. You just have to load a new - * module in xx seconds or reboot. - */ - -static void __exit wdtpci_cleanup(void) -{ - pci_unregister_driver(&wdtpci_driver); -} - - -/** - * wdtpci_init: - * - * Set up the WDT watchdog board. All we have to do is grab the - * resources we require and bitch if anyone beat us to them. - * The open() function will actually kick the board off. - */ - -static int __init wdtpci_init(void) -{ - return pci_register_driver(&wdtpci_driver); -} - - -module_init(wdtpci_init); -module_exit(wdtpci_cleanup); +module_pci_driver(wdtpci_driver); MODULE_AUTHOR("JP Nollmann, Alan Cox"); MODULE_DESCRIPTION("Driver for the ICS PCI-WDT500/501 watchdog cards"); diff --git a/drivers/watchdog/wm831x_wdt.c b/drivers/watchdog/wm831x_wdt.c index b1815c5ed7a7..87d66d236c3e 100644 --- a/drivers/watchdog/wm831x_wdt.c +++ b/drivers/watchdog/wm831x_wdt.c @@ -247,8 +247,9 @@ static int __devinit wm831x_wdt_probe(struct platform_device *pdev) reg |= pdata->software << WM831X_WDOG_RST_SRC_SHIFT; if (pdata->update_gpio) { - ret = gpio_request(pdata->update_gpio, - "Watchdog update"); + ret = gpio_request_one(pdata->update_gpio, + GPIOF_DIR_OUT | GPIOF_INIT_LOW, + "Watchdog update"); if (ret < 0) { dev_err(wm831x->dev, "Failed to request update GPIO: %d\n", @@ -256,14 +257,6 @@ static int __devinit wm831x_wdt_probe(struct platform_device *pdev) goto err; } - ret = gpio_direction_output(pdata->update_gpio, 0); - if (ret != 0) { - dev_err(wm831x->dev, - "gpio_direction_output returned: %d\n", - ret); - goto err_gpio; - } - driver_data->update_gpio = pdata->update_gpio; /* Make sure the watchdog takes hardware updates */ |