From 3790c8cdb99f23824b23cb16df608281b335ee91 Mon Sep 17 00:00:00 2001 From: Patrick McHardy Date: Fri, 12 Jun 2009 03:00:35 +0000 Subject: net: fix network driver ndo_start_xmit() return values (part 1) Fix up drivers that return an errno value to qdisc_restart(), causing qdisc_restart() to print a warning and requeue/retransmit the skb. - xpnet: memory allocation error, intention is to drop - ethoc: oversized packet, packet must be dropped - ibmlana: skb freed: use after free - rrunner: skb freed: use after free Signed-off-by: Patrick McHardy Signed-off-by: David S. Miller --- drivers/misc/sgi-xp/xpnet.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/misc') diff --git a/drivers/misc/sgi-xp/xpnet.c b/drivers/misc/sgi-xp/xpnet.c index 6faefcffcb5..8d1c60a3f0d 100644 --- a/drivers/misc/sgi-xp/xpnet.c +++ b/drivers/misc/sgi-xp/xpnet.c @@ -450,7 +450,8 @@ xpnet_dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) "packet\n", sizeof(struct xpnet_pending_msg)); dev->stats.tx_errors++; - return -ENOMEM; + dev_kfree_skb(skb); + return NETDEV_TX_OK; } /* get the beginning of the first cacheline and end of last */ -- cgit v1.2.3-70-g09d2 From 5f5bac8272be791b67c7b7b411e7c8c5847e598a Mon Sep 17 00:00:00 2001 From: Michał Mirosław Date: Fri, 22 May 2009 20:33:59 +0200 Subject: mmc: Driver for CB710/720 memory card reader (MMC part) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The code is divided in two parts. There is a virtual 'bus' driver that handles PCI device and registers three new devices one per card reader type. The other driver handles SD/MMC part of the reader. Signed-off-by: Michał Mirosław Signed-off-by: Pierre Ossman --- MAINTAINERS | 9 + drivers/misc/Kconfig | 1 + drivers/misc/Makefile | 1 + drivers/misc/cb710/Kconfig | 21 ++ drivers/misc/cb710/Makefile | 4 + drivers/misc/cb710/core.c | 357 +++++++++++++++++++ drivers/misc/cb710/debug.c | 119 +++++++ drivers/misc/cb710/sgbuf2.c | 150 ++++++++ drivers/mmc/host/Kconfig | 13 + drivers/mmc/host/Makefile | 1 + drivers/mmc/host/cb710-mmc.c | 804 +++++++++++++++++++++++++++++++++++++++++++ drivers/mmc/host/cb710-mmc.h | 104 ++++++ include/linux/cb710.h | 238 +++++++++++++ include/linux/pci_ids.h | 1 + 14 files changed, 1823 insertions(+) create mode 100644 drivers/misc/cb710/Kconfig create mode 100644 drivers/misc/cb710/Makefile create mode 100644 drivers/misc/cb710/core.c create mode 100644 drivers/misc/cb710/debug.c create mode 100644 drivers/misc/cb710/sgbuf2.c create mode 100644 drivers/mmc/host/cb710-mmc.c create mode 100644 drivers/mmc/host/cb710-mmc.h create mode 100644 include/linux/cb710.h (limited to 'drivers/misc') diff --git a/MAINTAINERS b/MAINTAINERS index 90f81283b72..b5cebddd284 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2105,6 +2105,15 @@ W: http://sourceforge.net/projects/lpfcxxxx S: Supported F: drivers/scsi/lpfc/ +ENE CB710 FLASH CARD READER DRIVER +P: Michał Mirosław +M: mirq-linux@rere.qmqm.pl +L: linux-kernel@vger.kernel.org +S: Maintained +F: drivers/misc/cb710/ +F: drivers/mmc/host/cb710-mmc.* +F: include/linux/cb710.h + EPSON 1355 FRAMEBUFFER DRIVER P: Christopher Hoover M: ch@murgatroid.com diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 6d1ac180f6e..68ab39d7cb3 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -235,5 +235,6 @@ config ISL29003 source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" +source "drivers/misc/cb710/Kconfig" endif # MISC_DEVICES diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 7871f05dcb9..36f733cd60e 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -21,3 +21,4 @@ obj-$(CONFIG_HP_ILO) += hpilo.o obj-$(CONFIG_ISL29003) += isl29003.o obj-$(CONFIG_C2PORT) += c2port/ obj-y += eeprom/ +obj-y += cb710/ diff --git a/drivers/misc/cb710/Kconfig b/drivers/misc/cb710/Kconfig new file mode 100644 index 00000000000..0b55079774c --- /dev/null +++ b/drivers/misc/cb710/Kconfig @@ -0,0 +1,21 @@ +config CB710_CORE + tristate "ENE CB710/720 Flash memory card reader support" + depends on PCI + help + This option enables support for PCI ENE CB710/720 Flash memory card + reader found in some laptops (ie. some versions of HP Compaq nx9500). + + You will also have to select some flash card format drivers (MMC/SD, + MemoryStick). + + This driver can also be built as a module. If so, the module + will be called cb710. + +config CB710_DEBUG + bool "Enable driver debugging" + depends on CB710_CORE != n + default n + help + This is an option for use by developers; most people should + say N here. This adds a lot of debugging output to dmesg. + diff --git a/drivers/misc/cb710/Makefile b/drivers/misc/cb710/Makefile new file mode 100644 index 00000000000..62d51acfeca --- /dev/null +++ b/drivers/misc/cb710/Makefile @@ -0,0 +1,4 @@ +obj-$(CONFIG_CB710_CORE) += cb710.o + +cb710-y := core.o sgbuf2.o +cb710-$(CONFIG_CB710_DEBUG) += debug.o diff --git a/drivers/misc/cb710/core.c b/drivers/misc/cb710/core.c new file mode 100644 index 00000000000..b14eab0f2ba --- /dev/null +++ b/drivers/misc/cb710/core.c @@ -0,0 +1,357 @@ +/* + * cb710/core.c + * + * Copyright by Michał Mirosław, 2008-2009 + * + * 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 +#include +#include +#include +#include +#include +#include + +static DEFINE_IDA(cb710_ida); +static DEFINE_SPINLOCK(cb710_ida_lock); + +void cb710_pci_update_config_reg(struct pci_dev *pdev, + int reg, uint32_t mask, uint32_t xor) +{ + u32 rval; + + pci_read_config_dword(pdev, reg, &rval); + rval = (rval & mask) ^ xor; + pci_write_config_dword(pdev, reg, rval); +} +EXPORT_SYMBOL_GPL(cb710_pci_update_config_reg); + +/* Some magic writes based on Windows driver init code */ +static int __devinit cb710_pci_configure(struct pci_dev *pdev) +{ + unsigned int devfn = PCI_DEVFN(PCI_SLOT(pdev->devfn), 0); + struct pci_dev *pdev0 = pci_get_slot(pdev->bus, devfn); + u32 val; + + cb710_pci_update_config_reg(pdev, 0x48, + ~0x000000FF, 0x0000003F); + + pci_read_config_dword(pdev, 0x48, &val); + if (val & 0x80000000) + return 0; + + if (!pdev0) + return -ENODEV; + + if (pdev0->vendor == PCI_VENDOR_ID_ENE + && pdev0->device == PCI_DEVICE_ID_ENE_720) { + cb710_pci_update_config_reg(pdev0, 0x8C, + ~0x00F00000, 0x00100000); + cb710_pci_update_config_reg(pdev0, 0xB0, + ~0x08000000, 0x08000000); + } + + cb710_pci_update_config_reg(pdev0, 0x8C, + ~0x00000F00, 0x00000200); + cb710_pci_update_config_reg(pdev0, 0x90, + ~0x00060000, 0x00040000); + + pci_dev_put(pdev0); + + return 0; +} + +static irqreturn_t cb710_irq_handler(int irq, void *data) +{ + struct cb710_chip *chip = data; + struct cb710_slot *slot = &chip->slot[0]; + irqreturn_t handled = IRQ_NONE; + unsigned nr; + + spin_lock(&chip->irq_lock); /* incl. smp_rmb() */ + + for (nr = chip->slots; nr; ++slot, --nr) { + cb710_irq_handler_t handler_func = slot->irq_handler; + if (handler_func && handler_func(slot)) + handled = IRQ_HANDLED; + } + + spin_unlock(&chip->irq_lock); + + return handled; +} + +static void cb710_release_slot(struct device *dev) +{ +#ifdef CONFIG_CB710_DEBUG_ASSUMPTIONS + struct cb710_slot *slot = cb710_pdev_to_slot(to_platform_device(dev)); + struct cb710_chip *chip = cb710_slot_to_chip(slot); + + /* slot struct can be freed now */ + atomic_dec(&chip->slot_refs_count); +#endif +} + +static int __devinit cb710_register_slot(struct cb710_chip *chip, + unsigned slot_mask, unsigned io_offset, const char *name) +{ + int nr = chip->slots; + struct cb710_slot *slot = &chip->slot[nr]; + int err; + + dev_dbg(cb710_chip_dev(chip), + "register: %s.%d; slot %d; mask %d; IO offset: 0x%02X\n", + name, chip->platform_id, nr, slot_mask, io_offset); + + /* slot->irq_handler == NULL here; this needs to be + * seen before platform_device_register() */ + ++chip->slots; + smp_wmb(); + + slot->iobase = chip->iobase + io_offset; + slot->pdev.name = name; + slot->pdev.id = chip->platform_id; + slot->pdev.dev.parent = &chip->pdev->dev; + slot->pdev.dev.release = cb710_release_slot; + + err = platform_device_register(&slot->pdev); + +#ifdef CONFIG_CB710_DEBUG_ASSUMPTIONS + atomic_inc(&chip->slot_refs_count); +#endif + + if (err) { + /* device_initialize() called from platform_device_register() + * wants this on error path */ + platform_device_put(&slot->pdev); + + /* slot->irq_handler == NULL here anyway, so no lock needed */ + --chip->slots; + return err; + } + + chip->slot_mask |= slot_mask; + + return 0; +} + +static void cb710_unregister_slot(struct cb710_chip *chip, + unsigned slot_mask) +{ + int nr = chip->slots - 1; + + if (!(chip->slot_mask & slot_mask)) + return; + + platform_device_unregister(&chip->slot[nr].pdev); + + /* complementary to spin_unlock() in cb710_set_irq_handler() */ + smp_rmb(); + BUG_ON(chip->slot[nr].irq_handler != NULL); + + /* slot->irq_handler == NULL here, so no lock needed */ + --chip->slots; + chip->slot_mask &= ~slot_mask; +} + +void cb710_set_irq_handler(struct cb710_slot *slot, + cb710_irq_handler_t handler) +{ + struct cb710_chip *chip = cb710_slot_to_chip(slot); + unsigned long flags; + + spin_lock_irqsave(&chip->irq_lock, flags); + slot->irq_handler = handler; + spin_unlock_irqrestore(&chip->irq_lock, flags); +} +EXPORT_SYMBOL_GPL(cb710_set_irq_handler); + +#ifdef CONFIG_PM + +static int cb710_suspend(struct pci_dev *pdev, pm_message_t state) +{ + struct cb710_chip *chip = pci_get_drvdata(pdev); + + free_irq(pdev->irq, chip); + pci_save_state(pdev); + pci_disable_device(pdev); + if (state.event & PM_EVENT_SLEEP) + pci_set_power_state(pdev, PCI_D3cold); + return 0; +} + +static int cb710_resume(struct pci_dev *pdev) +{ + struct cb710_chip *chip = pci_get_drvdata(pdev); + int err; + + pci_set_power_state(pdev, PCI_D0); + pci_restore_state(pdev); + err = pcim_enable_device(pdev); + if (err) + return err; + + return devm_request_irq(&pdev->dev, pdev->irq, + cb710_irq_handler, IRQF_SHARED, KBUILD_MODNAME, chip); +} + +#endif /* CONFIG_PM */ + +static int __devinit cb710_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct cb710_chip *chip; + unsigned long flags; + u32 val; + int err; + int n = 0; + + err = cb710_pci_configure(pdev); + if (err) + return err; + + /* this is actually magic... */ + pci_read_config_dword(pdev, 0x48, &val); + if (!(val & 0x80000000)) { + pci_write_config_dword(pdev, 0x48, val|0x71000000); + pci_read_config_dword(pdev, 0x48, &val); + } + + dev_dbg(&pdev->dev, "PCI config[0x48] = 0x%08X\n", val); + if (!(val & 0x70000000)) + return -ENODEV; + val = (val >> 28) & 7; + if (val & CB710_SLOT_MMC) + ++n; + if (val & CB710_SLOT_MS) + ++n; + if (val & CB710_SLOT_SM) + ++n; + + chip = devm_kzalloc(&pdev->dev, + sizeof(*chip) + n * sizeof(*chip->slot), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + err = pcim_enable_device(pdev); + if (err) + return err; + + err = pcim_iomap_regions(pdev, 0x0001, KBUILD_MODNAME); + if (err) + return err; + + chip->pdev = pdev; + chip->iobase = pcim_iomap_table(pdev)[0]; + + pci_set_drvdata(pdev, chip); + + err = devm_request_irq(&pdev->dev, pdev->irq, + cb710_irq_handler, IRQF_SHARED, KBUILD_MODNAME, chip); + if (err) + return err; + + do { + if (!ida_pre_get(&cb710_ida, GFP_KERNEL)) + return -ENOMEM; + + spin_lock_irqsave(&cb710_ida_lock, flags); + err = ida_get_new(&cb710_ida, &chip->platform_id); + spin_unlock_irqrestore(&cb710_ida_lock, flags); + + if (err && err != -EAGAIN) + return err; + } while (err); + + + dev_info(&pdev->dev, "id %d, IO 0x%p, IRQ %d\n", + chip->platform_id, chip->iobase, pdev->irq); + + if (val & CB710_SLOT_MMC) { /* MMC/SD slot */ + err = cb710_register_slot(chip, + CB710_SLOT_MMC, 0x00, "cb710-mmc"); + if (err) + return err; + } + + if (val & CB710_SLOT_MS) { /* MemoryStick slot */ + err = cb710_register_slot(chip, + CB710_SLOT_MS, 0x40, "cb710-ms"); + if (err) + goto unreg_mmc; + } + + if (val & CB710_SLOT_SM) { /* SmartMedia slot */ + err = cb710_register_slot(chip, + CB710_SLOT_SM, 0x60, "cb710-sm"); + if (err) + goto unreg_ms; + } + + return 0; +unreg_ms: + cb710_unregister_slot(chip, CB710_SLOT_MS); +unreg_mmc: + cb710_unregister_slot(chip, CB710_SLOT_MMC); + +#ifdef CONFIG_CB710_DEBUG_ASSUMPTIONS + BUG_ON(atomic_read(&chip->slot_refs_count) != 0); +#endif + return err; +} + +static void __devexit cb710_remove_one(struct pci_dev *pdev) +{ + struct cb710_chip *chip = pci_get_drvdata(pdev); + unsigned long flags; + + cb710_unregister_slot(chip, CB710_SLOT_SM); + cb710_unregister_slot(chip, CB710_SLOT_MS); + cb710_unregister_slot(chip, CB710_SLOT_MMC); +#ifdef CONFIG_CB710_DEBUG_ASSUMPTIONS + BUG_ON(atomic_read(&chip->slot_refs_count) != 0); +#endif + + spin_lock_irqsave(&cb710_ida_lock, flags); + ida_remove(&cb710_ida, chip->platform_id); + spin_unlock_irqrestore(&cb710_ida_lock, flags); +} + +static const struct pci_device_id cb710_pci_tbl[] = { + { PCI_VENDOR_ID_ENE, PCI_DEVICE_ID_ENE_CB710_FLASH, + PCI_ANY_ID, PCI_ANY_ID, }, + { 0, } +}; + +static struct pci_driver cb710_driver = { + .name = KBUILD_MODNAME, + .id_table = cb710_pci_tbl, + .probe = cb710_probe, + .remove = __devexit_p(cb710_remove_one), +#ifdef CONFIG_PM + .suspend = cb710_suspend, + .resume = cb710_resume, +#endif +}; + +static int __init cb710_init_module(void) +{ + return pci_register_driver(&cb710_driver); +} + +static void __exit cb710_cleanup_module(void) +{ + pci_unregister_driver(&cb710_driver); + ida_destroy(&cb710_ida); +} + +module_init(cb710_init_module); +module_exit(cb710_cleanup_module); + +MODULE_AUTHOR("Michał Mirosław "); +MODULE_DESCRIPTION("ENE CB710 memory card reader driver"); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(pci, cb710_pci_tbl); diff --git a/drivers/misc/cb710/debug.c b/drivers/misc/cb710/debug.c new file mode 100644 index 00000000000..9da11bc620a --- /dev/null +++ b/drivers/misc/cb710/debug.c @@ -0,0 +1,119 @@ +/* + * cb710/debug.c + * + * Copyright by Michał Mirosław, 2008-2009 + * + * 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 +#include +#include +#include + +#define CB710_REG_COUNT 0x80 + +static const u16 allow[CB710_REG_COUNT/16] = { + 0xFFF0, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFF0, 0xFFFF, 0xFFFF, 0xFFFF, +}; +static const char *const prefix[ARRAY_SIZE(allow)] = { + "MMC", "MMC", "MMC", "MMC", + "MS?", "MS?", "SM?", "SM?" +}; + +static inline int allow_reg_read(unsigned block, unsigned offset, unsigned bits) +{ + unsigned mask = (1 << bits/8) - 1; + offset *= bits/8; + return ((allow[block] >> offset) & mask) == mask; +} + +#define CB710_READ_REGS_TEMPLATE(t) \ +static void cb710_read_regs_##t(void __iomem *iobase, \ + u##t *reg, unsigned select) \ +{ \ + unsigned i, j; \ + \ + for (i = 0; i < ARRAY_SIZE(allow); ++i, reg += 16/(t/8)) { \ + if (!select & (1 << i)) \ + continue; \ + \ + for (j = 0; j < 0x10/(t/8); ++j) { \ + if (!allow_reg_read(i, j, t)) \ + continue; \ + reg[j] = ioread##t(iobase \ + + (i << 4) + (j * (t/8))); \ + } \ + } \ +} + +static const char cb710_regf_8[] = "%02X"; +static const char cb710_regf_16[] = "%04X"; +static const char cb710_regf_32[] = "%08X"; +static const char cb710_xes[] = "xxxxxxxx"; + +#define CB710_DUMP_REGS_TEMPLATE(t) \ +static void cb710_dump_regs_##t(struct device *dev, \ + const u##t *reg, unsigned select) \ +{ \ + const char *const xp = &cb710_xes[8 - t/4]; \ + const char *const format = cb710_regf_##t; \ + \ + char msg[100], *p; \ + unsigned i, j; \ + \ + for (i = 0; i < ARRAY_SIZE(allow); ++i, reg += 16/(t/8)) { \ + if (!(select & (1 << i))) \ + continue; \ + p = msg; \ + for (j = 0; j < 0x10/(t/8); ++j) { \ + *p++ = ' '; \ + if (j == 8/(t/8)) \ + *p++ = ' '; \ + if (allow_reg_read(i, j, t)) \ + p += sprintf(p, format, reg[j]); \ + else \ + p += sprintf(p, "%s", xp); \ + } \ + dev_dbg(dev, "%s 0x%02X %s\n", prefix[i], i << 4, msg); \ + } \ +} + +#define CB710_READ_AND_DUMP_REGS_TEMPLATE(t) \ +static void cb710_read_and_dump_regs_##t(struct cb710_chip *chip, \ + unsigned select) \ +{ \ + u##t regs[CB710_REG_COUNT/sizeof(u##t)]; \ + \ + memset(®s, 0, sizeof(regs)); \ + cb710_read_regs_##t(chip->iobase, regs, select); \ + cb710_dump_regs_##t(cb710_chip_dev(chip), regs, select); \ +} + +#define CB710_REG_ACCESS_TEMPLATES(t) \ + CB710_READ_REGS_TEMPLATE(t) \ + CB710_DUMP_REGS_TEMPLATE(t) \ + CB710_READ_AND_DUMP_REGS_TEMPLATE(t) + +CB710_REG_ACCESS_TEMPLATES(8) +CB710_REG_ACCESS_TEMPLATES(16) +CB710_REG_ACCESS_TEMPLATES(32) + +void cb710_dump_regs(struct cb710_chip *chip, unsigned select) +{ + if (!(select & CB710_DUMP_REGS_MASK)) + select = CB710_DUMP_REGS_ALL; + if (!(select & CB710_DUMP_ACCESS_MASK)) + select |= CB710_DUMP_ACCESS_8; + + if (select & CB710_DUMP_ACCESS_32) + cb710_read_and_dump_regs_32(chip, select); + if (select & CB710_DUMP_ACCESS_16) + cb710_read_and_dump_regs_16(chip, select); + if (select & CB710_DUMP_ACCESS_8) + cb710_read_and_dump_regs_8(chip, select); +} +EXPORT_SYMBOL_GPL(cb710_dump_regs); + diff --git a/drivers/misc/cb710/sgbuf2.c b/drivers/misc/cb710/sgbuf2.c new file mode 100644 index 00000000000..d38a7acdb6e --- /dev/null +++ b/drivers/misc/cb710/sgbuf2.c @@ -0,0 +1,150 @@ +/* + * cb710/sgbuf2.c + * + * Copyright by Michał Mirosław, 2008-2009 + * + * 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 +#include +#include + +static bool sg_dwiter_next(struct sg_mapping_iter *miter) +{ + if (sg_miter_next(miter)) { + miter->consumed = 0; + return true; + } else + return false; +} + +static bool sg_dwiter_is_at_end(struct sg_mapping_iter *miter) +{ + return miter->length == miter->consumed && !sg_dwiter_next(miter); +} + +static uint32_t sg_dwiter_read_buffer(struct sg_mapping_iter *miter) +{ + size_t len, left = 4; + uint32_t data; + void *addr = &data; + + do { + len = min(miter->length - miter->consumed, left); + memcpy(addr, miter->addr + miter->consumed, len); + miter->consumed += len; + left -= len; + if (!left) + return data; + addr += len; + } while (sg_dwiter_next(miter)); + + memset(addr, 0, left); + return data; +} + +static inline bool needs_unaligned_copy(const void *ptr) +{ +#ifdef HAVE_EFFICIENT_UNALIGNED_ACCESS + return false; +#else + return ((ptr - NULL) & 3) != 0; +#endif +} + +static bool sg_dwiter_get_next_block(struct sg_mapping_iter *miter, uint32_t **ptr) +{ + size_t len; + + if (sg_dwiter_is_at_end(miter)) + return true; + + len = miter->length - miter->consumed; + + if (likely(len >= 4 && !needs_unaligned_copy( + miter->addr + miter->consumed))) { + *ptr = miter->addr + miter->consumed; + miter->consumed += 4; + return true; + } + + return false; +} + +/** + * cb710_sg_dwiter_read_next_block() - get next 32-bit word from sg buffer + * @miter: sg mapping iterator used for reading + * + * Description: + * Returns 32-bit word starting at byte pointed to by @miter@ + * handling any alignment issues. Bytes past the buffer's end + * are not accessed (read) but are returned as zeroes. @miter@ + * is advanced by 4 bytes or to the end of buffer whichever is + * closer. + * + * Context: + * Same requirements as in sg_miter_next(). + * + * Returns: + * 32-bit word just read. + */ +uint32_t cb710_sg_dwiter_read_next_block(struct sg_mapping_iter *miter) +{ + uint32_t *ptr = NULL; + + if (likely(sg_dwiter_get_next_block(miter, &ptr))) + return ptr ? *ptr : 0; + + return sg_dwiter_read_buffer(miter); +} +EXPORT_SYMBOL_GPL(cb710_sg_dwiter_read_next_block); + +static void sg_dwiter_write_slow(struct sg_mapping_iter *miter, uint32_t data) +{ + size_t len, left = 4; + void *addr = &data; + + do { + len = min(miter->length - miter->consumed, left); + memcpy(miter->addr, addr, len); + miter->consumed += len; + left -= len; + if (!left) + return; + addr += len; + flush_kernel_dcache_page(miter->page); + } while (sg_dwiter_next(miter)); +} + +/** + * cb710_sg_dwiter_write_next_block() - write next 32-bit word to sg buffer + * @miter: sg mapping iterator used for writing + * + * Description: + * Writes 32-bit word starting at byte pointed to by @miter@ + * handling any alignment issues. Bytes which would be written + * past the buffer's end are silently discarded. @miter@ is + * advanced by 4 bytes or to the end of buffer whichever is closer. + * + * Context: + * Same requirements as in sg_miter_next(). + */ +void cb710_sg_dwiter_write_next_block(struct sg_mapping_iter *miter, uint32_t data) +{ + uint32_t *ptr = NULL; + + if (likely(sg_dwiter_get_next_block(miter, &ptr))) { + if (ptr) + *ptr = data; + else + return; + } else + sg_dwiter_write_slow(miter, data); + + if (miter->length == miter->consumed) + flush_kernel_dcache_page(miter->page); +} +EXPORT_SYMBOL_GPL(cb710_sg_dwiter_write_next_block); + diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index b4cf691f3f6..75337e442da 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -241,3 +241,16 @@ config MMC_TMIO help This provides support for the SD/MMC cell found in TC6393XB, T7L66XB and also ipaq ASIC3 + +config MMC_CB710 + tristate "ENE CB710 MMC/SD Interface support" + depends on PCI + select CB710_CORE + help + This option enables support for MMC/SD part of ENE CB710/720 Flash + memory card reader found in some laptops (ie. some versions of + HP Compaq nx9500). + + This driver can also be built as a module. If so, the module + will be called cb710-mmc. + diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index 970a997620e..ce9c8b6f0f4 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -29,4 +29,5 @@ endif obj-$(CONFIG_MMC_S3C) += s3cmci.o obj-$(CONFIG_MMC_SDRICOH_CS) += sdricoh_cs.o obj-$(CONFIG_MMC_TMIO) += tmio_mmc.o +obj-$(CONFIG_MMC_CB710) += cb710-mmc.o diff --git a/drivers/mmc/host/cb710-mmc.c b/drivers/mmc/host/cb710-mmc.c new file mode 100644 index 00000000000..bc048aae720 --- /dev/null +++ b/drivers/mmc/host/cb710-mmc.c @@ -0,0 +1,804 @@ +/* + * cb710/mmc.c + * + * Copyright by Michał Mirosław, 2008-2009 + * + * 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 +#include +#include +#include +#include +#include "cb710-mmc.h" + +static const u8 cb710_clock_divider_log2[8] = { +/* 1, 2, 4, 8, 16, 32, 128, 512 */ + 0, 1, 2, 3, 4, 5, 7, 9 +}; +#define CB710_MAX_DIVIDER_IDX \ + (ARRAY_SIZE(cb710_clock_divider_log2) - 1) + +static const u8 cb710_src_freq_mhz[16] = { + 33, 10, 20, 25, 30, 35, 40, 45, + 50, 55, 60, 65, 70, 75, 80, 85 +}; + +static void cb710_mmc_set_clock(struct mmc_host *mmc, int hz) +{ + struct cb710_slot *slot = cb710_mmc_to_slot(mmc); + struct pci_dev *pdev = cb710_slot_to_chip(slot)->pdev; + u32 src_freq_idx; + u32 divider_idx; + int src_hz; + + /* this is magic, unverifiable for me, unless I get + * MMC card with cables connected to bus signals */ + pci_read_config_dword(pdev, 0x48, &src_freq_idx); + src_freq_idx = (src_freq_idx >> 16) & 0xF; + src_hz = cb710_src_freq_mhz[src_freq_idx] * 1000000; + + for (divider_idx = 0; divider_idx < CB710_MAX_DIVIDER_IDX; ++divider_idx) { + if (hz >= src_hz >> cb710_clock_divider_log2[divider_idx]) + break; + } + + if (src_freq_idx) + divider_idx |= 0x8; + + cb710_pci_update_config_reg(pdev, 0x40, ~0xF0000000, divider_idx << 28); + + dev_dbg(cb710_slot_dev(slot), + "clock set to %d Hz, wanted %d Hz; flag = %d\n", + src_hz >> cb710_clock_divider_log2[divider_idx & 7], + hz, (divider_idx & 8) != 0); +} + +static void __cb710_mmc_enable_irq(struct cb710_slot *slot, + unsigned short enable, unsigned short mask) +{ + /* clear global IE + * - it gets set later if any interrupt sources are enabled */ + mask |= CB710_MMC_IE_IRQ_ENABLE; + + /* look like interrupt is fired whenever + * WORD[0x0C] & WORD[0x10] != 0; + * -> bit 15 port 0x0C seems to be global interrupt enable + */ + + enable = (cb710_read_port_16(slot, CB710_MMC_IRQ_ENABLE_PORT) + & ~mask) | enable; + + if (enable) + enable |= CB710_MMC_IE_IRQ_ENABLE; + + cb710_write_port_16(slot, CB710_MMC_IRQ_ENABLE_PORT, enable); +} + +static void cb710_mmc_enable_irq(struct cb710_slot *slot, + unsigned short enable, unsigned short mask) +{ + struct cb710_mmc_reader *reader = mmc_priv(cb710_slot_to_mmc(slot)); + unsigned long flags; + + spin_lock_irqsave(&reader->irq_lock, flags); + /* this is the only thing irq_lock protects */ + __cb710_mmc_enable_irq(slot, enable, mask); + spin_unlock_irqrestore(&reader->irq_lock, flags); +} + +static void cb710_mmc_reset_events(struct cb710_slot *slot) +{ + cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT, 0xFF); + cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT, 0xFF); + cb710_write_port_8(slot, CB710_MMC_STATUS2_PORT, 0xFF); +} + +static int cb710_mmc_is_card_inserted(struct cb710_slot *slot) +{ + return cb710_read_port_8(slot, CB710_MMC_STATUS3_PORT) + & CB710_MMC_S3_CARD_DETECTED; +} + +static void cb710_mmc_enable_4bit_data(struct cb710_slot *slot, int enable) +{ + dev_dbg(cb710_slot_dev(slot), "configuring %d-data-line%s mode\n", + enable ? 4 : 1, enable ? "s" : ""); + if (enable) + cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, + CB710_MMC_C1_4BIT_DATA_BUS, 0); + else + cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, + 0, CB710_MMC_C1_4BIT_DATA_BUS); +} + +static int cb710_check_event(struct cb710_slot *slot, u8 what) +{ + u16 status; + + status = cb710_read_port_16(slot, CB710_MMC_STATUS_PORT); + + if (status & CB710_MMC_S0_FIFO_UNDERFLOW) { + /* it is just a guess, so log it */ + dev_dbg(cb710_slot_dev(slot), + "CHECK : ignoring bit 6 in status %04X\n", status); + cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT, + CB710_MMC_S0_FIFO_UNDERFLOW); + status &= ~CB710_MMC_S0_FIFO_UNDERFLOW; + } + + if (status & CB710_MMC_STATUS_ERROR_EVENTS) { + dev_dbg(cb710_slot_dev(slot), + "CHECK : returning EIO on status %04X\n", status); + cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT, status & 0xFF); + cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT, + CB710_MMC_S1_RESET); + return -EIO; + } + + /* 'what' is a bit in MMC_STATUS1 */ + if ((status >> 8) & what) { + cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT, what); + return 1; + } + + return 0; +} + +static int cb710_wait_for_event(struct cb710_slot *slot, u8 what) +{ + int err = 0; + unsigned limit = 2000000; /* FIXME: real timeout */ + +#ifdef CONFIG_CB710_DEBUG + u32 e, x; + e = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT); +#endif + + while (!(err = cb710_check_event(slot, what))) { + if (!--limit) { + cb710_dump_regs(cb710_slot_to_chip(slot), + CB710_DUMP_REGS_MMC); + err = -ETIMEDOUT; + break; + } + udelay(1); + } + +#ifdef CONFIG_CB710_DEBUG + x = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT); + + limit = 2000000 - limit; + if (limit > 100) + dev_dbg(cb710_slot_dev(slot), + "WAIT10: waited %d loops, what %d, entry val %08X, exit val %08X\n", + limit, what, e, x); +#endif + return err < 0 ? err : 0; +} + + +static int cb710_wait_while_busy(struct cb710_slot *slot, uint8_t mask) +{ + unsigned limit = 500000; /* FIXME: real timeout */ + int err = 0; + +#ifdef CONFIG_CB710_DEBUG + u32 e, x; + e = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT); +#endif + + while (cb710_read_port_8(slot, CB710_MMC_STATUS2_PORT) & mask) { + if (!--limit) { + cb710_dump_regs(cb710_slot_to_chip(slot), + CB710_DUMP_REGS_MMC); + err = -ETIMEDOUT; + break; + } + udelay(1); + } + +#ifdef CONFIG_CB710_DEBUG + x = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT); + + limit = 500000 - limit; + if (limit > 100) + dev_dbg(cb710_slot_dev(slot), + "WAIT12: waited %d loops, mask %02X, entry val %08X, exit val %08X\n", + limit, mask, e, x); +#endif + return 0; +} + +static void cb710_mmc_set_transfer_size(struct cb710_slot *slot, + size_t count, size_t blocksize) +{ + cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20); + cb710_write_port_32(slot, CB710_MMC_TRANSFER_SIZE_PORT, + ((count - 1) << 16)|(blocksize - 1)); + + dev_vdbg(cb710_slot_dev(slot), "set up for %d block%s of %d bytes\n", + count, count == 1 ? "" : "s", blocksize); +} + +static void cb710_mmc_fifo_hack(struct cb710_slot *slot) +{ + /* without this, received data is prepended with 8-bytes of zeroes */ + u32 r1, r2; + int ok = 0; + + r1 = cb710_read_port_32(slot, CB710_MMC_DATA_PORT); + r2 = cb710_read_port_32(slot, CB710_MMC_DATA_PORT); + if (cb710_read_port_8(slot, CB710_MMC_STATUS0_PORT) + & CB710_MMC_S0_FIFO_UNDERFLOW) { + cb710_write_port_8(slot, CB710_MMC_STATUS0_PORT, + CB710_MMC_S0_FIFO_UNDERFLOW); + ok = 1; + } + + dev_dbg(cb710_slot_dev(slot), + "FIFO-read-hack: expected STATUS0 bit was %s\n", + ok ? "set." : "NOT SET!"); + dev_dbg(cb710_slot_dev(slot), + "FIFO-read-hack: dwords ignored: %08X %08X - %s\n", + r1, r2, (r1|r2) ? "BAD (NOT ZERO)!" : "ok"); +} + +static int cb710_mmc_receive_pio(struct cb710_slot *slot, + struct sg_mapping_iter *miter, size_t dw_count) +{ + if (!(cb710_read_port_8(slot, CB710_MMC_STATUS2_PORT) & CB710_MMC_S2_FIFO_READY)) { + int err = cb710_wait_for_event(slot, + CB710_MMC_S1_PIO_TRANSFER_DONE); + if (err) + return err; + } + + cb710_sg_dwiter_write_from_io(miter, + slot->iobase + CB710_MMC_DATA_PORT, dw_count); + + return 0; +} + +static bool cb710_is_transfer_size_supported(struct mmc_data *data) +{ + return !(data->blksz & 15 && (data->blocks != 1 || data->blksz != 8)); +} + +static int cb710_mmc_receive(struct cb710_slot *slot, struct mmc_data *data) +{ + struct sg_mapping_iter miter; + size_t len, blocks = data->blocks; + int err = 0; + + /* TODO: I don't know how/if the hardware handles non-16B-boundary blocks + * except single 8B block */ + if (unlikely(data->blksz & 15 && (data->blocks != 1 || data->blksz != 8))) + return -EINVAL; + + sg_miter_start(&miter, data->sg, data->sg_len, 0); + + cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT, + 15, CB710_MMC_C2_READ_PIO_SIZE_MASK); + + cb710_mmc_fifo_hack(slot); + + while (blocks-- > 0) { + len = data->blksz; + + while (len >= 16) { + err = cb710_mmc_receive_pio(slot, &miter, 4); + if (err) + goto out; + len -= 16; + } + + if (!len) + continue; + + cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT, + len - 1, CB710_MMC_C2_READ_PIO_SIZE_MASK); + + len = (len >= 8) ? 4 : 2; + err = cb710_mmc_receive_pio(slot, &miter, len); + if (err) + goto out; + } +out: + cb710_sg_miter_stop_writing(&miter); + return err; +} + +static int cb710_mmc_send(struct cb710_slot *slot, struct mmc_data *data) +{ + struct sg_mapping_iter miter; + size_t len, blocks = data->blocks; + int err = 0; + + /* TODO: I don't know how/if the hardware handles multiple + * non-16B-boundary blocks */ + if (unlikely(data->blocks > 1 && data->blksz & 15)) + return -EINVAL; + + sg_miter_start(&miter, data->sg, data->sg_len, 0); + + cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT, + 0, CB710_MMC_C2_READ_PIO_SIZE_MASK); + + while (blocks-- > 0) { + len = (data->blksz + 15) >> 4; + do { + if (!(cb710_read_port_8(slot, CB710_MMC_STATUS2_PORT) + & CB710_MMC_S2_FIFO_EMPTY)) { + err = cb710_wait_for_event(slot, + CB710_MMC_S1_PIO_TRANSFER_DONE); + if (err) + goto out; + } + cb710_sg_dwiter_read_to_io(&miter, + slot->iobase + CB710_MMC_DATA_PORT, 4); + } while (--len); + } +out: + sg_miter_stop(&miter); + return err; +} + +static u16 cb710_encode_cmd_flags(struct cb710_mmc_reader *reader, + struct mmc_command *cmd) +{ + unsigned int flags = cmd->flags; + u16 cb_flags = 0; + + /* Windows driver returned 0 for commands for which no response + * is expected. It happened that there were only two such commands + * used: MMC_GO_IDLE_STATE and MMC_GO_INACTIVE_STATE so it might + * as well be a bug in that driver. + * + * Original driver set bit 14 for MMC/SD application + * commands. There's no difference 'on the wire' and + * it apparently works without it anyway. + */ + + switch (flags & MMC_CMD_MASK) { + case MMC_CMD_AC: cb_flags = CB710_MMC_CMD_AC; break; + case MMC_CMD_ADTC: cb_flags = CB710_MMC_CMD_ADTC; break; + case MMC_CMD_BC: cb_flags = CB710_MMC_CMD_BC; break; + case MMC_CMD_BCR: cb_flags = CB710_MMC_CMD_BCR; break; + } + + if (flags & MMC_RSP_BUSY) + cb_flags |= CB710_MMC_RSP_BUSY; + + cb_flags |= cmd->opcode << CB710_MMC_CMD_CODE_SHIFT; + + if (cmd->data && (cmd->data->flags & MMC_DATA_READ)) + cb_flags |= CB710_MMC_DATA_READ; + + if (flags & MMC_RSP_PRESENT) { + /* Windows driver set 01 at bits 4,3 except for + * MMC_SET_BLOCKLEN where it set 10. Maybe the + * hardware can do something special about this + * command? The original driver looks buggy/incomplete + * anyway so we ignore this for now. + * + * I assume that 00 here means no response is expected. + */ + cb_flags |= CB710_MMC_RSP_PRESENT; + + if (flags & MMC_RSP_136) + cb_flags |= CB710_MMC_RSP_136; + if (!(flags & MMC_RSP_CRC)) + cb_flags |= CB710_MMC_RSP_NO_CRC; + } + + return cb_flags; +} + +static void cb710_receive_response(struct cb710_slot *slot, + struct mmc_command *cmd) +{ + unsigned rsp_opcode, wanted_opcode; + + /* Looks like final byte with CRC is always stripped (same as SDHCI) */ + if (cmd->flags & MMC_RSP_136) { + u32 resp[4]; + + resp[0] = cb710_read_port_32(slot, CB710_MMC_RESPONSE3_PORT); + resp[1] = cb710_read_port_32(slot, CB710_MMC_RESPONSE2_PORT); + resp[2] = cb710_read_port_32(slot, CB710_MMC_RESPONSE1_PORT); + resp[3] = cb710_read_port_32(slot, CB710_MMC_RESPONSE0_PORT); + rsp_opcode = resp[0] >> 24; + + cmd->resp[0] = (resp[0] << 8)|(resp[1] >> 24); + cmd->resp[1] = (resp[1] << 8)|(resp[2] >> 24); + cmd->resp[2] = (resp[2] << 8)|(resp[3] >> 24); + cmd->resp[3] = (resp[3] << 8); + } else { + rsp_opcode = cb710_read_port_32(slot, CB710_MMC_RESPONSE1_PORT) & 0x3F; + cmd->resp[0] = cb710_read_port_32(slot, CB710_MMC_RESPONSE0_PORT); + } + + wanted_opcode = (cmd->flags & MMC_RSP_OPCODE) ? cmd->opcode : 0x3F; + if (rsp_opcode != wanted_opcode) + cmd->error = -EILSEQ; +} + +static int cb710_mmc_transfer_data(struct cb710_slot *slot, + struct mmc_data *data) +{ + int error, to; + + if (data->flags & MMC_DATA_READ) + error = cb710_mmc_receive(slot, data); + else + error = cb710_mmc_send(slot, data); + + to = cb710_wait_for_event(slot, CB710_MMC_S1_DATA_TRANSFER_DONE); + if (!error) + error = to; + + if (!error) + data->bytes_xfered = data->blksz * data->blocks; + return error; +} + +static int cb710_mmc_command(struct mmc_host *mmc, struct mmc_command *cmd) +{ + struct cb710_slot *slot = cb710_mmc_to_slot(mmc); + struct cb710_mmc_reader *reader = mmc_priv(mmc); + struct mmc_data *data = cmd->data; + + u16 cb_cmd = cb710_encode_cmd_flags(reader, cmd); + dev_dbg(cb710_slot_dev(slot), "cmd request: 0x%04X\n", cb_cmd); + + if (data) { + if (!cb710_is_transfer_size_supported(data)) { + data->error = -EINVAL; + return -1; + } + cb710_mmc_set_transfer_size(slot, data->blocks, data->blksz); + } + + cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20|CB710_MMC_S2_BUSY_10); + cb710_write_port_16(slot, CB710_MMC_CMD_TYPE_PORT, cb_cmd); + cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20); + cb710_write_port_32(slot, CB710_MMC_CMD_PARAM_PORT, cmd->arg); + cb710_mmc_reset_events(slot); + cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20); + cb710_modify_port_8(slot, CB710_MMC_CONFIG0_PORT, 0x01, 0); + + cmd->error = cb710_wait_for_event(slot, CB710_MMC_S1_COMMAND_SENT); + if (cmd->error) + return -1; + + if (cmd->flags & MMC_RSP_PRESENT) { + cb710_receive_response(slot, cmd); + if (cmd->error) + return -1; + } + + if (data) + data->error = cb710_mmc_transfer_data(slot, data); + return 0; +} + +static void cb710_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct cb710_slot *slot = cb710_mmc_to_slot(mmc); + struct cb710_mmc_reader *reader = mmc_priv(mmc); + + WARN_ON(reader->mrq != NULL); + + reader->mrq = mrq; + cb710_mmc_enable_irq(slot, CB710_MMC_IE_TEST_MASK, 0); + + if (cb710_mmc_is_card_inserted(slot)) { + if (!cb710_mmc_command(mmc, mrq->cmd) && mrq->stop) + cb710_mmc_command(mmc, mrq->stop); + mdelay(1); + } else { + mrq->cmd->error = -ENOMEDIUM; + } + + tasklet_schedule(&reader->finish_req_tasklet); +} + +static int cb710_mmc_powerup(struct cb710_slot *slot) +{ +#ifdef CONFIG_CB710_DEBUG + struct cb710_chip *chip = cb710_slot_to_chip(slot); +#endif + int err; + + /* a lot of magic; see comment in cb710_mmc_set_clock() */ + dev_dbg(cb710_slot_dev(slot), "bus powerup\n"); + cb710_dump_regs(chip, CB710_DUMP_REGS_MMC); + err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20); + if (unlikely(err)) + return err; + cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0x80, 0); + cb710_modify_port_8(slot, CB710_MMC_CONFIG3_PORT, 0x80, 0); + cb710_dump_regs(chip, CB710_DUMP_REGS_MMC); + mdelay(1); + dev_dbg(cb710_slot_dev(slot), "after delay 1\n"); + cb710_dump_regs(chip, CB710_DUMP_REGS_MMC); + err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20); + if (unlikely(err)) + return err; + cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0x09, 0); + cb710_dump_regs(chip, CB710_DUMP_REGS_MMC); + mdelay(1); + dev_dbg(cb710_slot_dev(slot), "after delay 2\n"); + cb710_dump_regs(chip, CB710_DUMP_REGS_MMC); + err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20); + if (unlikely(err)) + return err; + cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0, 0x08); + cb710_dump_regs(chip, CB710_DUMP_REGS_MMC); + mdelay(2); + dev_dbg(cb710_slot_dev(slot), "after delay 3\n"); + cb710_dump_regs(chip, CB710_DUMP_REGS_MMC); + cb710_modify_port_8(slot, CB710_MMC_CONFIG0_PORT, 0x06, 0); + cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0x70, 0); + cb710_modify_port_8(slot, CB710_MMC_CONFIG2_PORT, 0x80, 0); + cb710_modify_port_8(slot, CB710_MMC_CONFIG3_PORT, 0x03, 0); + cb710_dump_regs(chip, CB710_DUMP_REGS_MMC); + err = cb710_wait_while_busy(slot, CB710_MMC_S2_BUSY_20); + if (unlikely(err)) + return err; + /* This port behaves weird: quick byte reads of 0x08,0x09 return + * 0xFF,0x00 after writing 0xFFFF to 0x08; it works correctly when + * read/written from userspace... What am I missing here? + * (it doesn't depend on write-to-read delay) */ + cb710_write_port_16(slot, CB710_MMC_CONFIGB_PORT, 0xFFFF); + cb710_modify_port_8(slot, CB710_MMC_CONFIG0_PORT, 0x06, 0); + cb710_dump_regs(chip, CB710_DUMP_REGS_MMC); + dev_dbg(cb710_slot_dev(slot), "bus powerup finished\n"); + + return cb710_check_event(slot, 0); +} + +static void cb710_mmc_powerdown(struct cb710_slot *slot) +{ + cb710_modify_port_8(slot, CB710_MMC_CONFIG1_PORT, 0, 0x81); + cb710_modify_port_8(slot, CB710_MMC_CONFIG3_PORT, 0, 0x80); +} + +static void cb710_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct cb710_slot *slot = cb710_mmc_to_slot(mmc); + struct cb710_mmc_reader *reader = mmc_priv(mmc); + int err; + + cb710_mmc_set_clock(mmc, ios->clock); + + if (!cb710_mmc_is_card_inserted(slot)) { + dev_dbg(cb710_slot_dev(slot), + "no card inserted - ignoring bus powerup request\n"); + ios->power_mode = MMC_POWER_OFF; + } + + if (ios->power_mode != reader->last_power_mode) + switch (ios->power_mode) { + case MMC_POWER_ON: + err = cb710_mmc_powerup(slot); + if (err) { + dev_warn(cb710_slot_dev(slot), + "powerup failed (%d)- retrying\n", err); + cb710_mmc_powerdown(slot); + udelay(1); + err = cb710_mmc_powerup(slot); + if (err) + dev_warn(cb710_slot_dev(slot), + "powerup retry failed (%d) - expect errors\n", + err); + } + reader->last_power_mode = MMC_POWER_ON; + break; + case MMC_POWER_OFF: + cb710_mmc_powerdown(slot); + reader->last_power_mode = MMC_POWER_OFF; + break; + case MMC_POWER_UP: + default: + /* ignore */; + } + + cb710_mmc_enable_4bit_data(slot, ios->bus_width != MMC_BUS_WIDTH_1); + + cb710_mmc_enable_irq(slot, CB710_MMC_IE_TEST_MASK, 0); +} + +static int cb710_mmc_get_ro(struct mmc_host *mmc) +{ + struct cb710_slot *slot = cb710_mmc_to_slot(mmc); + + return cb710_read_port_8(slot, CB710_MMC_STATUS3_PORT) + & CB710_MMC_S3_WRITE_PROTECTED; +} + +static int cb710_mmc_irq_handler(struct cb710_slot *slot) +{ + struct mmc_host *mmc = cb710_slot_to_mmc(slot); + struct cb710_mmc_reader *reader = mmc_priv(mmc); + u32 status, config1, config2, irqen; + + status = cb710_read_port_32(slot, CB710_MMC_STATUS_PORT); + irqen = cb710_read_port_32(slot, CB710_MMC_IRQ_ENABLE_PORT); + config2 = cb710_read_port_32(slot, CB710_MMC_CONFIGB_PORT); + config1 = cb710_read_port_32(slot, CB710_MMC_CONFIG_PORT); + + dev_dbg(cb710_slot_dev(slot), "interrupt; status: %08X, " + "ie: %08X, c2: %08X, c1: %08X\n", + status, irqen, config2, config1); + + if (status & (CB710_MMC_S1_CARD_CHANGED << 8)) { + /* ack the event */ + cb710_write_port_8(slot, CB710_MMC_STATUS1_PORT, + CB710_MMC_S1_CARD_CHANGED); + if ((irqen & CB710_MMC_IE_CISTATUS_MASK) + == CB710_MMC_IE_CISTATUS_MASK) + mmc_detect_change(mmc, HZ/5); + } else { + dev_dbg(cb710_slot_dev(slot), "unknown interrupt (test)\n"); + spin_lock(&reader->irq_lock); + __cb710_mmc_enable_irq(slot, 0, CB710_MMC_IE_TEST_MASK); + spin_unlock(&reader->irq_lock); + } + + return 1; +} + +static void cb710_mmc_finish_request_tasklet(unsigned long data) +{ + struct mmc_host *mmc = (void *)data; + struct cb710_mmc_reader *reader = mmc_priv(mmc); + struct mmc_request *mrq = reader->mrq; + + reader->mrq = NULL; + mmc_request_done(mmc, mrq); +} + +static const struct mmc_host_ops cb710_mmc_host = { + .request = cb710_mmc_request, + .set_ios = cb710_mmc_set_ios, + .get_ro = cb710_mmc_get_ro +}; + +#ifdef CONFIG_PM + +static int cb710_mmc_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct cb710_slot *slot = cb710_pdev_to_slot(pdev); + struct mmc_host *mmc = cb710_slot_to_mmc(slot); + int err; + + err = mmc_suspend_host(mmc, state); + if (err) + return err; + + cb710_mmc_enable_irq(slot, 0, ~0); + return 0; +} + +static int cb710_mmc_resume(struct platform_device *pdev) +{ + struct cb710_slot *slot = cb710_pdev_to_slot(pdev); + struct mmc_host *mmc = cb710_slot_to_mmc(slot); + + cb710_mmc_enable_irq(slot, 0, ~0); + + return mmc_resume_host(mmc); +} + +#endif /* CONFIG_PM */ + +static int __devinit cb710_mmc_init(struct platform_device *pdev) +{ + struct cb710_slot *slot = cb710_pdev_to_slot(pdev); + struct cb710_chip *chip = cb710_slot_to_chip(slot); + struct mmc_host *mmc; + struct cb710_mmc_reader *reader; + int err; + u32 val; + + mmc = mmc_alloc_host(sizeof(*reader), cb710_slot_dev(slot)); + if (!mmc) + return -ENOMEM; + + dev_set_drvdata(&pdev->dev, mmc); + + /* harmless (maybe) magic */ + pci_read_config_dword(chip->pdev, 0x48, &val); + val = cb710_src_freq_mhz[(val >> 16) & 0xF]; + dev_dbg(cb710_slot_dev(slot), "source frequency: %dMHz\n", val); + val *= 1000000; + + mmc->ops = &cb710_mmc_host; + mmc->f_max = val; + mmc->f_min = val >> cb710_clock_divider_log2[CB710_MAX_DIVIDER_IDX]; + mmc->ocr_avail = MMC_VDD_32_33|MMC_VDD_33_34; + mmc->caps = MMC_CAP_4_BIT_DATA; + + reader = mmc_priv(mmc); + + tasklet_init(&reader->finish_req_tasklet, + cb710_mmc_finish_request_tasklet, (unsigned long)mmc); + spin_lock_init(&reader->irq_lock); + cb710_dump_regs(chip, CB710_DUMP_REGS_MMC); + + cb710_mmc_enable_irq(slot, 0, ~0); + cb710_set_irq_handler(slot, cb710_mmc_irq_handler); + + err = mmc_add_host(mmc); + if (unlikely(err)) + goto err_free_mmc; + + dev_dbg(cb710_slot_dev(slot), "mmc_hostname is %s\n", + mmc_hostname(mmc)); + + cb710_mmc_enable_irq(slot, CB710_MMC_IE_CARD_INSERTION_STATUS, 0); + + return 0; + +err_free_mmc: + dev_dbg(cb710_slot_dev(slot), "mmc_add_host() failed: %d\n", err); + + mmc_free_host(mmc); + return err; +} + +static int __devexit cb710_mmc_exit(struct platform_device *pdev) +{ + struct cb710_slot *slot = cb710_pdev_to_slot(pdev); + struct mmc_host *mmc = cb710_slot_to_mmc(slot); + struct cb710_mmc_reader *reader = mmc_priv(mmc); + + cb710_mmc_enable_irq(slot, 0, CB710_MMC_IE_CARD_INSERTION_STATUS); + + mmc_remove_host(mmc); + + /* IRQs should be disabled now, but let's stay on the safe side */ + cb710_mmc_enable_irq(slot, 0, ~0); + cb710_set_irq_handler(slot, NULL); + + /* clear config ports - just in case */ + cb710_write_port_32(slot, CB710_MMC_CONFIG_PORT, 0); + cb710_write_port_16(slot, CB710_MMC_CONFIGB_PORT, 0); + + tasklet_kill(&reader->finish_req_tasklet); + + mmc_free_host(mmc); + return 0; +} + +static struct platform_driver cb710_mmc_driver = { + .driver.name = "cb710-mmc", + .probe = cb710_mmc_init, + .remove = __devexit_p(cb710_mmc_exit), +#ifdef CONFIG_PM + .suspend = cb710_mmc_suspend, + .resume = cb710_mmc_resume, +#endif +}; + +static int __init cb710_mmc_init_module(void) +{ + return platform_driver_register(&cb710_mmc_driver); +} + +static void __exit cb710_mmc_cleanup_module(void) +{ + platform_driver_unregister(&cb710_mmc_driver); +} + +module_init(cb710_mmc_init_module); +module_exit(cb710_mmc_cleanup_module); + +MODULE_AUTHOR("Michał Mirosław "); +MODULE_DESCRIPTION("ENE CB710 memory card reader driver - MMC/SD part"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:cb710-mmc"); diff --git a/drivers/mmc/host/cb710-mmc.h b/drivers/mmc/host/cb710-mmc.h new file mode 100644 index 00000000000..e845c776bdd --- /dev/null +++ b/drivers/mmc/host/cb710-mmc.h @@ -0,0 +1,104 @@ +/* + * cb710/cb710-mmc.h + * + * Copyright by Michał Mirosław, 2008-2009 + * + * 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. + */ +#ifndef LINUX_CB710_MMC_H +#define LINUX_CB710_MMC_H + +#include + +/* per-MMC-reader structure */ +struct cb710_mmc_reader { + struct tasklet_struct finish_req_tasklet; + struct mmc_request *mrq; + spinlock_t irq_lock; + unsigned char last_power_mode; +}; + +/* some device struct walking */ + +static inline struct mmc_host *cb710_slot_to_mmc(struct cb710_slot *slot) +{ + return dev_get_drvdata(&slot->pdev.dev); +} + +static inline struct cb710_slot *cb710_mmc_to_slot(struct mmc_host *mmc) +{ + struct platform_device *pdev = container_of(mmc_dev(mmc), + struct platform_device, dev); + return cb710_pdev_to_slot(pdev); +} + +/* registers (this might be all wrong ;) */ + +#define CB710_MMC_DATA_PORT 0x00 + +#define CB710_MMC_CONFIG_PORT 0x04 +#define CB710_MMC_CONFIG0_PORT 0x04 +#define CB710_MMC_CONFIG1_PORT 0x05 +#define CB710_MMC_C1_4BIT_DATA_BUS 0x40 +#define CB710_MMC_CONFIG2_PORT 0x06 +#define CB710_MMC_C2_READ_PIO_SIZE_MASK 0x0F /* N-1 */ +#define CB710_MMC_CONFIG3_PORT 0x07 + +#define CB710_MMC_CONFIGB_PORT 0x08 + +#define CB710_MMC_IRQ_ENABLE_PORT 0x0C +#define CB710_MMC_IE_TEST_MASK 0x00BF +#define CB710_MMC_IE_CARD_INSERTION_STATUS 0x1000 +#define CB710_MMC_IE_IRQ_ENABLE 0x8000 +#define CB710_MMC_IE_CISTATUS_MASK \ + (CB710_MMC_IE_CARD_INSERTION_STATUS|CB710_MMC_IE_IRQ_ENABLE) + +#define CB710_MMC_STATUS_PORT 0x10 +#define CB710_MMC_STATUS_ERROR_EVENTS 0x60FF +#define CB710_MMC_STATUS0_PORT 0x10 +#define CB710_MMC_S0_FIFO_UNDERFLOW 0x40 +#define CB710_MMC_STATUS1_PORT 0x11 +#define CB710_MMC_S1_COMMAND_SENT 0x01 +#define CB710_MMC_S1_DATA_TRANSFER_DONE 0x02 +#define CB710_MMC_S1_PIO_TRANSFER_DONE 0x04 +#define CB710_MMC_S1_CARD_CHANGED 0x10 +#define CB710_MMC_S1_RESET 0x20 +#define CB710_MMC_STATUS2_PORT 0x12 +#define CB710_MMC_S2_FIFO_READY 0x01 +#define CB710_MMC_S2_FIFO_EMPTY 0x02 +#define CB710_MMC_S2_BUSY_10 0x10 +#define CB710_MMC_S2_BUSY_20 0x20 +#define CB710_MMC_STATUS3_PORT 0x13 +#define CB710_MMC_S3_CARD_DETECTED 0x02 +#define CB710_MMC_S3_WRITE_PROTECTED 0x04 + +#define CB710_MMC_CMD_TYPE_PORT 0x14 +#define CB710_MMC_RSP_TYPE_MASK 0x0007 +#define CB710_MMC_RSP_R1 (0) +#define CB710_MMC_RSP_136 (5) +#define CB710_MMC_RSP_NO_CRC (2) +#define CB710_MMC_RSP_PRESENT_MASK 0x0018 +#define CB710_MMC_RSP_NONE (0 << 3) +#define CB710_MMC_RSP_PRESENT (1 << 3) +#define CB710_MMC_RSP_PRESENT_X (2 << 3) +#define CB710_MMC_CMD_TYPE_MASK 0x0060 +#define CB710_MMC_CMD_BC (0 << 5) +#define CB710_MMC_CMD_BCR (1 << 5) +#define CB710_MMC_CMD_AC (2 << 5) +#define CB710_MMC_CMD_ADTC (3 << 5) +#define CB710_MMC_DATA_READ 0x0080 +#define CB710_MMC_CMD_CODE_MASK 0x3F00 +#define CB710_MMC_CMD_CODE_SHIFT 8 +#define CB710_MMC_IS_APP_CMD 0x4000 +#define CB710_MMC_RSP_BUSY 0x8000 + +#define CB710_MMC_CMD_PARAM_PORT 0x18 +#define CB710_MMC_TRANSFER_SIZE_PORT 0x1C +#define CB710_MMC_RESPONSE0_PORT 0x20 +#define CB710_MMC_RESPONSE1_PORT 0x24 +#define CB710_MMC_RESPONSE2_PORT 0x28 +#define CB710_MMC_RESPONSE3_PORT 0x2C + +#endif /* LINUX_CB710_MMC_H */ diff --git a/include/linux/cb710.h b/include/linux/cb710.h new file mode 100644 index 00000000000..c3819e1ce1c --- /dev/null +++ b/include/linux/cb710.h @@ -0,0 +1,238 @@ +/* + * cb710/cb710.h + * + * Copyright by Michał Mirosław, 2008-2009 + * + * 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. + */ +#ifndef LINUX_CB710_DRIVER_H +#define LINUX_CB710_DRIVER_H + +#ifdef CONFIG_CB710_DEBUG +#define DEBUG +#endif + +/* verify assumptions on platform_device framework */ +#define CONFIG_CB710_DEBUG_ASSUMPTIONS + +#include +#include +#include +#include +#include +#include + +struct cb710_slot; + +typedef int (*cb710_irq_handler_t)(struct cb710_slot *); + +/* per-virtual-slot structure */ +struct cb710_slot { + struct platform_device pdev; + void __iomem *iobase; + cb710_irq_handler_t irq_handler; +}; + +/* per-device structure */ +struct cb710_chip { + struct pci_dev *pdev; + void __iomem *iobase; + unsigned platform_id; +#ifdef CONFIG_CB710_DEBUG_ASSUMPTIONS + atomic_t slot_refs_count; +#endif + unsigned slot_mask; + unsigned slots; + spinlock_t irq_lock; + struct cb710_slot slot[0]; +}; + +/* NOTE: cb710_chip.slots is modified only during device init/exit and + * they are all serialized wrt themselves */ + +/* cb710_chip.slot_mask values */ +#define CB710_SLOT_MMC 1 +#define CB710_SLOT_MS 2 +#define CB710_SLOT_SM 4 + +/* slot port accessors - so the logic is more clear in the code */ +#define CB710_PORT_ACCESSORS(t) \ +static inline void cb710_write_port_##t(struct cb710_slot *slot, \ + unsigned port, u##t value) \ +{ \ + iowrite##t(value, slot->iobase + port); \ +} \ + \ +static inline u##t cb710_read_port_##t(struct cb710_slot *slot, \ + unsigned port) \ +{ \ + return ioread##t(slot->iobase + port); \ +} \ + \ +static inline void cb710_modify_port_##t(struct cb710_slot *slot, \ + unsigned port, u##t set, u##t clear) \ +{ \ + iowrite##t( \ + (ioread##t(slot->iobase + port) & ~clear)|set, \ + slot->iobase + port); \ +} + +CB710_PORT_ACCESSORS(8) +CB710_PORT_ACCESSORS(16) +CB710_PORT_ACCESSORS(32) + +void cb710_pci_update_config_reg(struct pci_dev *pdev, + int reg, uint32_t and, uint32_t xor); +void cb710_set_irq_handler(struct cb710_slot *slot, + cb710_irq_handler_t handler); + +/* some device struct walking */ + +static inline struct cb710_slot *cb710_pdev_to_slot( + struct platform_device *pdev) +{ + return container_of(pdev, struct cb710_slot, pdev); +} + +static inline struct cb710_chip *cb710_slot_to_chip(struct cb710_slot *slot) +{ + return dev_get_drvdata(slot->pdev.dev.parent); +} + +static inline struct device *cb710_slot_dev(struct cb710_slot *slot) +{ + return &slot->pdev.dev; +} + +static inline struct device *cb710_chip_dev(struct cb710_chip *chip) +{ + return &chip->pdev->dev; +} + +/* debugging aids */ + +#ifdef CONFIG_CB710_DEBUG +void cb710_dump_regs(struct cb710_chip *chip, unsigned dump); +#else +#define cb710_dump_regs(c, d) do {} while (0) +#endif + +#define CB710_DUMP_REGS_MMC 0x0F +#define CB710_DUMP_REGS_MS 0x30 +#define CB710_DUMP_REGS_SM 0xC0 +#define CB710_DUMP_REGS_ALL 0xFF +#define CB710_DUMP_REGS_MASK 0xFF + +#define CB710_DUMP_ACCESS_8 0x100 +#define CB710_DUMP_ACCESS_16 0x200 +#define CB710_DUMP_ACCESS_32 0x400 +#define CB710_DUMP_ACCESS_ALL 0x700 +#define CB710_DUMP_ACCESS_MASK 0x700 + +#endif /* LINUX_CB710_DRIVER_H */ +/* + * cb710/sgbuf2.h + * + * Copyright by Michał Mirosław, 2008-2009 + * + * 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. + */ +#ifndef LINUX_CB710_SG_H +#define LINUX_CB710_SG_H + +#include +#include + +/** + * cb710_sg_miter_stop_writing - stop mapping iteration after writing + * @miter: sg mapping iter to be stopped + * + * Description: + * Stops mapping iterator @miter. @miter should have been started + * started using sg_miter_start(). A stopped iteration can be + * resumed by calling sg_miter_next() on it. This is useful when + * resources (kmap) need to be released during iteration. + * + * This is a convenience wrapper that will be optimized out for arches + * that don't need flush_kernel_dcache_page(). + * + * Context: + * IRQ disabled if the SG_MITER_ATOMIC is set. Don't care otherwise. + */ +static inline void cb710_sg_miter_stop_writing(struct sg_mapping_iter *miter) +{ + if (miter->page) + flush_kernel_dcache_page(miter->page); + sg_miter_stop(miter); +} + +/* + * 32-bit PIO mapping sg iterator + * + * Hides scatterlist access issues - fragment boundaries, alignment, page + * mapping - for drivers using 32-bit-word-at-a-time-PIO (ie. PCI devices + * without DMA support). + * + * Best-case reading (transfer from device): + * sg_miter_start(); + * cb710_sg_dwiter_write_from_io(); + * cb710_sg_miter_stop_writing(); + * + * Best-case writing (transfer to device): + * sg_miter_start(); + * cb710_sg_dwiter_read_to_io(); + * sg_miter_stop(); + */ + +uint32_t cb710_sg_dwiter_read_next_block(struct sg_mapping_iter *miter); +void cb710_sg_dwiter_write_next_block(struct sg_mapping_iter *miter, uint32_t data); + +/** + * cb710_sg_dwiter_write_from_io - transfer data to mapped buffer from 32-bit IO port + * @miter: sg mapping iter + * @port: PIO port - IO or MMIO address + * @count: number of 32-bit words to transfer + * + * Description: + * Reads @count 32-bit words from register @port and stores it in + * buffer iterated by @miter. Data that would overflow the buffer + * is silently ignored. Iterator is advanced by 4*@count bytes + * or to the buffer's end whichever is closer. + * + * Context: + * IRQ disabled if the SG_MITER_ATOMIC is set. Don't care otherwise. + */ +static inline void cb710_sg_dwiter_write_from_io(struct sg_mapping_iter *miter, + void __iomem *port, size_t count) +{ + while (count-- > 0) + cb710_sg_dwiter_write_next_block(miter, ioread32(port)); +} + +/** + * cb710_sg_dwiter_read_to_io - transfer data to 32-bit IO port from mapped buffer + * @miter: sg mapping iter + * @port: PIO port - IO or MMIO address + * @count: number of 32-bit words to transfer + * + * Description: + * Writes @count 32-bit words to register @port from buffer iterated + * through @miter. If buffer ends before @count words are written + * missing data is replaced by zeroes. @miter is advanced by 4*@count + * bytes or to the buffer's end whichever is closer. + * + * Context: + * IRQ disabled if the SG_MITER_ATOMIC is set. Don't care otherwise. + */ +static inline void cb710_sg_dwiter_read_to_io(struct sg_mapping_iter *miter, + void __iomem *port, size_t count) +{ + while (count-- > 0) + iowrite32(cb710_sg_dwiter_read_next_block(miter), port); +} + +#endif /* LINUX_CB710_SG_H */ diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index 19f8e6d1a4d..9f36e1cdbf0 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -2127,6 +2127,7 @@ #define PCI_VENDOR_ID_MAINPINE 0x1522 #define PCI_DEVICE_ID_MAINPINE_PBRIDGE 0x0100 #define PCI_VENDOR_ID_ENE 0x1524 +#define PCI_DEVICE_ID_ENE_CB710_FLASH 0x0510 #define PCI_DEVICE_ID_ENE_CB712_SD 0x0550 #define PCI_DEVICE_ID_ENE_CB712_SD_2 0x0551 #define PCI_DEVICE_ID_ENE_CB714_SD 0x0750 -- cgit v1.2.3-70-g09d2 From 10eb4f901cacf7da87145330f3ca77b723783497 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Thu, 4 Jun 2009 07:58:57 +0200 Subject: cb710: add missing parenthesis Signed-off-by: Pierre Ossman --- drivers/misc/cb710/debug.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/misc') diff --git a/drivers/misc/cb710/debug.c b/drivers/misc/cb710/debug.c index 9da11bc620a..02358d086e0 100644 --- a/drivers/misc/cb710/debug.c +++ b/drivers/misc/cb710/debug.c @@ -37,7 +37,7 @@ static void cb710_read_regs_##t(void __iomem *iobase, \ unsigned i, j; \ \ for (i = 0; i < ARRAY_SIZE(allow); ++i, reg += 16/(t/8)) { \ - if (!select & (1 << i)) \ + if (!(select & (1 << i))) \ continue; \ \ for (j = 0; j < 0x10/(t/8); ++j) { \ -- cgit v1.2.3-70-g09d2 From 9bf69a26ad9ccdc49469402275204271b3336ab6 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Thu, 4 Jun 2009 08:00:40 +0200 Subject: cb710: handle DEBUG define in Makefile Signed-off-by: Pierre Ossman --- drivers/misc/cb710/Makefile | 4 ++++ include/linux/cb710.h | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers/misc') diff --git a/drivers/misc/cb710/Makefile b/drivers/misc/cb710/Makefile index 62d51acfeca..7b80cbf1a60 100644 --- a/drivers/misc/cb710/Makefile +++ b/drivers/misc/cb710/Makefile @@ -1,3 +1,7 @@ +ifeq ($(CONFIG_CB710_DEBUG),y) + EXTRA_CFLAGS += -DDEBUG +endif + obj-$(CONFIG_CB710_CORE) += cb710.o cb710-y := core.o sgbuf2.o diff --git a/include/linux/cb710.h b/include/linux/cb710.h index c3819e1ce1c..a09213b5261 100644 --- a/include/linux/cb710.h +++ b/include/linux/cb710.h @@ -10,10 +10,6 @@ #ifndef LINUX_CB710_DRIVER_H #define LINUX_CB710_DRIVER_H -#ifdef CONFIG_CB710_DEBUG -#define DEBUG -#endif - /* verify assumptions on platform_device framework */ #define CONFIG_CB710_DEBUG_ASSUMPTIONS -- cgit v1.2.3-70-g09d2 From c54f6bc67a4398243682f7438a2129906e127d21 Mon Sep 17 00:00:00 2001 From: Michał Mirosław Date: Sat, 13 Jun 2009 12:37:59 +0200 Subject: cb710: more cleanup for the DEBUG case. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Mirosław Signed-off-by: Pierre Ossman --- drivers/misc/cb710/Kconfig | 4 ++++ drivers/mmc/host/Makefile | 3 +++ include/linux/cb710.h | 3 --- 3 files changed, 7 insertions(+), 3 deletions(-) (limited to 'drivers/misc') diff --git a/drivers/misc/cb710/Kconfig b/drivers/misc/cb710/Kconfig index 0b55079774c..22429b8b106 100644 --- a/drivers/misc/cb710/Kconfig +++ b/drivers/misc/cb710/Kconfig @@ -19,3 +19,7 @@ config CB710_DEBUG This is an option for use by developers; most people should say N here. This adds a lot of debugging output to dmesg. +config CB710_DEBUG_ASSUMPTIONS + bool + depends on CB710_CORE != n + default y diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index 254e441291d..79da397c5fe 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -32,3 +32,6 @@ obj-$(CONFIG_MMC_SDRICOH_CS) += sdricoh_cs.o obj-$(CONFIG_MMC_TMIO) += tmio_mmc.o obj-$(CONFIG_MMC_CB710) += cb710-mmc.o +ifeq ($(CONFIG_CB710_DEBUG),y) + CFLAGS-cb710-mmc += -DDEBUG +endif diff --git a/include/linux/cb710.h b/include/linux/cb710.h index a09213b5261..63bc9a4d292 100644 --- a/include/linux/cb710.h +++ b/include/linux/cb710.h @@ -10,9 +10,6 @@ #ifndef LINUX_CB710_DRIVER_H #define LINUX_CB710_DRIVER_H -/* verify assumptions on platform_device framework */ -#define CONFIG_CB710_DEBUG_ASSUMPTIONS - #include #include #include -- cgit v1.2.3-70-g09d2 From c53bd2e1949ddbe06fe2a6079c0658d58ce25edb Mon Sep 17 00:00:00 2001 From: Vegard Nossum Date: Thu, 26 Feb 2009 14:05:59 +0100 Subject: c2port: annotate bitfield for kmemcheck This silences a false positive warning with kmemcheck. Signed-off-by: Vegard Nossum --- drivers/misc/c2port/core.c | 2 ++ include/linux/c2port.h | 3 +++ 2 files changed, 5 insertions(+) (limited to 'drivers/misc') diff --git a/drivers/misc/c2port/core.c b/drivers/misc/c2port/core.c index 0207dd59090..b5346b4db91 100644 --- a/drivers/misc/c2port/core.c +++ b/drivers/misc/c2port/core.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -891,6 +892,7 @@ struct c2port_device *c2port_device_register(char *name, return ERR_PTR(-EINVAL); c2dev = kmalloc(sizeof(struct c2port_device), GFP_KERNEL); + kmemcheck_annotate_bitfield(c2dev, flags); if (unlikely(!c2dev)) return ERR_PTR(-ENOMEM); diff --git a/include/linux/c2port.h b/include/linux/c2port.h index 7b5a2388ba6..2a5cd867c36 100644 --- a/include/linux/c2port.h +++ b/include/linux/c2port.h @@ -10,6 +10,7 @@ */ #include +#include #define C2PORT_NAME_LEN 32 @@ -20,8 +21,10 @@ /* Main struct */ struct c2port_ops; struct c2port_device { + kmemcheck_bitfield_begin(flags); unsigned int access:1; unsigned int flash_access:1; + kmemcheck_bitfield_end(flags); int id; char name[C2PORT_NAME_LEN]; -- cgit v1.2.3-70-g09d2 From 4b364f230a5ef984818837ec7c2be0884eac191c Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Mon, 15 Jun 2009 18:01:49 +0200 Subject: i2c/chips: Move max6875 to drivers/misc/eeprom This driver only reads the user EEPROM of that chip, so we can move it to the eeprom-directory in order to further clean up (and later remove) drivers/i2c/chips. The Kconfig text was updated to match the current functionality, dropping the meanwhile obsoleted parts. Defconfigs have been adapted. Signed-off-by: Wolfram Sang Acked-by: Ben Gardner Signed-off-by: Jean Delvare --- arch/mips/configs/bigsur_defconfig | 2 +- arch/mips/configs/mtx1_defconfig | 2 +- arch/powerpc/configs/ppc6xx_defconfig | 2 +- drivers/i2c/chips/Kconfig | 15 --- drivers/i2c/chips/Makefile | 1 - drivers/i2c/chips/max6875.c | 246 ---------------------------------- drivers/misc/eeprom/Kconfig | 14 ++ drivers/misc/eeprom/Makefile | 1 + drivers/misc/eeprom/max6875.c | 246 ++++++++++++++++++++++++++++++++++ 9 files changed, 264 insertions(+), 265 deletions(-) delete mode 100644 drivers/i2c/chips/max6875.c create mode 100644 drivers/misc/eeprom/max6875.c (limited to 'drivers/misc') diff --git a/arch/mips/configs/bigsur_defconfig b/arch/mips/configs/bigsur_defconfig index 783da855a2e..d6d35b2e5fe 100644 --- a/arch/mips/configs/bigsur_defconfig +++ b/arch/mips/configs/bigsur_defconfig @@ -963,7 +963,7 @@ CONFIG_EEPROM_LEGACY=y CONFIG_SENSORS_PCF8574=y # CONFIG_PCF8575 is not set CONFIG_SENSORS_PCF8591=y -CONFIG_SENSORS_MAX6875=y +CONFIG_EEPROM_MAX6875=y # CONFIG_SENSORS_TSL2550 is not set CONFIG_I2C_DEBUG_CORE=y CONFIG_I2C_DEBUG_ALGO=y diff --git a/arch/mips/configs/mtx1_defconfig b/arch/mips/configs/mtx1_defconfig index 8426d3b9501..fadb351d249 100644 --- a/arch/mips/configs/mtx1_defconfig +++ b/arch/mips/configs/mtx1_defconfig @@ -1849,7 +1849,7 @@ CONFIG_EEPROM_LEGACY=m CONFIG_SENSORS_PCF8574=m CONFIG_SENSORS_PCA9539=m CONFIG_SENSORS_PCF8591=m -CONFIG_SENSORS_MAX6875=m +CONFIG_EEPROM_MAX6875=m # CONFIG_SENSORS_TSL2550 is not set # CONFIG_I2C_DEBUG_CORE is not set # CONFIG_I2C_DEBUG_ALGO is not set diff --git a/arch/powerpc/configs/ppc6xx_defconfig b/arch/powerpc/configs/ppc6xx_defconfig index 7d044dfd923..12dc7c40961 100644 --- a/arch/powerpc/configs/ppc6xx_defconfig +++ b/arch/powerpc/configs/ppc6xx_defconfig @@ -1808,7 +1808,7 @@ CONFIG_PCF8575=m CONFIG_SENSORS_PCA9539=m CONFIG_SENSORS_PCF8591=m # CONFIG_TPS65010 is not set -CONFIG_SENSORS_MAX6875=m +CONFIG_EEPROM_MAX6875=m CONFIG_SENSORS_TSL2550=m CONFIG_MCU_MPC8349EMITX=m # CONFIG_I2C_DEBUG_CORE is not set diff --git a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig index 8f8c81eb0ae..02d746c9c47 100644 --- a/drivers/i2c/chips/Kconfig +++ b/drivers/i2c/chips/Kconfig @@ -64,21 +64,6 @@ config SENSORS_PCA9539 This driver is deprecated and will be dropped soon. Use drivers/gpio/pca953x.c instead. -config SENSORS_MAX6875 - tristate "Maxim MAX6875 Power supply supervisor" - depends on EXPERIMENTAL - help - If you say yes here you get support for the Maxim MAX6875 - EEPROM-programmable, quad power-supply sequencer/supervisor. - - This provides an interface to program the EEPROM and reset the chip. - - This driver also supports the Maxim MAX6874 hex power-supply - sequencer/supervisor if found at a compatible address. - - This driver can also be built as a module. If so, the module - will be called max6875. - config SENSORS_TSL2550 tristate "Taos TSL2550 ambient light sensor" depends on EXPERIMENTAL diff --git a/drivers/i2c/chips/Makefile b/drivers/i2c/chips/Makefile index 55a37603718..f4680d16ee3 100644 --- a/drivers/i2c/chips/Makefile +++ b/drivers/i2c/chips/Makefile @@ -11,7 +11,6 @@ # obj-$(CONFIG_DS1682) += ds1682.o -obj-$(CONFIG_SENSORS_MAX6875) += max6875.o obj-$(CONFIG_SENSORS_PCA9539) += pca9539.o obj-$(CONFIG_SENSORS_PCF8574) += pcf8574.o obj-$(CONFIG_PCF8575) += pcf8575.o diff --git a/drivers/i2c/chips/max6875.c b/drivers/i2c/chips/max6875.c deleted file mode 100644 index 033d9d81ec8..00000000000 --- a/drivers/i2c/chips/max6875.c +++ /dev/null @@ -1,246 +0,0 @@ -/* - max6875.c - driver for MAX6874/MAX6875 - - Copyright (C) 2005 Ben Gardner - - Based on i2c/chips/eeprom.c - - The MAX6875 has a bank of registers and two banks of EEPROM. - Address ranges are defined as follows: - * 0x0000 - 0x0046 = configuration registers - * 0x8000 - 0x8046 = configuration EEPROM - * 0x8100 - 0x82FF = user EEPROM - - This driver makes the user EEPROM available for read. - - The registers & config EEPROM should be accessed via i2c-dev. - - The MAX6875 ignores the lowest address bit, so each chip responds to - two addresses - 0x50/0x51 and 0x52/0x53. - - Note that the MAX6875 uses i2c_smbus_write_byte_data() to set the read - address, so this driver is destructive if loaded for the wrong EEPROM chip. - - 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 -#include -#include -#include -#include -#include - -/* Do not scan - the MAX6875 access method will write to some EEPROM chips */ -static const unsigned short normal_i2c[] = { I2C_CLIENT_END }; - -/* Insmod parameters */ -I2C_CLIENT_INSMOD_1(max6875); - -/* The MAX6875 can only read/write 16 bytes at a time */ -#define SLICE_SIZE 16 -#define SLICE_BITS 4 - -/* USER EEPROM is at addresses 0x8100 - 0x82FF */ -#define USER_EEPROM_BASE 0x8100 -#define USER_EEPROM_SIZE 0x0200 -#define USER_EEPROM_SLICES 32 - -/* MAX6875 commands */ -#define MAX6875_CMD_BLK_READ 0x84 - -/* Each client has this additional data */ -struct max6875_data { - struct i2c_client *fake_client; - struct mutex update_lock; - - u32 valid; - u8 data[USER_EEPROM_SIZE]; - unsigned long last_updated[USER_EEPROM_SLICES]; -}; - -static void max6875_update_slice(struct i2c_client *client, int slice) -{ - struct max6875_data *data = i2c_get_clientdata(client); - int i, j, addr; - u8 *buf; - - if (slice >= USER_EEPROM_SLICES) - return; - - mutex_lock(&data->update_lock); - - buf = &data->data[slice << SLICE_BITS]; - - if (!(data->valid & (1 << slice)) || - time_after(jiffies, data->last_updated[slice])) { - - dev_dbg(&client->dev, "Starting update of slice %u\n", slice); - - data->valid &= ~(1 << slice); - - addr = USER_EEPROM_BASE + (slice << SLICE_BITS); - - /* select the eeprom address */ - if (i2c_smbus_write_byte_data(client, addr >> 8, addr & 0xFF)) { - dev_err(&client->dev, "address set failed\n"); - goto exit_up; - } - - if (i2c_check_functionality(client->adapter, - I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { - if (i2c_smbus_read_i2c_block_data(client, - MAX6875_CMD_BLK_READ, - SLICE_SIZE, - buf) != SLICE_SIZE) { - goto exit_up; - } - } else { - for (i = 0; i < SLICE_SIZE; i++) { - j = i2c_smbus_read_byte(client); - if (j < 0) { - goto exit_up; - } - buf[i] = j; - } - } - data->last_updated[slice] = jiffies; - data->valid |= (1 << slice); - } -exit_up: - mutex_unlock(&data->update_lock); -} - -static ssize_t max6875_read(struct kobject *kobj, - struct bin_attribute *bin_attr, - char *buf, loff_t off, size_t count) -{ - struct i2c_client *client = kobj_to_i2c_client(kobj); - struct max6875_data *data = i2c_get_clientdata(client); - int slice, max_slice; - - if (off > USER_EEPROM_SIZE) - return 0; - - if (off + count > USER_EEPROM_SIZE) - count = USER_EEPROM_SIZE - off; - - /* refresh slices which contain requested bytes */ - max_slice = (off + count - 1) >> SLICE_BITS; - for (slice = (off >> SLICE_BITS); slice <= max_slice; slice++) - max6875_update_slice(client, slice); - - memcpy(buf, &data->data[off], count); - - return count; -} - -static struct bin_attribute user_eeprom_attr = { - .attr = { - .name = "eeprom", - .mode = S_IRUGO, - }, - .size = USER_EEPROM_SIZE, - .read = max6875_read, -}; - -/* Return 0 if detection is successful, -ENODEV otherwise */ -static int max6875_detect(struct i2c_client *client, int kind, - struct i2c_board_info *info) -{ - struct i2c_adapter *adapter = client->adapter; - - if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WRITE_BYTE_DATA - | I2C_FUNC_SMBUS_READ_BYTE)) - return -ENODEV; - - /* Only check even addresses */ - if (client->addr & 1) - return -ENODEV; - - strlcpy(info->type, "max6875", I2C_NAME_SIZE); - - return 0; -} - -static int max6875_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - struct max6875_data *data; - int err; - - if (!(data = kzalloc(sizeof(struct max6875_data), GFP_KERNEL))) - return -ENOMEM; - - /* A fake client is created on the odd address */ - data->fake_client = i2c_new_dummy(client->adapter, client->addr + 1); - if (!data->fake_client) { - err = -ENOMEM; - goto exit_kfree; - } - - /* Init real i2c_client */ - i2c_set_clientdata(client, data); - mutex_init(&data->update_lock); - - err = sysfs_create_bin_file(&client->dev.kobj, &user_eeprom_attr); - if (err) - goto exit_remove_fake; - - return 0; - -exit_remove_fake: - i2c_unregister_device(data->fake_client); -exit_kfree: - kfree(data); - return err; -} - -static int max6875_remove(struct i2c_client *client) -{ - struct max6875_data *data = i2c_get_clientdata(client); - - i2c_unregister_device(data->fake_client); - - sysfs_remove_bin_file(&client->dev.kobj, &user_eeprom_attr); - kfree(data); - - return 0; -} - -static const struct i2c_device_id max6875_id[] = { - { "max6875", 0 }, - { } -}; - -static struct i2c_driver max6875_driver = { - .driver = { - .name = "max6875", - }, - .probe = max6875_probe, - .remove = max6875_remove, - .id_table = max6875_id, - - .detect = max6875_detect, - .address_data = &addr_data, -}; - -static int __init max6875_init(void) -{ - return i2c_add_driver(&max6875_driver); -} - -static void __exit max6875_exit(void) -{ - i2c_del_driver(&max6875_driver); -} - - -MODULE_AUTHOR("Ben Gardner "); -MODULE_DESCRIPTION("MAX6875 driver"); -MODULE_LICENSE("GPL"); - -module_init(max6875_init); -module_exit(max6875_exit); diff --git a/drivers/misc/eeprom/Kconfig b/drivers/misc/eeprom/Kconfig index 89fec052f3b..9118613af32 100644 --- a/drivers/misc/eeprom/Kconfig +++ b/drivers/misc/eeprom/Kconfig @@ -48,6 +48,20 @@ config EEPROM_LEGACY This driver can also be built as a module. If so, the module will be called eeprom. +config EEPROM_MAX6875 + tristate "Maxim MAX6874/5 power supply supervisor" + depends on I2C && EXPERIMENTAL + help + If you say yes here you get read-only support for the user EEPROM of + the Maxim MAX6874/5 EEPROM-programmable, quad power-supply + sequencer/supervisor. + + All other features of this chip should be accessed via i2c-dev. + + This driver can also be built as a module. If so, the module + will be called max6875. + + config EEPROM_93CX6 tristate "EEPROM 93CX6 support" help diff --git a/drivers/misc/eeprom/Makefile b/drivers/misc/eeprom/Makefile index 539dd8f8812..df3d68ffa9d 100644 --- a/drivers/misc/eeprom/Makefile +++ b/drivers/misc/eeprom/Makefile @@ -1,4 +1,5 @@ obj-$(CONFIG_EEPROM_AT24) += at24.o obj-$(CONFIG_EEPROM_AT25) += at25.o obj-$(CONFIG_EEPROM_LEGACY) += eeprom.o +obj-$(CONFIG_EEPROM_MAX6875) += max6875.o obj-$(CONFIG_EEPROM_93CX6) += eeprom_93cx6.o diff --git a/drivers/misc/eeprom/max6875.c b/drivers/misc/eeprom/max6875.c new file mode 100644 index 00000000000..3c0c58eed34 --- /dev/null +++ b/drivers/misc/eeprom/max6875.c @@ -0,0 +1,246 @@ +/* + max6875.c - driver for MAX6874/MAX6875 + + Copyright (C) 2005 Ben Gardner + + Based on eeprom.c + + The MAX6875 has a bank of registers and two banks of EEPROM. + Address ranges are defined as follows: + * 0x0000 - 0x0046 = configuration registers + * 0x8000 - 0x8046 = configuration EEPROM + * 0x8100 - 0x82FF = user EEPROM + + This driver makes the user EEPROM available for read. + + The registers & config EEPROM should be accessed via i2c-dev. + + The MAX6875 ignores the lowest address bit, so each chip responds to + two addresses - 0x50/0x51 and 0x52/0x53. + + Note that the MAX6875 uses i2c_smbus_write_byte_data() to set the read + address, so this driver is destructive if loaded for the wrong EEPROM chip. + + 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 +#include +#include +#include +#include +#include + +/* Do not scan - the MAX6875 access method will write to some EEPROM chips */ +static const unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +/* Insmod parameters */ +I2C_CLIENT_INSMOD_1(max6875); + +/* The MAX6875 can only read/write 16 bytes at a time */ +#define SLICE_SIZE 16 +#define SLICE_BITS 4 + +/* USER EEPROM is at addresses 0x8100 - 0x82FF */ +#define USER_EEPROM_BASE 0x8100 +#define USER_EEPROM_SIZE 0x0200 +#define USER_EEPROM_SLICES 32 + +/* MAX6875 commands */ +#define MAX6875_CMD_BLK_READ 0x84 + +/* Each client has this additional data */ +struct max6875_data { + struct i2c_client *fake_client; + struct mutex update_lock; + + u32 valid; + u8 data[USER_EEPROM_SIZE]; + unsigned long last_updated[USER_EEPROM_SLICES]; +}; + +static void max6875_update_slice(struct i2c_client *client, int slice) +{ + struct max6875_data *data = i2c_get_clientdata(client); + int i, j, addr; + u8 *buf; + + if (slice >= USER_EEPROM_SLICES) + return; + + mutex_lock(&data->update_lock); + + buf = &data->data[slice << SLICE_BITS]; + + if (!(data->valid & (1 << slice)) || + time_after(jiffies, data->last_updated[slice])) { + + dev_dbg(&client->dev, "Starting update of slice %u\n", slice); + + data->valid &= ~(1 << slice); + + addr = USER_EEPROM_BASE + (slice << SLICE_BITS); + + /* select the eeprom address */ + if (i2c_smbus_write_byte_data(client, addr >> 8, addr & 0xFF)) { + dev_err(&client->dev, "address set failed\n"); + goto exit_up; + } + + if (i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_I2C_BLOCK)) { + if (i2c_smbus_read_i2c_block_data(client, + MAX6875_CMD_BLK_READ, + SLICE_SIZE, + buf) != SLICE_SIZE) { + goto exit_up; + } + } else { + for (i = 0; i < SLICE_SIZE; i++) { + j = i2c_smbus_read_byte(client); + if (j < 0) { + goto exit_up; + } + buf[i] = j; + } + } + data->last_updated[slice] = jiffies; + data->valid |= (1 << slice); + } +exit_up: + mutex_unlock(&data->update_lock); +} + +static ssize_t max6875_read(struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct i2c_client *client = kobj_to_i2c_client(kobj); + struct max6875_data *data = i2c_get_clientdata(client); + int slice, max_slice; + + if (off > USER_EEPROM_SIZE) + return 0; + + if (off + count > USER_EEPROM_SIZE) + count = USER_EEPROM_SIZE - off; + + /* refresh slices which contain requested bytes */ + max_slice = (off + count - 1) >> SLICE_BITS; + for (slice = (off >> SLICE_BITS); slice <= max_slice; slice++) + max6875_update_slice(client, slice); + + memcpy(buf, &data->data[off], count); + + return count; +} + +static struct bin_attribute user_eeprom_attr = { + .attr = { + .name = "eeprom", + .mode = S_IRUGO, + }, + .size = USER_EEPROM_SIZE, + .read = max6875_read, +}; + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int max6875_detect(struct i2c_client *client, int kind, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WRITE_BYTE_DATA + | I2C_FUNC_SMBUS_READ_BYTE)) + return -ENODEV; + + /* Only check even addresses */ + if (client->addr & 1) + return -ENODEV; + + strlcpy(info->type, "max6875", I2C_NAME_SIZE); + + return 0; +} + +static int max6875_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct max6875_data *data; + int err; + + if (!(data = kzalloc(sizeof(struct max6875_data), GFP_KERNEL))) + return -ENOMEM; + + /* A fake client is created on the odd address */ + data->fake_client = i2c_new_dummy(client->adapter, client->addr + 1); + if (!data->fake_client) { + err = -ENOMEM; + goto exit_kfree; + } + + /* Init real i2c_client */ + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + err = sysfs_create_bin_file(&client->dev.kobj, &user_eeprom_attr); + if (err) + goto exit_remove_fake; + + return 0; + +exit_remove_fake: + i2c_unregister_device(data->fake_client); +exit_kfree: + kfree(data); + return err; +} + +static int max6875_remove(struct i2c_client *client) +{ + struct max6875_data *data = i2c_get_clientdata(client); + + i2c_unregister_device(data->fake_client); + + sysfs_remove_bin_file(&client->dev.kobj, &user_eeprom_attr); + kfree(data); + + return 0; +} + +static const struct i2c_device_id max6875_id[] = { + { "max6875", 0 }, + { } +}; + +static struct i2c_driver max6875_driver = { + .driver = { + .name = "max6875", + }, + .probe = max6875_probe, + .remove = max6875_remove, + .id_table = max6875_id, + + .detect = max6875_detect, + .address_data = &addr_data, +}; + +static int __init max6875_init(void) +{ + return i2c_add_driver(&max6875_driver); +} + +static void __exit max6875_exit(void) +{ + i2c_del_driver(&max6875_driver); +} + + +MODULE_AUTHOR("Ben Gardner "); +MODULE_DESCRIPTION("MAX6875 driver"); +MODULE_LICENSE("GPL"); + +module_init(max6875_init); +module_exit(max6875_exit); -- cgit v1.2.3-70-g09d2 From 6484eb3e2a81807722c5f28efef94d8338b7b996 Mon Sep 17 00:00:00 2001 From: Mel Gorman Date: Tue, 16 Jun 2009 15:31:54 -0700 Subject: page allocator: do not check NUMA node ID when the caller knows the node is valid Callers of alloc_pages_node() can optionally specify -1 as a node to mean "allocate from the current node". However, a number of the callers in fast paths know for a fact their node is valid. To avoid a comparison and branch, this patch adds alloc_pages_exact_node() that only checks the nid with VM_BUG_ON(). Callers that know their node is valid are then converted. Signed-off-by: Mel Gorman Reviewed-by: Christoph Lameter Reviewed-by: KOSAKI Motohiro Reviewed-by: Pekka Enberg Acked-by: Paul Mundt [for the SLOB NUMA bits] Cc: Peter Zijlstra Cc: Nick Piggin Cc: Dave Hansen Cc: Lee Schermerhorn Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- arch/ia64/hp/common/sba_iommu.c | 2 +- arch/ia64/kernel/mca.c | 3 +-- arch/ia64/kernel/uncached.c | 3 ++- arch/ia64/sn/pci/pci_dma.c | 3 ++- arch/powerpc/platforms/cell/ras.c | 4 ++-- arch/x86/kvm/vmx.c | 2 +- drivers/misc/sgi-gru/grufile.c | 2 +- drivers/misc/sgi-xp/xpc_uv.c | 2 +- include/linux/gfp.h | 9 +++++++++ include/linux/mm.h | 1 - kernel/profile.c | 8 ++++---- mm/filemap.c | 2 +- mm/hugetlb.c | 4 ++-- mm/mempolicy.c | 2 +- mm/migrate.c | 2 +- mm/slab.c | 4 ++-- mm/slob.c | 4 ++-- 17 files changed, 33 insertions(+), 24 deletions(-) (limited to 'drivers/misc') diff --git a/arch/ia64/hp/common/sba_iommu.c b/arch/ia64/hp/common/sba_iommu.c index 56ceb68eb99..fe63b2dc9d0 100644 --- a/arch/ia64/hp/common/sba_iommu.c +++ b/arch/ia64/hp/common/sba_iommu.c @@ -1131,7 +1131,7 @@ sba_alloc_coherent (struct device *dev, size_t size, dma_addr_t *dma_handle, gfp #ifdef CONFIG_NUMA { struct page *page; - page = alloc_pages_node(ioc->node == MAX_NUMNODES ? + page = alloc_pages_exact_node(ioc->node == MAX_NUMNODES ? numa_node_id() : ioc->node, flags, get_order(size)); diff --git a/arch/ia64/kernel/mca.c b/arch/ia64/kernel/mca.c index 8f33a884042..5b17bd40227 100644 --- a/arch/ia64/kernel/mca.c +++ b/arch/ia64/kernel/mca.c @@ -1829,8 +1829,7 @@ ia64_mca_cpu_init(void *cpu_data) data = mca_bootmem(); first_time = 0; } else - data = page_address(alloc_pages_node(numa_node_id(), - GFP_KERNEL, get_order(sz))); + data = __get_free_pages(GFP_KERNEL, get_order(sz)); if (!data) panic("Could not allocate MCA memory for cpu %d\n", cpu); diff --git a/arch/ia64/kernel/uncached.c b/arch/ia64/kernel/uncached.c index 8eff8c1d40a..6ba72ab42fc 100644 --- a/arch/ia64/kernel/uncached.c +++ b/arch/ia64/kernel/uncached.c @@ -98,7 +98,8 @@ static int uncached_add_chunk(struct uncached_pool *uc_pool, int nid) /* attempt to allocate a granule's worth of cached memory pages */ - page = alloc_pages_node(nid, GFP_KERNEL | __GFP_ZERO | GFP_THISNODE, + page = alloc_pages_exact_node(nid, + GFP_KERNEL | __GFP_ZERO | GFP_THISNODE, IA64_GRANULE_SHIFT-PAGE_SHIFT); if (!page) { mutex_unlock(&uc_pool->add_chunk_mutex); diff --git a/arch/ia64/sn/pci/pci_dma.c b/arch/ia64/sn/pci/pci_dma.c index d876423e4e7..98b684928e1 100644 --- a/arch/ia64/sn/pci/pci_dma.c +++ b/arch/ia64/sn/pci/pci_dma.c @@ -90,7 +90,8 @@ static void *sn_dma_alloc_coherent(struct device *dev, size_t size, */ node = pcibus_to_node(pdev->bus); if (likely(node >=0)) { - struct page *p = alloc_pages_node(node, flags, get_order(size)); + struct page *p = alloc_pages_exact_node(node, + flags, get_order(size)); if (likely(p)) cpuaddr = page_address(p); diff --git a/arch/powerpc/platforms/cell/ras.c b/arch/powerpc/platforms/cell/ras.c index 296b5268754..5e0a191764f 100644 --- a/arch/powerpc/platforms/cell/ras.c +++ b/arch/powerpc/platforms/cell/ras.c @@ -122,8 +122,8 @@ static int __init cbe_ptcal_enable_on_node(int nid, int order) area->nid = nid; area->order = order; - area->pages = alloc_pages_node(area->nid, GFP_KERNEL | GFP_THISNODE, - area->order); + area->pages = alloc_pages_exact_node(area->nid, GFP_KERNEL|GFP_THISNODE, + area->order); if (!area->pages) { printk(KERN_WARNING "%s: no page on node %d\n", diff --git a/arch/x86/kvm/vmx.c b/arch/x86/kvm/vmx.c index 32d6ae8fb60..e770bf349ec 100644 --- a/arch/x86/kvm/vmx.c +++ b/arch/x86/kvm/vmx.c @@ -1277,7 +1277,7 @@ static struct vmcs *alloc_vmcs_cpu(int cpu) struct page *pages; struct vmcs *vmcs; - pages = alloc_pages_node(node, GFP_KERNEL, vmcs_config.order); + pages = alloc_pages_exact_node(node, GFP_KERNEL, vmcs_config.order); if (!pages) return NULL; vmcs = page_address(pages); diff --git a/drivers/misc/sgi-gru/grufile.c b/drivers/misc/sgi-gru/grufile.c index bbefe77c67a..3ce2920e2bf 100644 --- a/drivers/misc/sgi-gru/grufile.c +++ b/drivers/misc/sgi-gru/grufile.c @@ -302,7 +302,7 @@ static int gru_init_tables(unsigned long gru_base_paddr, void *gru_base_vaddr) pnode = uv_node_to_pnode(nid); if (bid < 0 || gru_base[bid]) continue; - page = alloc_pages_node(nid, GFP_KERNEL, order); + page = alloc_pages_exact_node(nid, GFP_KERNEL, order); if (!page) goto fail; gru_base[bid] = page_address(page); diff --git a/drivers/misc/sgi-xp/xpc_uv.c b/drivers/misc/sgi-xp/xpc_uv.c index 9172fcdee4e..c76677afda1 100644 --- a/drivers/misc/sgi-xp/xpc_uv.c +++ b/drivers/misc/sgi-xp/xpc_uv.c @@ -232,7 +232,7 @@ xpc_create_gru_mq_uv(unsigned int mq_size, int cpu, char *irq_name, mq->mmr_blade = uv_cpu_to_blade_id(cpu); nid = cpu_to_node(cpu); - page = alloc_pages_node(nid, GFP_KERNEL | __GFP_ZERO | GFP_THISNODE, + page = alloc_pages_exact_node(nid, GFP_KERNEL | __GFP_ZERO | GFP_THISNODE, pg_order); if (page == NULL) { dev_err(xpc_part, "xpc_create_gru_mq_uv() failed to alloc %d " diff --git a/include/linux/gfp.h b/include/linux/gfp.h index c2d3fe03b5d..4efa33088a8 100644 --- a/include/linux/gfp.h +++ b/include/linux/gfp.h @@ -5,6 +5,7 @@ #include #include #include +#include struct vm_area_struct; @@ -192,6 +193,14 @@ static inline struct page *alloc_pages_node(int nid, gfp_t gfp_mask, return __alloc_pages(gfp_mask, order, node_zonelist(nid, gfp_mask)); } +static inline struct page *alloc_pages_exact_node(int nid, gfp_t gfp_mask, + unsigned int order) +{ + VM_BUG_ON(nid < 0 || nid >= MAX_NUMNODES); + + return __alloc_pages(gfp_mask, order, node_zonelist(nid, gfp_mask)); +} + #ifdef CONFIG_NUMA extern struct page *alloc_pages_current(gfp_t gfp_mask, unsigned order); diff --git a/include/linux/mm.h b/include/linux/mm.h index a880161a385..7b548e7cfbd 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -7,7 +7,6 @@ #include #include -#include #include #include #include diff --git a/kernel/profile.c b/kernel/profile.c index 28cf26ad2d2..69911b5745e 100644 --- a/kernel/profile.c +++ b/kernel/profile.c @@ -365,7 +365,7 @@ static int __cpuinit profile_cpu_callback(struct notifier_block *info, node = cpu_to_node(cpu); per_cpu(cpu_profile_flip, cpu) = 0; if (!per_cpu(cpu_profile_hits, cpu)[1]) { - page = alloc_pages_node(node, + page = alloc_pages_exact_node(node, GFP_KERNEL | __GFP_ZERO, 0); if (!page) @@ -373,7 +373,7 @@ static int __cpuinit profile_cpu_callback(struct notifier_block *info, per_cpu(cpu_profile_hits, cpu)[1] = page_address(page); } if (!per_cpu(cpu_profile_hits, cpu)[0]) { - page = alloc_pages_node(node, + page = alloc_pages_exact_node(node, GFP_KERNEL | __GFP_ZERO, 0); if (!page) @@ -564,14 +564,14 @@ static int create_hash_tables(void) int node = cpu_to_node(cpu); struct page *page; - page = alloc_pages_node(node, + page = alloc_pages_exact_node(node, GFP_KERNEL | __GFP_ZERO | GFP_THISNODE, 0); if (!page) goto out_cleanup; per_cpu(cpu_profile_hits, cpu)[1] = (struct profile_hit *)page_address(page); - page = alloc_pages_node(node, + page = alloc_pages_exact_node(node, GFP_KERNEL | __GFP_ZERO | GFP_THISNODE, 0); if (!page) diff --git a/mm/filemap.c b/mm/filemap.c index 6846a902f5c..22396713feb 100644 --- a/mm/filemap.c +++ b/mm/filemap.c @@ -521,7 +521,7 @@ struct page *__page_cache_alloc(gfp_t gfp) { if (cpuset_do_page_mem_spread()) { int n = cpuset_mem_spread_node(); - return alloc_pages_node(n, gfp, 0); + return alloc_pages_exact_node(n, gfp, 0); } return alloc_pages(gfp, 0); } diff --git a/mm/hugetlb.c b/mm/hugetlb.c index e83ad2c9228..2f8241f300f 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -630,7 +630,7 @@ static struct page *alloc_fresh_huge_page_node(struct hstate *h, int nid) if (h->order >= MAX_ORDER) return NULL; - page = alloc_pages_node(nid, + page = alloc_pages_exact_node(nid, htlb_alloc_mask|__GFP_COMP|__GFP_THISNODE| __GFP_REPEAT|__GFP_NOWARN, huge_page_order(h)); @@ -649,7 +649,7 @@ static struct page *alloc_fresh_huge_page_node(struct hstate *h, int nid) * Use a helper variable to find the next node and then * copy it back to hugetlb_next_nid afterwards: * otherwise there's a window in which a racer might - * pass invalid nid MAX_NUMNODES to alloc_pages_node. + * pass invalid nid MAX_NUMNODES to alloc_pages_exact_node. * But we don't need to use a spin_lock here: it really * doesn't matter if occasionally a racer chooses the * same nid as we do. Move nid forward in the mask even diff --git a/mm/mempolicy.c b/mm/mempolicy.c index 46bdf9ddf2b..e08e2c4da63 100644 --- a/mm/mempolicy.c +++ b/mm/mempolicy.c @@ -803,7 +803,7 @@ static void migrate_page_add(struct page *page, struct list_head *pagelist, static struct page *new_node_page(struct page *page, unsigned long node, int **x) { - return alloc_pages_node(node, GFP_HIGHUSER_MOVABLE, 0); + return alloc_pages_exact_node(node, GFP_HIGHUSER_MOVABLE, 0); } /* diff --git a/mm/migrate.c b/mm/migrate.c index 068655d8f88..5a24923e7fd 100644 --- a/mm/migrate.c +++ b/mm/migrate.c @@ -802,7 +802,7 @@ static struct page *new_page_node(struct page *p, unsigned long private, *result = &pm->status; - return alloc_pages_node(pm->node, + return alloc_pages_exact_node(pm->node, GFP_HIGHUSER_MOVABLE | GFP_THISNODE, 0); } diff --git a/mm/slab.c b/mm/slab.c index 18e3164de09..bb3254c95cd 100644 --- a/mm/slab.c +++ b/mm/slab.c @@ -1707,7 +1707,7 @@ static void *kmem_getpages(struct kmem_cache *cachep, gfp_t flags, int nodeid) if (cachep->flags & SLAB_RECLAIM_ACCOUNT) flags |= __GFP_RECLAIMABLE; - page = alloc_pages_node(nodeid, flags, cachep->gfporder); + page = alloc_pages_exact_node(nodeid, flags, cachep->gfporder); if (!page) return NULL; @@ -3261,7 +3261,7 @@ retry: if (local_flags & __GFP_WAIT) local_irq_enable(); kmem_flagcheck(cache, flags); - obj = kmem_getpages(cache, local_flags, -1); + obj = kmem_getpages(cache, local_flags, numa_node_id()); if (local_flags & __GFP_WAIT) local_irq_disable(); if (obj) { diff --git a/mm/slob.c b/mm/slob.c index 12f26149992..64f6db1943b 100644 --- a/mm/slob.c +++ b/mm/slob.c @@ -46,7 +46,7 @@ * NUMA support in SLOB is fairly simplistic, pushing most of the real * logic down to the page allocator, and simply doing the node accounting * on the upper levels. In the event that a node id is explicitly - * provided, alloc_pages_node() with the specified node id is used + * provided, alloc_pages_exact_node() with the specified node id is used * instead. The common case (or when the node id isn't explicitly provided) * will default to the current node, as per numa_node_id(). * @@ -244,7 +244,7 @@ static void *slob_new_pages(gfp_t gfp, int order, int node) #ifdef CONFIG_NUMA if (node != -1) - page = alloc_pages_node(node, gfp, order); + page = alloc_pages_exact_node(node, gfp, order); else #endif page = alloc_pages(gfp, order); -- cgit v1.2.3-70-g09d2 From cd1334f03f7b799bc6893b511daf2080e8f73863 Mon Sep 17 00:00:00 2001 From: Jack Steiner Date: Wed, 17 Jun 2009 16:28:19 -0700 Subject: gru: bug fixes for GRU exception handling Bug fixes for GRU exception handling. Additional fields from the CBR must be returned to the user to allow the user to correctly diagnose GRU exceptions. Handle endcase in TFH TLB miss handling. Verify that TFH actually indicates a pending exception. Signed-off-by: Jack Steiner Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/misc/sgi-gru/gru_instructions.h | 20 +++++++++++++++++++- drivers/misc/sgi-gru/grufault.c | 25 +++++++++++++++++++++---- drivers/misc/sgi-gru/gruhandles.h | 23 ++++------------------- drivers/misc/sgi-gru/grumain.c | 3 +++ drivers/misc/sgi-gru/gruprocfs.c | 5 +++-- drivers/misc/sgi-gru/grutables.h | 2 ++ 6 files changed, 52 insertions(+), 26 deletions(-) (limited to 'drivers/misc') diff --git a/drivers/misc/sgi-gru/gru_instructions.h b/drivers/misc/sgi-gru/gru_instructions.h index 3fde33c1e8f..2feb885ca96 100644 --- a/drivers/misc/sgi-gru/gru_instructions.h +++ b/drivers/misc/sgi-gru/gru_instructions.h @@ -81,6 +81,8 @@ struct control_block_extended_exc_detail { int exopc; long exceptdet0; int exceptdet1; + int cbrstate; + int cbrexecstatus; }; /* @@ -107,7 +109,8 @@ struct gru_instruction_bits { unsigned char reserved2: 2; unsigned char istatus: 2; unsigned char isubstatus:4; - unsigned char reserved3: 2; + unsigned char reserved3: 1; + unsigned char tlb_fault_color: 1; /* DW 1 */ unsigned long idef4; /* 42 bits: TRi1, BufSize */ /* DW 2-6 */ @@ -253,6 +256,21 @@ struct gru_instruction { #define CBE_CAUSE_RESPONSE_DATA_ERROR (1 << 16) #define CBE_CAUSE_PROTOCOL_STATE_DATA_ERROR (1 << 17) +/* CBE cbrexecstatus bits */ +#define CBR_EXS_ABORT_OCC_BIT 0 +#define CBR_EXS_INT_OCC_BIT 1 +#define CBR_EXS_PENDING_BIT 2 +#define CBR_EXS_QUEUED_BIT 3 +#define CBR_EXS_TLBHW_BIT 4 +#define CBR_EXS_EXCEPTION_BIT 5 + +#define CBR_EXS_ABORT_OCC (1 << CBR_EXS_ABORT_OCC_BIT) +#define CBR_EXS_INT_OCC (1 << CBR_EXS_INT_OCC_BIT) +#define CBR_EXS_PENDING (1 << CBR_EXS_PENDING_BIT) +#define CBR_EXS_QUEUED (1 << CBR_EXS_QUEUED_BIT) +#define CBR_EXS_TLBHW (1 << CBR_EXS_TLBHW_BIT) +#define CBR_EXS_EXCEPTION (1 << CBR_EXS_EXCEPTION_BIT) + /* * Exceptions are retried for the following cases. If any OTHER bits are set * in ecause, the exception is not retryable. diff --git a/drivers/misc/sgi-gru/grufault.c b/drivers/misc/sgi-gru/grufault.c index ab118558552..4089f862aa2 100644 --- a/drivers/misc/sgi-gru/grufault.c +++ b/drivers/misc/sgi-gru/grufault.c @@ -334,6 +334,8 @@ static int gru_try_dropin(struct gru_thread_state *gts, * Might be a hardware race OR a stupid user. Ignore FMM because FMM * is a transient state. */ + if (tfh->status != TFHSTATUS_EXCEPTION) + goto failnoexception; if (tfh->state == TFHSTATE_IDLE) goto failidle; if (tfh->state == TFHSTATE_MISS_FMM && cb) @@ -401,8 +403,17 @@ failfmm: gru_dbg(grudev, "FAILED fmm tfh: 0x%p, state %d\n", tfh, tfh->state); return 0; +failnoexception: + /* TFH status did not show exception pending */ + gru_flush_cache(tfh); + if (cb) + gru_flush_cache(cb); + STAT(tlb_dropin_fail_no_exception); + gru_dbg(grudev, "FAILED non-exception tfh: 0x%p, status %d, state %d\n", tfh, tfh->status, tfh->state); + return 0; + failidle: - /* TFH was idle - no miss pending */ + /* TFH state was idle - no miss pending */ gru_flush_cache(tfh); if (cb) gru_flush_cache(cb); @@ -472,7 +483,8 @@ irqreturn_t gru_intr(int irq, void *dev_id) * This is running in interrupt context. Trylock the mmap_sem. * If it fails, retry the fault in user context. */ - if (down_read_trylock(>s->ts_mm->mmap_sem)) { + if (!gts->ts_force_cch_reload && + down_read_trylock(>s->ts_mm->mmap_sem)) { gru_try_dropin(gts, tfh, NULL); up_read(>s->ts_mm->mmap_sem); } else { @@ -595,14 +607,19 @@ int gru_get_exception_detail(unsigned long arg) excdet.ecause = cbe->ecause; excdet.exceptdet0 = cbe->idef1upd; excdet.exceptdet1 = cbe->idef3upd; + excdet.cbrstate = cbe->cbrstate; + excdet.cbrexecstatus = cbe->cbrexecstatus; ret = 0; } else { ret = -EAGAIN; } gru_unlock_gts(gts); - gru_dbg(grudev, "address 0x%lx, ecause 0x%x\n", excdet.cb, - excdet.ecause); + gru_dbg(grudev, + "cb 0x%lx, op %d, exopc %d, cbrstate %d, cbrexecstatus 0x%x, ecause 0x%x, " + "exdet0 0x%lx, exdet1 0x%x\n", + excdet.cb, excdet.opc, excdet.exopc, excdet.cbrstate, excdet.cbrexecstatus, + excdet.ecause, excdet.exceptdet0, excdet.exceptdet1); if (!ret && copy_to_user((void __user *)arg, &excdet, sizeof(excdet))) ret = -EFAULT; return ret; diff --git a/drivers/misc/sgi-gru/gruhandles.h b/drivers/misc/sgi-gru/gruhandles.h index 1ed74d7508c..03b76a1993c 100644 --- a/drivers/misc/sgi-gru/gruhandles.h +++ b/drivers/misc/sgi-gru/gruhandles.h @@ -251,15 +251,14 @@ struct gru_tlb_fault_handle { unsigned int fill1:9; unsigned int status:2; - unsigned int fill2:1; - unsigned int color:1; + unsigned int fill2:2; unsigned int state:3; unsigned int fill3:1; - unsigned int cause:7; /* DW 0 - high 32 */ + unsigned int cause:7; unsigned int fill4:1; - unsigned int indexway:12; + unsigned int indexway:12; /* DW 0 - high 32 */ unsigned int fill5:4; unsigned int ctxnum:4; @@ -457,21 +456,7 @@ enum gru_cbr_state { CBRSTATE_BUSY_INTERRUPT, }; -/* CBE cbrexecstatus bits */ -#define CBR_EXS_ABORT_OCC_BIT 0 -#define CBR_EXS_INT_OCC_BIT 1 -#define CBR_EXS_PENDING_BIT 2 -#define CBR_EXS_QUEUED_BIT 3 -#define CBR_EXS_TLBHW_BIT 4 -#define CBR_EXS_EXCEPTION_BIT 5 - -#define CBR_EXS_ABORT_OCC (1 << CBR_EXS_ABORT_OCC_BIT) -#define CBR_EXS_INT_OCC (1 << CBR_EXS_INT_OCC_BIT) -#define CBR_EXS_PENDING (1 << CBR_EXS_PENDING_BIT) -#define CBR_EXS_QUEUED (1 << CBR_EXS_QUEUED_BIT) -#define CBR_EXS_TLBHW (1 << CBR_EXS_TLBHW_BIT) -#define CBR_EXS_EXCEPTION (1 << CBR_EXS_EXCEPTION_BIT) - +/* CBE cbrexecstatus bits - defined in gru_instructions.h*/ /* CBE ecause bits - defined in gru_instructions.h */ /* diff --git a/drivers/misc/sgi-gru/grumain.c b/drivers/misc/sgi-gru/grumain.c index ec3f7a17d22..374af38862e 100644 --- a/drivers/misc/sgi-gru/grumain.c +++ b/drivers/misc/sgi-gru/grumain.c @@ -599,6 +599,9 @@ int gru_update_cch(struct gru_thread_state *gts, int force_unload) cch->sizeavail[i] = gts->ts_sizeavail; gts->ts_tlb_int_select = gru_cpu_fault_map_id(); cch->tlb_int_select = gru_cpu_fault_map_id(); + cch->tfm_fault_bit_enable = + (gts->ts_user_options == GRU_OPT_MISS_FMM_POLL + || gts->ts_user_options == GRU_OPT_MISS_FMM_INTR); } else { for (i = 0; i < 8; i++) cch->asid[i] = 0; diff --git a/drivers/misc/sgi-gru/gruprocfs.c b/drivers/misc/sgi-gru/gruprocfs.c index ee74821b171..c46c1c5f0c7 100644 --- a/drivers/misc/sgi-gru/gruprocfs.c +++ b/drivers/misc/sgi-gru/gruprocfs.c @@ -84,6 +84,8 @@ static int statistics_show(struct seq_file *s, void *p) printstat(s, tlb_dropin_fail_range_active); printstat(s, tlb_dropin_fail_idle); printstat(s, tlb_dropin_fail_fmm); + printstat(s, tlb_dropin_fail_no_exception); + printstat(s, tlb_dropin_fail_no_exception_war); printstat(s, mmu_invalidate_range); printstat(s, mmu_invalidate_page); printstat(s, mmu_clear_flush_young); @@ -158,8 +160,7 @@ static ssize_t options_write(struct file *file, const char __user *userbuf, unsigned long val; char buf[80]; - if (copy_from_user - (buf, userbuf, count < sizeof(buf) ? count : sizeof(buf))) + if (strncpy_from_user(buf, userbuf, sizeof(buf) - 1) < 0) return -EFAULT; buf[count - 1] = '\0'; if (!strict_strtoul(buf, 10, &val)) diff --git a/drivers/misc/sgi-gru/grutables.h b/drivers/misc/sgi-gru/grutables.h index bf1eeb7553e..ebf6183c163 100644 --- a/drivers/misc/sgi-gru/grutables.h +++ b/drivers/misc/sgi-gru/grutables.h @@ -207,6 +207,8 @@ struct gru_stats_s { atomic_long_t tlb_dropin_fail_range_active; atomic_long_t tlb_dropin_fail_idle; atomic_long_t tlb_dropin_fail_fmm; + atomic_long_t tlb_dropin_fail_no_exception; + atomic_long_t tlb_dropin_fail_no_exception_war; atomic_long_t mmu_invalidate_range; atomic_long_t mmu_invalidate_page; atomic_long_t mmu_clear_flush_young; -- cgit v1.2.3-70-g09d2 From 9cc9b056ea51608788609d7e26c7db55ef81bb2e Mon Sep 17 00:00:00 2001 From: Jack Steiner Date: Wed, 17 Jun 2009 16:28:19 -0700 Subject: gru: dump chiplet state Add support for dumpping the state of an entire GRU chiplet. Signed-off-by: Jack Steiner Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/misc/sgi-gru/Makefile | 2 +- drivers/misc/sgi-gru/grufile.c | 3 + drivers/misc/sgi-gru/grukdump.c | 218 +++++++++++++++++++++++++++++++++++++++ drivers/misc/sgi-gru/grulib.h | 33 ++++++ drivers/misc/sgi-gru/grutables.h | 12 +++ 5 files changed, 267 insertions(+), 1 deletion(-) create mode 100644 drivers/misc/sgi-gru/grukdump.c (limited to 'drivers/misc') diff --git a/drivers/misc/sgi-gru/Makefile b/drivers/misc/sgi-gru/Makefile index bcd8136d2f9..7c4c306dfa8 100644 --- a/drivers/misc/sgi-gru/Makefile +++ b/drivers/misc/sgi-gru/Makefile @@ -3,5 +3,5 @@ ifdef CONFIG_SGI_GRU_DEBUG endif obj-$(CONFIG_SGI_GRU) := gru.o -gru-y := grufile.o grumain.o grufault.o grutlbpurge.o gruprocfs.o grukservices.o gruhandles.o +gru-y := grufile.o grumain.o grufault.o grutlbpurge.o gruprocfs.o grukservices.o gruhandles.o grukdump.o diff --git a/drivers/misc/sgi-gru/grufile.c b/drivers/misc/sgi-gru/grufile.c index 3ce2920e2bf..9e6da46eeb0 100644 --- a/drivers/misc/sgi-gru/grufile.c +++ b/drivers/misc/sgi-gru/grufile.c @@ -255,6 +255,9 @@ static long gru_file_unlocked_ioctl(struct file *file, unsigned int req, case GRU_GET_CONFIG_INFO: err = gru_get_config_info(arg); break; + case GRU_DUMP_CHIPLET_STATE: + err = gru_dump_chiplet_request(arg); + break; } return err; } diff --git a/drivers/misc/sgi-gru/grukdump.c b/drivers/misc/sgi-gru/grukdump.c new file mode 100644 index 00000000000..27e00931a7b --- /dev/null +++ b/drivers/misc/sgi-gru/grukdump.c @@ -0,0 +1,218 @@ +/* + * SN Platform GRU Driver + * + * Dump GRU State + * + * 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 + * for more details. + * + * Copyright (c) 2008 Silicon Graphics, Inc. All Rights Reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "gru.h" +#include "grutables.h" +#include "gruhandles.h" +#include "grulib.h" + +#define CCH_LOCK_ATTEMPTS 10 + +static int gru_user_copy_handle(void __user **dp, void *s) +{ + if (copy_to_user(dp, s, GRU_HANDLE_BYTES)) + return -1; + *dp += GRU_HANDLE_BYTES; + return 0; +} + +static int gru_dump_context_data(void *grubase, + struct gru_context_configuration_handle *cch, + void __user *ubuf, int ctxnum, int dsrcnt) +{ + void *cb, *cbe, *tfh, *gseg; + int i, scr; + + gseg = grubase + ctxnum * GRU_GSEG_STRIDE; + cb = gseg + GRU_CB_BASE; + cbe = grubase + GRU_CBE_BASE; + tfh = grubase + GRU_TFH_BASE; + + for_each_cbr_in_allocation_map(i, &cch->cbr_allocation_map, scr) { + if (gru_user_copy_handle(&ubuf, cb)) + goto fail; + if (gru_user_copy_handle(&ubuf, tfh + i * GRU_HANDLE_STRIDE)) + goto fail; + if (gru_user_copy_handle(&ubuf, cbe + i * GRU_HANDLE_STRIDE)) + goto fail; + cb += GRU_HANDLE_STRIDE; + } + if (dsrcnt) + memcpy(ubuf, gseg + GRU_DS_BASE, dsrcnt * GRU_HANDLE_STRIDE); + return 0; + +fail: + return -EFAULT; +} + +static int gru_dump_tfm(struct gru_state *gru, + void __user *ubuf, void __user *ubufend) +{ + struct gru_tlb_fault_map *tfm; + int i, ret, bytes; + + bytes = GRU_NUM_TFM * GRU_CACHE_LINE_BYTES; + if (bytes > ubufend - ubuf) + ret = -EFBIG; + + for (i = 0; i < GRU_NUM_TFM; i++) { + tfm = get_tfm(gru->gs_gru_base_vaddr, i); + if (gru_user_copy_handle(&ubuf, tfm)) + goto fail; + } + return GRU_NUM_TFM * GRU_CACHE_LINE_BYTES; + +fail: + return -EFAULT; +} + +static int gru_dump_tgh(struct gru_state *gru, + void __user *ubuf, void __user *ubufend) +{ + struct gru_tlb_global_handle *tgh; + int i, ret, bytes; + + bytes = GRU_NUM_TGH * GRU_CACHE_LINE_BYTES; + if (bytes > ubufend - ubuf) + ret = -EFBIG; + + for (i = 0; i < GRU_NUM_TGH; i++) { + tgh = get_tgh(gru->gs_gru_base_vaddr, i); + if (gru_user_copy_handle(&ubuf, tgh)) + goto fail; + } + return GRU_NUM_TGH * GRU_CACHE_LINE_BYTES; + +fail: + return -EFAULT; +} + +static int gru_dump_context(struct gru_state *gru, int ctxnum, + void __user *ubuf, void __user *ubufend, char data_opt, + char lock_cch) +{ + struct gru_dump_context_header hdr; + struct gru_dump_context_header __user *uhdr = ubuf; + struct gru_context_configuration_handle *cch; + struct gru_thread_state *gts; + int try, cch_locked, cbrcnt = 0, dsrcnt = 0, bytes = 0, ret = 0; + void *grubase; + + memset(&hdr, 0, sizeof(hdr)); + grubase = gru->gs_gru_base_vaddr; + cch = get_cch(grubase, ctxnum); + for (try = 0; try < CCH_LOCK_ATTEMPTS; try++) { + cch_locked = trylock_cch_handle(cch); + if (cch_locked) + break; + msleep(1); + } + + ubuf += sizeof(hdr); + if (gru_user_copy_handle(&ubuf, cch)) + goto fail; + bytes = sizeof(hdr) + GRU_CACHE_LINE_BYTES; + + if (cch_locked || !lock_cch) { + gts = gru->gs_gts[ctxnum]; + if (gts) { + hdr.pid = gts->ts_tgid_owner; + hdr.vaddr = gts->ts_vma->vm_start; + } + if (cch->state != CCHSTATE_INACTIVE) { + cbrcnt = hweight64(cch->cbr_allocation_map) * + GRU_CBR_AU_SIZE; + dsrcnt = data_opt ? hweight32(cch->dsr_allocation_map) * + GRU_DSR_AU_CL : 0; + } + bytes += (3 * cbrcnt + dsrcnt) * GRU_CACHE_LINE_BYTES; + if (bytes > ubufend - ubuf) + ret = -EFBIG; + else + ret = gru_dump_context_data(grubase, cch, ubuf, ctxnum, + dsrcnt); + + } + if (cch_locked) + unlock_cch_handle(cch); + if (ret) + return ret; + + hdr.magic = GRU_DUMP_MAGIC; + hdr.ctxnum = ctxnum; + hdr.cbrcnt = cbrcnt; + hdr.dsrcnt = dsrcnt; + hdr.cch_locked = cch_locked; + if (!ret && copy_to_user((void __user *)uhdr, &hdr, sizeof(hdr))) + ret = -EFAULT; + + return ret ? ret : bytes; + +fail: + unlock_cch_handle(cch); + return -EFAULT; +} + +int gru_dump_chiplet_request(unsigned long arg) +{ + struct gru_state *gru; + struct gru_dump_chiplet_state_req req; + void __user *ubuf; + void __user *ubufend; + int ctxnum, ret, cnt = 0; + + if (copy_from_user(&req, (void __user *)arg, sizeof(req))) + return -EFAULT; + + /* Currently, only dump by gid is implemented */ + if (req.gid >= gru_max_gids || req.gid < 0) + return -EINVAL; + + gru = GID_TO_GRU(req.gid); + ubuf = req.buf; + ubufend = req.buf + req.buflen; + + ret = gru_dump_tfm(gru, ubuf, ubufend); + if (ret < 0) + goto fail; + ubuf += ret; + + ret = gru_dump_tgh(gru, ubuf, ubufend); + if (ret < 0) + goto fail; + ubuf += ret; + + for (ctxnum = 0; ctxnum < GRU_NUM_CCH; ctxnum++) { + if (req.ctxnum == ctxnum || req.ctxnum < 0) { + ret = gru_dump_context(gru, ctxnum, ubuf, ubufend, + req.data_opt, req.lock_cch); + if (ret < 0) + goto fail; + ubuf += ret; + cnt++; + } + } + + if (copy_to_user((void __user *)arg, &req, sizeof(req))) + return -EFAULT; + return cnt; + +fail: + return ret; +} diff --git a/drivers/misc/sgi-gru/grulib.h b/drivers/misc/sgi-gru/grulib.h index e56e196a699..6ab872665e7 100644 --- a/drivers/misc/sgi-gru/grulib.h +++ b/drivers/misc/sgi-gru/grulib.h @@ -47,6 +47,9 @@ /* For fetching GRU chiplet status */ #define GRU_GET_CHIPLET_STATUS _IOWR(GRU_IOCTL_NUM, 10, void *) +/* For dumpping GRU chiplet state */ +#define GRU_DUMP_CHIPLET_STATE _IOWR(GRU_IOCTL_NUM, 11, void *) + /* For user TLB flushing (primarily for tests) */ #define GRU_USER_FLUSH_TLB _IOWR(GRU_IOCTL_NUM, 50, void *) @@ -83,6 +86,36 @@ struct gru_flush_tlb_req { size_t len; }; +/* + * Structure used to pass TLB flush parameters to the driver + */ +enum {dcs_pid, dcs_gid}; +struct gru_dump_chiplet_state_req { + unsigned int op; + int gid; + int ctxnum; + char data_opt; + char lock_cch; + pid_t pid; + void *buf; + size_t buflen; + /* ---- output --- */ + unsigned int num_contexts; +}; + +#define GRU_DUMP_MAGIC 0x3474ab6c +struct gru_dump_context_header { + unsigned int magic; + unsigned char gid; + unsigned char ctxnum; + unsigned char cbrcnt; + unsigned char dsrcnt; + pid_t pid; + unsigned long vaddr; + int cch_locked; + unsigned long data[0]; +}; + /* * GRU configuration info (temp - for testing) */ diff --git a/drivers/misc/sgi-gru/grutables.h b/drivers/misc/sgi-gru/grutables.h index ebf6183c163..d768f54dc25 100644 --- a/drivers/misc/sgi-gru/grutables.h +++ b/drivers/misc/sgi-gru/grutables.h @@ -554,6 +554,12 @@ struct gru_blade_state { /* Lock hierarchy checking enabled only in emulator */ +/* 0 = lock failed, 1 = locked */ +static inline int __trylock_handle(void *h) +{ + return !test_and_set_bit(1, h); +} + static inline void __lock_handle(void *h) { while (test_and_set_bit(1, h)) @@ -565,6 +571,11 @@ static inline void __unlock_handle(void *h) clear_bit(1, h); } +static inline int trylock_cch_handle(struct gru_context_configuration_handle *cch) +{ + return __trylock_handle(cch); +} + static inline void lock_cch_handle(struct gru_context_configuration_handle *cch) { __lock_handle(cch); @@ -606,6 +617,7 @@ extern void gts_drop(struct gru_thread_state *gts); extern void gru_tgh_flush_init(struct gru_state *gru); extern int gru_kservices_init(struct gru_state *gru); extern void gru_kservices_exit(struct gru_state *gru); +extern int gru_dump_chiplet_request(unsigned long arg); extern irqreturn_t gru_intr(int irq, void *dev_id); extern int gru_handle_user_call_os(unsigned long address); extern int gru_user_flush_tlb(unsigned long arg); -- cgit v1.2.3-70-g09d2 From 364b76df80f62cee1b66e871df2f69db6e3d3d9e Mon Sep 17 00:00:00 2001 From: Jack Steiner Date: Wed, 17 Jun 2009 16:28:20 -0700 Subject: gru: dynamic allocation of kernel contexts Change the interface to gru_alloc_gts() so that it can be used to allocate GRU contexts for kernel threads. Kernel threads do not have vdata structures for the GRU contexts. The GRU resource count are now passed explicitly instead of inside the vdata structure. Signed-off-by: Jack Steiner Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/misc/sgi-gru/grumain.c | 60 +++++++++++++++++++++------------------- drivers/misc/sgi-gru/grutables.h | 2 ++ 2 files changed, 33 insertions(+), 29 deletions(-) (limited to 'drivers/misc') diff --git a/drivers/misc/sgi-gru/grumain.c b/drivers/misc/sgi-gru/grumain.c index 374af38862e..14baabc79da 100644 --- a/drivers/misc/sgi-gru/grumain.c +++ b/drivers/misc/sgi-gru/grumain.c @@ -299,15 +299,13 @@ static struct gru_thread_state *gru_find_current_gts_nolock(struct gru_vma_data /* * Allocate a thread state structure. */ -static struct gru_thread_state *gru_alloc_gts(struct vm_area_struct *vma, - struct gru_vma_data *vdata, - int tsid) +struct gru_thread_state *gru_alloc_gts(struct vm_area_struct *vma, + int cbr_au_count, int dsr_au_count, int options, int tsid) { struct gru_thread_state *gts; int bytes; - bytes = DSR_BYTES(vdata->vd_dsr_au_count) + - CBR_BYTES(vdata->vd_cbr_au_count); + bytes = DSR_BYTES(dsr_au_count) + CBR_BYTES(cbr_au_count); bytes += sizeof(struct gru_thread_state); gts = kzalloc(bytes, GFP_KERNEL); if (!gts) @@ -316,21 +314,22 @@ static struct gru_thread_state *gru_alloc_gts(struct vm_area_struct *vma, STAT(gts_alloc); atomic_set(>s->ts_refcnt, 1); mutex_init(>s->ts_ctxlock); - gts->ts_cbr_au_count = vdata->vd_cbr_au_count; - gts->ts_dsr_au_count = vdata->vd_dsr_au_count; - gts->ts_user_options = vdata->vd_user_options; + gts->ts_cbr_au_count = cbr_au_count; + gts->ts_dsr_au_count = dsr_au_count; + gts->ts_user_options = options; gts->ts_tsid = tsid; - gts->ts_user_options = vdata->vd_user_options; gts->ts_ctxnum = NULLCTX; - gts->ts_mm = current->mm; - gts->ts_vma = vma; gts->ts_tlb_int_select = -1; - gts->ts_gms = gru_register_mmu_notifier(); gts->ts_sizeavail = GRU_SIZEAVAIL(PAGE_SHIFT); - if (!gts->ts_gms) - goto err; + if (vma) { + gts->ts_mm = current->mm; + gts->ts_vma = vma; + gts->ts_gms = gru_register_mmu_notifier(); + if (!gts->ts_gms) + goto err; + } - gru_dbg(grudev, "alloc vdata %p, new gts %p\n", vdata, gts); + gru_dbg(grudev, "alloc gts %p\n", gts); return gts; err: @@ -381,7 +380,8 @@ struct gru_thread_state *gru_alloc_thread_state(struct vm_area_struct *vma, struct gru_vma_data *vdata = vma->vm_private_data; struct gru_thread_state *gts, *ngts; - gts = gru_alloc_gts(vma, vdata, tsid); + gts = gru_alloc_gts(vma, vdata->vd_cbr_au_count, vdata->vd_dsr_au_count, + vdata->vd_user_options, tsid); if (!gts) return NULL; @@ -645,7 +645,7 @@ static int gru_retarget_intr(struct gru_thread_state *gts) #define next_gru(b, g) (((g) < &(b)->bs_grus[GRU_CHIPLETS_PER_BLADE - 1]) ? \ ((g)+1) : &(b)->bs_grus[0]) -static void gru_steal_context(struct gru_thread_state *gts) +static void gru_steal_context(struct gru_thread_state *gts, int blade_id) { struct gru_blade_state *blade; struct gru_state *gru, *gru0; @@ -655,8 +655,7 @@ static void gru_steal_context(struct gru_thread_state *gts) cbr = gts->ts_cbr_au_count; dsr = gts->ts_dsr_au_count; - preempt_disable(); - blade = gru_base[uv_numa_blade_id()]; + blade = gru_base[blade_id]; spin_lock(&blade->bs_lock); ctxnum = next_ctxnum(blade->bs_lru_ctxnum); @@ -693,7 +692,6 @@ static void gru_steal_context(struct gru_thread_state *gts) blade->bs_lru_gru = gru; blade->bs_lru_ctxnum = ctxnum; spin_unlock(&blade->bs_lock); - preempt_enable(); if (ngts) { STAT(steal_context); @@ -713,17 +711,17 @@ static void gru_steal_context(struct gru_thread_state *gts) /* * Scan the GRUs on the local blade & assign a GRU context. */ -static struct gru_state *gru_assign_gru_context(struct gru_thread_state *gts) +static struct gru_state *gru_assign_gru_context(struct gru_thread_state *gts, + int blade) { struct gru_state *gru, *grux; int i, max_active_contexts; - preempt_disable(); again: gru = NULL; max_active_contexts = GRU_NUM_CCH; - for_each_gru_on_blade(grux, uv_numa_blade_id(), i) { + for_each_gru_on_blade(grux, blade, i) { if (check_gru_resources(grux, gts->ts_cbr_au_count, gts->ts_dsr_au_count, max_active_contexts)) { @@ -763,7 +761,6 @@ again: STAT(assign_context_failed); } - preempt_enable(); return gru; } @@ -778,6 +775,7 @@ int gru_fault(struct vm_area_struct *vma, struct vm_fault *vmf) { struct gru_thread_state *gts; unsigned long paddr, vaddr; + int blade_id; vaddr = (unsigned long)vmf->virtual_address; gru_dbg(grudev, "vma %p, vaddr 0x%lx (0x%lx)\n", @@ -792,8 +790,10 @@ int gru_fault(struct vm_area_struct *vma, struct vm_fault *vmf) again: mutex_lock(>s->ts_ctxlock); preempt_disable(); + blade_id = uv_numa_blade_id(); + if (gts->ts_gru) { - if (gts->ts_gru->gs_blade_id != uv_numa_blade_id()) { + if (gts->ts_gru->gs_blade_id != blade_id) { STAT(migrated_nopfn_unload); gru_unload_context(gts, 1); } else { @@ -803,12 +803,14 @@ again: } if (!gts->ts_gru) { - if (!gru_assign_gru_context(gts)) { - mutex_unlock(>s->ts_ctxlock); + if (!gru_assign_gru_context(gts, blade_id)) { preempt_enable(); + mutex_unlock(>s->ts_ctxlock); + set_current_state(TASK_INTERRUPTIBLE); schedule_timeout(GRU_ASSIGN_DELAY); /* true hack ZZZ */ + blade_id = uv_numa_blade_id(); if (gts->ts_steal_jiffies + GRU_STEAL_DELAY < jiffies) - gru_steal_context(gts); + gru_steal_context(gts, blade_id); goto again; } gru_load_context(gts); @@ -818,8 +820,8 @@ again: vma->vm_page_prot); } - mutex_unlock(>s->ts_ctxlock); preempt_enable(); + mutex_unlock(>s->ts_ctxlock); return VM_FAULT_NOPAGE; } diff --git a/drivers/misc/sgi-gru/grutables.h b/drivers/misc/sgi-gru/grutables.h index d768f54dc25..c69086c9b98 100644 --- a/drivers/misc/sgi-gru/grutables.h +++ b/drivers/misc/sgi-gru/grutables.h @@ -630,6 +630,8 @@ extern void gru_flush_all_tlb(struct gru_state *gru); extern int gru_proc_init(void); extern void gru_proc_exit(void); +extern struct gru_thread_state *gru_alloc_gts(struct vm_area_struct *vma, + int cbr_au_count, int dsr_au_count, int options, int tsid); extern unsigned long gru_reserve_cb_resources(struct gru_state *gru, int cbr_au_count, char *cbmap); extern unsigned long gru_reserve_ds_resources(struct gru_state *gru, -- cgit v1.2.3-70-g09d2 From d57c82b10709bbb1deb7eb26cf42abcde8851e4d Mon Sep 17 00:00:00 2001 From: Jack Steiner Date: Wed, 17 Jun 2009 16:28:20 -0700 Subject: gru: change context load and unload Remove "static" from the functions for loading/unloading GRU contexts. These functions will be called from other GRU files. Fix bug in unlocking gru context. Signed-off-by: Jack Steiner Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/misc/sgi-gru/grufault.c | 4 ++-- drivers/misc/sgi-gru/grumain.c | 10 +++++----- drivers/misc/sgi-gru/grutables.h | 4 ++++ 3 files changed, 11 insertions(+), 7 deletions(-) (limited to 'drivers/misc') diff --git a/drivers/misc/sgi-gru/grufault.c b/drivers/misc/sgi-gru/grufault.c index 4089f862aa2..f15152165a9 100644 --- a/drivers/misc/sgi-gru/grufault.c +++ b/drivers/misc/sgi-gru/grufault.c @@ -558,8 +558,8 @@ int gru_handle_user_call_os(unsigned long cb) * CCH may contain stale data if ts_force_cch_reload is set. */ if (gts->ts_gru && gts->ts_force_cch_reload) { - gru_update_cch(gts, 0); gts->ts_force_cch_reload = 0; + gru_update_cch(gts, 0); } ret = -EAGAIN; @@ -644,7 +644,7 @@ static int gru_unload_all_contexts(void) if (gts && mutex_trylock(>s->ts_ctxlock)) { spin_unlock(&gru->gs_lock); gru_unload_context(gts, 1); - gru_unlock_gts(gts); + mutex_unlock(>s->ts_ctxlock); spin_lock(&gru->gs_lock); } } diff --git a/drivers/misc/sgi-gru/grumain.c b/drivers/misc/sgi-gru/grumain.c index 14baabc79da..6358244f392 100644 --- a/drivers/misc/sgi-gru/grumain.c +++ b/drivers/misc/sgi-gru/grumain.c @@ -533,7 +533,7 @@ void gru_unload_context(struct gru_thread_state *gts, int savestate) * Load a GRU context by copying it from the thread data structure in memory * to the GRU. */ -static void gru_load_context(struct gru_thread_state *gts) +void gru_load_context(struct gru_thread_state *gts) { struct gru_state *gru = gts->ts_gru; struct gru_context_configuration_handle *cch; @@ -600,8 +600,8 @@ int gru_update_cch(struct gru_thread_state *gts, int force_unload) gts->ts_tlb_int_select = gru_cpu_fault_map_id(); cch->tlb_int_select = gru_cpu_fault_map_id(); cch->tfm_fault_bit_enable = - (gts->ts_user_options == GRU_OPT_MISS_FMM_POLL - || gts->ts_user_options == GRU_OPT_MISS_FMM_INTR); + (gts->ts_user_options == GRU_OPT_MISS_FMM_POLL + || gts->ts_user_options == GRU_OPT_MISS_FMM_INTR); } else { for (i = 0; i < 8; i++) cch->asid[i] = 0; @@ -645,7 +645,7 @@ static int gru_retarget_intr(struct gru_thread_state *gts) #define next_gru(b, g) (((g) < &(b)->bs_grus[GRU_CHIPLETS_PER_BLADE - 1]) ? \ ((g)+1) : &(b)->bs_grus[0]) -static void gru_steal_context(struct gru_thread_state *gts, int blade_id) +void gru_steal_context(struct gru_thread_state *gts, int blade_id) { struct gru_blade_state *blade; struct gru_state *gru, *gru0; @@ -711,7 +711,7 @@ static void gru_steal_context(struct gru_thread_state *gts, int blade_id) /* * Scan the GRUs on the local blade & assign a GRU context. */ -static struct gru_state *gru_assign_gru_context(struct gru_thread_state *gts, +struct gru_state *gru_assign_gru_context(struct gru_thread_state *gts, int blade) { struct gru_state *gru, *grux; diff --git a/drivers/misc/sgi-gru/grutables.h b/drivers/misc/sgi-gru/grutables.h index c69086c9b98..4ddb5b92acb 100644 --- a/drivers/misc/sgi-gru/grutables.h +++ b/drivers/misc/sgi-gru/grutables.h @@ -611,6 +611,10 @@ extern struct gru_thread_state *gru_find_thread_state(struct vm_area_struct *vma, int tsid); extern struct gru_thread_state *gru_alloc_thread_state(struct vm_area_struct *vma, int tsid); +extern struct gru_state *gru_assign_gru_context(struct gru_thread_state *gts, + int blade); +extern void gru_load_context(struct gru_thread_state *gts); +extern void gru_steal_context(struct gru_thread_state *gts, int blade_id); extern void gru_unload_context(struct gru_thread_state *gts, int savestate); extern int gru_update_cch(struct gru_thread_state *gts, int force_unload); extern void gts_drop(struct gru_thread_state *gts); -- cgit v1.2.3-70-g09d2 From 6e9100741ca430eeef8022794f8b62a23a5916af Mon Sep 17 00:00:00 2001 From: Jack Steiner Date: Wed, 17 Jun 2009 16:28:21 -0700 Subject: gru: support cch_allocate for kernel threads Change the interface to cch_allocate so that it can be used to allocate GRU contexts for kernel threads. Kernel threads use the GRU in unmapped mode and do not require ASIDs for the GRU TLB. Signed-off-by: Jack Steiner Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/misc/sgi-gru/gruhandles.c | 12 +----------- drivers/misc/sgi-gru/gruhandles.h | 4 +--- drivers/misc/sgi-gru/grukservices.c | 5 ++++- drivers/misc/sgi-gru/grumain.c | 15 +++++++++++---- 4 files changed, 17 insertions(+), 19 deletions(-) (limited to 'drivers/misc') diff --git a/drivers/misc/sgi-gru/gruhandles.c b/drivers/misc/sgi-gru/gruhandles.c index 9b7ccb32869..a3a870ad915 100644 --- a/drivers/misc/sgi-gru/gruhandles.c +++ b/drivers/misc/sgi-gru/gruhandles.c @@ -72,18 +72,8 @@ static int wait_instruction_complete(void *h, enum mcs_op opc) return status; } -int cch_allocate(struct gru_context_configuration_handle *cch, - int asidval, int sizeavail, unsigned long cbrmap, - unsigned long dsrmap) +int cch_allocate(struct gru_context_configuration_handle *cch) { - int i; - - for (i = 0; i < 8; i++) { - cch->asid[i] = (asidval++); - cch->sizeavail[i] = sizeavail; - } - cch->dsr_allocation_map = dsrmap; - cch->cbr_allocation_map = cbrmap; cch->opc = CCHOP_ALLOCATE; start_instruction(cch); return wait_instruction_complete(cch, cchop_allocate); diff --git a/drivers/misc/sgi-gru/gruhandles.h b/drivers/misc/sgi-gru/gruhandles.h index 03b76a1993c..9f41e2cc09d 100644 --- a/drivers/misc/sgi-gru/gruhandles.h +++ b/drivers/misc/sgi-gru/gruhandles.h @@ -480,9 +480,7 @@ enum gru_cbr_state { /* minimum TLB purge count to ensure a full purge */ #define GRUMAXINVAL 1024UL -int cch_allocate(struct gru_context_configuration_handle *cch, - int asidval, int sizeavail, unsigned long cbrmap, unsigned long dsrmap); - +int cch_allocate(struct gru_context_configuration_handle *cch); int cch_start(struct gru_context_configuration_handle *cch); int cch_interrupt(struct gru_context_configuration_handle *cch); int cch_deallocate(struct gru_context_configuration_handle *cch); diff --git a/drivers/misc/sgi-gru/grukservices.c b/drivers/misc/sgi-gru/grukservices.c index d8bd7d84a7c..900f7aad228 100644 --- a/drivers/misc/sgi-gru/grukservices.c +++ b/drivers/misc/sgi-gru/grukservices.c @@ -672,7 +672,10 @@ int gru_kservices_init(struct gru_state *gru) cch->tlb_int_enable = 0; cch->tfm_done_bit_enable = 0; cch->unmap_enable = 1; - err = cch_allocate(cch, 0, 0, cbr_map, dsr_map); + cch->dsr_allocation_map = dsr_map; + cch->cbr_allocation_map = cbr_map; + + err = cch_allocate(cch); if (err) { gru_dbg(grudev, "Unable to allocate kernel CCH: gid %d, err %d\n", diff --git a/drivers/misc/sgi-gru/grumain.c b/drivers/misc/sgi-gru/grumain.c index 6358244f392..0c7bd384f0c 100644 --- a/drivers/misc/sgi-gru/grumain.c +++ b/drivers/misc/sgi-gru/grumain.c @@ -537,13 +537,12 @@ void gru_load_context(struct gru_thread_state *gts) { struct gru_state *gru = gts->ts_gru; struct gru_context_configuration_handle *cch; - int err, asid, ctxnum = gts->ts_ctxnum; + int i, err, asid, ctxnum = gts->ts_ctxnum; gru_dbg(grudev, "gts %p\n", gts); cch = get_cch(gru->gs_gru_base_vaddr, ctxnum); lock_cch_handle(cch); - asid = gru_load_mm_tracker(gru, gts); cch->tfm_fault_bit_enable = (gts->ts_user_options == GRU_OPT_MISS_FMM_POLL || gts->ts_user_options == GRU_OPT_MISS_FMM_INTR); @@ -553,8 +552,16 @@ void gru_load_context(struct gru_thread_state *gts) cch->tlb_int_select = gts->ts_tlb_int_select; } cch->tfm_done_bit_enable = 0; - err = cch_allocate(cch, asid, gts->ts_sizeavail, gts->ts_cbr_map, - gts->ts_dsr_map); + cch->dsr_allocation_map = gts->ts_dsr_map; + cch->cbr_allocation_map = gts->ts_cbr_map; + asid = gru_load_mm_tracker(gru, gts); + cch->unmap_enable = 0; + for (i = 0; i < 8; i++) { + cch->asid[i] = asid + i; + cch->sizeavail[i] = gts->ts_sizeavail; + } + + err = cch_allocate(cch); if (err) { gru_dbg(grudev, "err %d: cch %p, gts %p, cbr 0x%lx, dsr 0x%lx\n", -- cgit v1.2.3-70-g09d2 From 836ce679c0b5b5040164171afc33753396864b30 Mon Sep 17 00:00:00 2001 From: Jack Steiner Date: Wed, 17 Jun 2009 16:28:22 -0700 Subject: gru: change resource assignment for kernel threads Change the way GRU resources are assigned for kernel threads. GRU contexts for kernel threads are now allocated on demand and can be stolen by user processes when idle. This allows MPI jobs to use ALL of the GRU resources when the kernel is not using them. Signed-off-by: Jack Steiner Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/misc/sgi-gru/gruhandles.c | 5 +- drivers/misc/sgi-gru/grukdump.c | 2 +- drivers/misc/sgi-gru/grukservices.c | 201 +++++++++++++++++++++--------------- drivers/misc/sgi-gru/grumain.c | 55 +++++++--- drivers/misc/sgi-gru/gruprocfs.c | 9 +- drivers/misc/sgi-gru/grutables.h | 17 ++- 6 files changed, 184 insertions(+), 105 deletions(-) (limited to 'drivers/misc') diff --git a/drivers/misc/sgi-gru/gruhandles.c b/drivers/misc/sgi-gru/gruhandles.c index a3a870ad915..37e7cfc53b9 100644 --- a/drivers/misc/sgi-gru/gruhandles.c +++ b/drivers/misc/sgi-gru/gruhandles.c @@ -57,7 +57,7 @@ static void start_instruction(void *h) static int wait_instruction_complete(void *h, enum mcs_op opc) { int status; - cycles_t start_time = get_cycles(); + unsigned long start_time = get_cycles(); while (1) { cpu_relax(); @@ -65,7 +65,8 @@ static int wait_instruction_complete(void *h, enum mcs_op opc) if (status != CCHSTATUS_ACTIVE) break; if (GRU_OPERATION_TIMEOUT < (get_cycles() - start_time)) - panic("GRU %p is malfunctioning\n", h); + panic("GRU %p is malfunctioning: start %ld, end %ld\n", + h, start_time, (unsigned long)get_cycles()); } if (gru_options & OPT_STATS) update_mcs_stats(opc, get_cycles() - start_time); diff --git a/drivers/misc/sgi-gru/grukdump.c b/drivers/misc/sgi-gru/grukdump.c index 27e00931a7b..7b1bdf3906b 100644 --- a/drivers/misc/sgi-gru/grukdump.c +++ b/drivers/misc/sgi-gru/grukdump.c @@ -131,7 +131,7 @@ static int gru_dump_context(struct gru_state *gru, int ctxnum, if (cch_locked || !lock_cch) { gts = gru->gs_gts[ctxnum]; - if (gts) { + if (gts && gts->ts_vma) { hdr.pid = gts->ts_tgid_owner; hdr.vaddr = gts->ts_vma->vm_start; } diff --git a/drivers/misc/sgi-gru/grukservices.c b/drivers/misc/sgi-gru/grukservices.c index 900f7aad228..50b4dd8b0c9 100644 --- a/drivers/misc/sgi-gru/grukservices.c +++ b/drivers/misc/sgi-gru/grukservices.c @@ -31,6 +31,7 @@ #include #include #include +#include #include "gru.h" #include "grulib.h" #include "grutables.h" @@ -45,18 +46,17 @@ * resources. This will likely be replaced when we better understand the * kernel/user requirements. * - * At boot time, the kernel permanently reserves a fixed number of - * CBRs/DSRs for each cpu to use. The resources are all taken from - * the GRU chiplet 1 on the blade. This leaves the full set of resources - * of chiplet 0 available to be allocated to a single user. + * Blade percpu resources reserved for kernel use. These resources are + * reserved whenever the the kernel context for the blade is loaded. Note + * that the kernel context is not guaranteed to be always available. It is + * loaded on demand & can be stolen by a user if the user demand exceeds the + * kernel demand. The kernel can always reload the kernel context but + * a SLEEP may be required!!!. */ - -/* Blade percpu resources PERMANENTLY reserved for kernel use */ #define GRU_NUM_KERNEL_CBR 1 #define GRU_NUM_KERNEL_DSR_BYTES 256 #define GRU_NUM_KERNEL_DSR_CL (GRU_NUM_KERNEL_DSR_BYTES / \ GRU_CACHE_LINE_BYTES) -#define KERNEL_CTXNUM 15 /* GRU instruction attributes for all instructions */ #define IMA IMA_CB_DELAY @@ -98,6 +98,88 @@ struct message_header { #define HSTATUS(mq, h) ((mq) + offsetof(struct message_queue, hstatus[h])) +/* + * Allocate a kernel context (GTS) for the specified blade. + * - protected by writelock on bs_kgts_sema. + */ +static void gru_alloc_kernel_context(struct gru_blade_state *bs, int blade_id) +{ + int cbr_au_count, dsr_au_count, ncpus; + + ncpus = uv_blade_nr_possible_cpus(blade_id); + cbr_au_count = GRU_CB_COUNT_TO_AU(GRU_NUM_KERNEL_CBR * ncpus); + dsr_au_count = GRU_DS_BYTES_TO_AU(GRU_NUM_KERNEL_DSR_BYTES * ncpus); + bs->bs_kgts = gru_alloc_gts(NULL, cbr_au_count, dsr_au_count, 0, 0); +} + +/* + * Reload the blade's kernel context into a GRU chiplet. Called holding + * the bs_kgts_sema for READ. Will steal user contexts if necessary. + */ +static void gru_load_kernel_context(struct gru_blade_state *bs, int blade_id) +{ + struct gru_state *gru; + struct gru_thread_state *kgts; + void *vaddr; + int ctxnum; + + up_read(&bs->bs_kgts_sema); + down_write(&bs->bs_kgts_sema); + + if (!bs->bs_kgts) + gru_alloc_kernel_context(bs, blade_id); + kgts = bs->bs_kgts; + + if (!kgts->ts_gru) { + STAT(load_kernel_context); + while (!gru_assign_gru_context(kgts, blade_id)) { + msleep(1); + gru_steal_context(kgts, blade_id); + } + gru_load_context(kgts); + gru = bs->bs_kgts->ts_gru; + vaddr = gru->gs_gru_base_vaddr; + ctxnum = kgts->ts_ctxnum; + bs->kernel_cb = get_gseg_base_address_cb(vaddr, ctxnum, 0); + bs->kernel_dsr = get_gseg_base_address_ds(vaddr, ctxnum, 0); + } + downgrade_write(&bs->bs_kgts_sema); +} + +/* + * Lock & load the kernel context for the specified blade. + */ +static struct gru_blade_state *gru_lock_kernel_context(int blade_id) +{ + struct gru_blade_state *bs; + + STAT(lock_kernel_context); + bs = gru_base[blade_id]; + + down_read(&bs->bs_kgts_sema); + if (!bs->bs_kgts || !bs->bs_kgts->ts_gru) + gru_load_kernel_context(bs, blade_id); + return bs; + +} + +/* + * Unlock the kernel context for the specified blade. Context is not + * unloaded but may be stolen before next use. + */ +static void gru_unlock_kernel_context(int blade_id) +{ + struct gru_blade_state *bs; + + bs = gru_base[blade_id]; + up_read(&bs->bs_kgts_sema); + STAT(unlock_kernel_context); +} + +/* + * Reserve & get pointers to the DSR/CBRs reserved for the current cpu. + * - returns with preemption disabled + */ static int gru_get_cpu_resources(int dsr_bytes, void **cb, void **dsr) { struct gru_blade_state *bs; @@ -105,18 +187,23 @@ static int gru_get_cpu_resources(int dsr_bytes, void **cb, void **dsr) BUG_ON(dsr_bytes > GRU_NUM_KERNEL_DSR_BYTES); preempt_disable(); - bs = gru_base[uv_numa_blade_id()]; + bs = gru_lock_kernel_context(uv_numa_blade_id()); lcpu = uv_blade_processor_id(); *cb = bs->kernel_cb + lcpu * GRU_HANDLE_STRIDE; *dsr = bs->kernel_dsr + lcpu * GRU_NUM_KERNEL_DSR_BYTES; return 0; } +/* + * Free the current cpus reserved DSR/CBR resources. + */ static void gru_free_cpu_resources(void *cb, void *dsr) { + gru_unlock_kernel_context(uv_numa_blade_id()); preempt_enable(); } +/*----------------------------------------------------------------------*/ int gru_get_cb_exception_detail(void *cb, struct control_block_extended_exc_detail *excdet) { @@ -597,34 +684,36 @@ EXPORT_SYMBOL_GPL(gru_copy_gpa); /* ------------------- KERNEL QUICKTESTS RUN AT STARTUP ----------------*/ /* Temp - will delete after we gain confidence in the GRU */ -static __cacheline_aligned unsigned long word0; -static __cacheline_aligned unsigned long word1; -static int quicktest(struct gru_state *gru) +int quicktest(void) { + unsigned long word0; + unsigned long word1; void *cb; - void *ds; + void *dsr; unsigned long *p; - cb = get_gseg_base_address_cb(gru->gs_gru_base_vaddr, KERNEL_CTXNUM, 0); - ds = get_gseg_base_address_ds(gru->gs_gru_base_vaddr, KERNEL_CTXNUM, 0); - p = ds; + if (gru_get_cpu_resources(GRU_CACHE_LINE_BYTES, &cb, &dsr)) + return MQE_BUG_NO_RESOURCES; + p = dsr; word0 = MAGIC; + word1 = 0; - gru_vload(cb, uv_gpa(&word0), 0, XTYPE_DW, 1, 1, IMA); + gru_vload(cb, uv_gpa(&word0), gru_get_tri(dsr), XTYPE_DW, 1, 1, IMA); if (gru_wait(cb) != CBS_IDLE) BUG(); - if (*(unsigned long *)ds != MAGIC) + if (*p != MAGIC) BUG(); - gru_vstore(cb, uv_gpa(&word1), 0, XTYPE_DW, 1, 1, IMA); + gru_vstore(cb, uv_gpa(&word1), gru_get_tri(dsr), XTYPE_DW, 1, 1, IMA); if (gru_wait(cb) != CBS_IDLE) BUG(); + gru_free_cpu_resources(cb, dsr); - if (word0 != word1 || word0 != MAGIC) { + if (word0 != word1 || word1 != MAGIC) { printk - ("GRU quicktest err: gid %d, found 0x%lx, expected 0x%lx\n", - gru->gs_gid, word1, MAGIC); + ("GRU quicktest err: found 0x%lx, expected 0x%lx\n", + word1, MAGIC); BUG(); /* ZZZ should not be fatal */ } @@ -635,80 +724,30 @@ static int quicktest(struct gru_state *gru) int gru_kservices_init(struct gru_state *gru) { struct gru_blade_state *bs; - struct gru_context_configuration_handle *cch; - unsigned long cbr_map, dsr_map; - int err, num, cpus_possible; - - /* - * Currently, resources are reserved ONLY on the second chiplet - * on each blade. This leaves ALL resources on chiplet 0 available - * for user code. - */ + bs = gru->gs_blade; - if (gru != &bs->bs_grus[1]) + if (gru != &bs->bs_grus[0]) return 0; - cpus_possible = uv_blade_nr_possible_cpus(gru->gs_blade_id); - - num = GRU_NUM_KERNEL_CBR * cpus_possible; - cbr_map = gru_reserve_cb_resources(gru, GRU_CB_COUNT_TO_AU(num), NULL); - gru->gs_reserved_cbrs += num; - - num = GRU_NUM_KERNEL_DSR_BYTES * cpus_possible; - dsr_map = gru_reserve_ds_resources(gru, GRU_DS_BYTES_TO_AU(num), NULL); - gru->gs_reserved_dsr_bytes += num; - - gru->gs_active_contexts++; - __set_bit(KERNEL_CTXNUM, &gru->gs_context_map); - cch = get_cch(gru->gs_gru_base_vaddr, KERNEL_CTXNUM); - - bs->kernel_cb = get_gseg_base_address_cb(gru->gs_gru_base_vaddr, - KERNEL_CTXNUM, 0); - bs->kernel_dsr = get_gseg_base_address_ds(gru->gs_gru_base_vaddr, - KERNEL_CTXNUM, 0); - - lock_cch_handle(cch); - cch->tfm_fault_bit_enable = 0; - cch->tlb_int_enable = 0; - cch->tfm_done_bit_enable = 0; - cch->unmap_enable = 1; - cch->dsr_allocation_map = dsr_map; - cch->cbr_allocation_map = cbr_map; - - err = cch_allocate(cch); - if (err) { - gru_dbg(grudev, - "Unable to allocate kernel CCH: gid %d, err %d\n", - gru->gs_gid, err); - BUG(); - } - if (cch_start(cch)) { - gru_dbg(grudev, "Unable to start kernel CCH: gid %d, err %d\n", - gru->gs_gid, err); - BUG(); - } - unlock_cch_handle(cch); + init_rwsem(&bs->bs_kgts_sema); if (gru_options & GRU_QUICKLOOK) - quicktest(gru); + quicktest(); return 0; } void gru_kservices_exit(struct gru_state *gru) { - struct gru_context_configuration_handle *cch; struct gru_blade_state *bs; + struct gru_thread_state *kgts; bs = gru->gs_blade; - if (gru != &bs->bs_grus[1]) + if (gru != &bs->bs_grus[0]) return; - cch = get_cch(gru->gs_gru_base_vaddr, KERNEL_CTXNUM); - lock_cch_handle(cch); - if (cch_interrupt_sync(cch)) - BUG(); - if (cch_deallocate(cch)) - BUG(); - unlock_cch_handle(cch); + kgts = bs->bs_kgts; + if (kgts && kgts->ts_gru) + gru_unload_context(kgts, 0); + kfree(kgts); } diff --git a/drivers/misc/sgi-gru/grumain.c b/drivers/misc/sgi-gru/grumain.c index 0c7bd384f0c..3398e54a762 100644 --- a/drivers/misc/sgi-gru/grumain.c +++ b/drivers/misc/sgi-gru/grumain.c @@ -96,7 +96,7 @@ static int gru_reset_asid_limit(struct gru_state *gru, int asid) gid = gru->gs_gid; again: for (i = 0; i < GRU_NUM_CCH; i++) { - if (!gru->gs_gts[i]) + if (!gru->gs_gts[i] || is_kernel_context(gru->gs_gts[i])) continue; inuse_asid = gru->gs_gts[i]->ts_gms->ms_asids[gid].mt_asid; gru_dbg(grudev, "gid %d, gts %p, gms %p, inuse 0x%x, cxt %d\n", @@ -506,7 +506,8 @@ void gru_unload_context(struct gru_thread_state *gts, int savestate) struct gru_context_configuration_handle *cch; int ctxnum = gts->ts_ctxnum; - zap_vma_ptes(gts->ts_vma, UGRUADDR(gts), GRU_GSEG_PAGESIZE); + if (!is_kernel_context(gts)) + zap_vma_ptes(gts->ts_vma, UGRUADDR(gts), GRU_GSEG_PAGESIZE); cch = get_cch(gru->gs_gru_base_vaddr, ctxnum); gru_dbg(grudev, "gts %p\n", gts); @@ -514,7 +515,8 @@ void gru_unload_context(struct gru_thread_state *gts, int savestate) if (cch_interrupt_sync(cch)) BUG(); - gru_unload_mm_tracker(gru, gts); + if (!is_kernel_context(gts)) + gru_unload_mm_tracker(gru, gts); if (savestate) gru_unload_context_data(gts->ts_gdata, gru->gs_gru_base_vaddr, ctxnum, gts->ts_cbr_map, @@ -526,7 +528,6 @@ void gru_unload_context(struct gru_thread_state *gts, int savestate) unlock_cch_handle(cch); gru_free_gru_context(gts); - STAT(unload_context); } /* @@ -554,11 +555,16 @@ void gru_load_context(struct gru_thread_state *gts) cch->tfm_done_bit_enable = 0; cch->dsr_allocation_map = gts->ts_dsr_map; cch->cbr_allocation_map = gts->ts_cbr_map; - asid = gru_load_mm_tracker(gru, gts); - cch->unmap_enable = 0; - for (i = 0; i < 8; i++) { - cch->asid[i] = asid + i; - cch->sizeavail[i] = gts->ts_sizeavail; + + if (is_kernel_context(gts)) { + cch->unmap_enable = 1; + } else { + cch->unmap_enable = 0; + asid = gru_load_mm_tracker(gru, gts); + for (i = 0; i < 8; i++) { + cch->asid[i] = asid + i; + cch->sizeavail[i] = gts->ts_sizeavail; + } } err = cch_allocate(cch); @@ -575,8 +581,6 @@ void gru_load_context(struct gru_thread_state *gts) if (cch_start(cch)) BUG(); unlock_cch_handle(cch); - - STAT(load_context); } /* @@ -652,6 +656,27 @@ static int gru_retarget_intr(struct gru_thread_state *gts) #define next_gru(b, g) (((g) < &(b)->bs_grus[GRU_CHIPLETS_PER_BLADE - 1]) ? \ ((g)+1) : &(b)->bs_grus[0]) +static int is_gts_stealable(struct gru_thread_state *gts, + struct gru_blade_state *bs) +{ + if (is_kernel_context(gts)) + return down_write_trylock(&bs->bs_kgts_sema); + else + return mutex_trylock(>s->ts_ctxlock); +} + +static void gts_stolen(struct gru_thread_state *gts, + struct gru_blade_state *bs) +{ + if (is_kernel_context(gts)) { + up_write(&bs->bs_kgts_sema); + STAT(steal_kernel_context); + } else { + mutex_unlock(>s->ts_ctxlock); + STAT(steal_user_context); + } +} + void gru_steal_context(struct gru_thread_state *gts, int blade_id) { struct gru_blade_state *blade; @@ -685,7 +710,7 @@ void gru_steal_context(struct gru_thread_state *gts, int blade_id) * success are high. If trylock fails, try to steal a * different GSEG. */ - if (ngts && mutex_trylock(&ngts->ts_ctxlock)) + if (ngts && is_gts_stealable(ngts, blade)) break; ngts = NULL; flag = 1; @@ -701,10 +726,9 @@ void gru_steal_context(struct gru_thread_state *gts, int blade_id) spin_unlock(&blade->bs_lock); if (ngts) { - STAT(steal_context); ngts->ts_steal_jiffies = jiffies; - gru_unload_context(ngts, 1); - mutex_unlock(&ngts->ts_ctxlock); + gru_unload_context(ngts, is_kernel_context(ngts) ? 0 : 1); + gts_stolen(ngts, blade); } else { STAT(steal_context_failed); } @@ -810,6 +834,7 @@ again: } if (!gts->ts_gru) { + STAT(load_user_context); if (!gru_assign_gru_context(gts, blade_id)) { preempt_enable(); mutex_unlock(>s->ts_ctxlock); diff --git a/drivers/misc/sgi-gru/gruprocfs.c b/drivers/misc/sgi-gru/gruprocfs.c index c46c1c5f0c7..6ef4cb4b84c 100644 --- a/drivers/misc/sgi-gru/gruprocfs.c +++ b/drivers/misc/sgi-gru/gruprocfs.c @@ -51,9 +51,12 @@ static int statistics_show(struct seq_file *s, void *p) printstat(s, assign_context); printstat(s, assign_context_failed); printstat(s, free_context); - printstat(s, load_context); - printstat(s, unload_context); - printstat(s, steal_context); + printstat(s, load_user_context); + printstat(s, load_kernel_context); + printstat(s, lock_kernel_context); + printstat(s, unlock_kernel_context); + printstat(s, steal_user_context); + printstat(s, steal_kernel_context); printstat(s, steal_context_failed); printstat(s, nopfn); printstat(s, break_cow); diff --git a/drivers/misc/sgi-gru/grutables.h b/drivers/misc/sgi-gru/grutables.h index 4ddb5b92acb..1c85fdcf5d3 100644 --- a/drivers/misc/sgi-gru/grutables.h +++ b/drivers/misc/sgi-gru/grutables.h @@ -174,9 +174,12 @@ struct gru_stats_s { atomic_long_t assign_context; atomic_long_t assign_context_failed; atomic_long_t free_context; - atomic_long_t load_context; - atomic_long_t unload_context; - atomic_long_t steal_context; + atomic_long_t load_user_context; + atomic_long_t load_kernel_context; + atomic_long_t lock_kernel_context; + atomic_long_t unlock_kernel_context; + atomic_long_t steal_user_context; + atomic_long_t steal_kernel_context; atomic_long_t steal_context_failed; atomic_long_t nopfn; atomic_long_t break_cow; @@ -454,6 +457,9 @@ struct gru_blade_state { reserved cb */ void *kernel_dsr; /* First kernel reserved DSR */ + struct rw_semaphore bs_kgts_sema; /* lock for kgts */ + struct gru_thread_state *bs_kgts; /* GTS for kernel use */ + /* ---- the following are protected by the bs_lock spinlock ---- */ spinlock_t bs_lock; /* lock used for stealing contexts */ @@ -597,6 +603,11 @@ static inline void unlock_tgh_handle(struct gru_tlb_global_handle *tgh) __unlock_handle(tgh); } +static inline int is_kernel_context(struct gru_thread_state *gts) +{ + return !gts->ts_mm; +} + /*----------------------------------------------------------------------------- * Function prototypes & externs */ -- cgit v1.2.3-70-g09d2 From 3eac2e95d7bb92222e185e445eca1fe3f695050f Mon Sep 17 00:00:00 2001 From: Jack Steiner Date: Wed, 17 Jun 2009 16:28:23 -0700 Subject: gru: support contexts with zero dsrs or cbrs Support alocation of GRU contexts that contain zero DSR or CBR resources. Some instructions do not require DSR resources. Contexts without CBR resources are useful for diagnostics. Signed-off-by: Jack Steiner Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/misc/sgi-gru/grufile.c | 6 ++---- drivers/misc/sgi-gru/grumain.c | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) (limited to 'drivers/misc') diff --git a/drivers/misc/sgi-gru/grufile.c b/drivers/misc/sgi-gru/grufile.c index 9e6da46eeb0..b1567ce868e 100644 --- a/drivers/misc/sgi-gru/grufile.c +++ b/drivers/misc/sgi-gru/grufile.c @@ -135,11 +135,9 @@ static int gru_create_new_context(unsigned long arg) if (copy_from_user(&req, (void __user *)arg, sizeof(req))) return -EFAULT; - if (req.data_segment_bytes == 0 || - req.data_segment_bytes > max_user_dsr_bytes) + if (req.data_segment_bytes > max_user_dsr_bytes) return -EINVAL; - if (!req.control_blocks || !req.maximum_thread_count || - req.control_blocks > max_user_cbrs) + if (req.control_blocks > max_user_cbrs || !req.maximum_thread_count) return -EINVAL; if (!(req.options & GRU_OPT_MISS_MASK)) diff --git a/drivers/misc/sgi-gru/grumain.c b/drivers/misc/sgi-gru/grumain.c index 3398e54a762..4e6e8c3554f 100644 --- a/drivers/misc/sgi-gru/grumain.c +++ b/drivers/misc/sgi-gru/grumain.c @@ -150,7 +150,7 @@ static unsigned long reserve_resources(unsigned long *p, int n, int mmax, unsigned long bits = 0; int i; - do { + while (n--) { i = find_first_bit(p, mmax); if (i == mmax) BUG(); @@ -158,7 +158,7 @@ static unsigned long reserve_resources(unsigned long *p, int n, int mmax, __set_bit(i, &bits); if (idx) *idx++ = i; - } while (--n); + } return bits; } -- cgit v1.2.3-70-g09d2 From 17b49a67a6a59f0e9f3c22e67ddb602410e8e182 Mon Sep 17 00:00:00 2001 From: Jack Steiner Date: Wed, 17 Jun 2009 16:28:23 -0700 Subject: gru: fix handling of mesq failures Fix endcase in handling GRU message queue failures due to NACKs of PUT requests. Must ensure that the "present" bits are cleared before resending the message. Signed-off-by: Jack Steiner Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/misc/sgi-gru/grukservices.c | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) (limited to 'drivers/misc') diff --git a/drivers/misc/sgi-gru/grukservices.c b/drivers/misc/sgi-gru/grukservices.c index 50b4dd8b0c9..a0f981022a6 100644 --- a/drivers/misc/sgi-gru/grukservices.c +++ b/drivers/misc/sgi-gru/grukservices.c @@ -503,6 +503,29 @@ static void send_message_queue_interrupt(struct gru_message_queue_desc *mqd) mqd->interrupt_vector); } +/* + * Handle a PUT failure. Note: if message was a 2-line message, one of the + * lines might have successfully have been written. Before sending the + * message, "present" must be cleared in BOTH lines to prevent the receiver + * from prematurely seeing the full message. + */ +static int send_message_put_nacked(void *cb, struct gru_message_queue_desc *mqd, + void *mesg, int lines) +{ + unsigned long m; + + m = mqd->mq_gpa + (gru_get_amo_value_head(cb) << 6); + if (lines == 2) { + gru_vset(cb, m, 0, XTYPE_CL, lines, 1, IMA); + if (gru_wait(cb) != CBS_IDLE) + return MQE_UNEXPECTED_CB_ERR; + } + gru_vstore(cb, m, gru_get_tri(mesg), XTYPE_CL, lines, 1, IMA); + if (gru_wait(cb) != CBS_IDLE) + return MQE_UNEXPECTED_CB_ERR; + send_message_queue_interrupt(mqd); + return MQE_OK; +} /* * Handle a gru_mesq failure. Some of these failures are software recoverable @@ -512,7 +535,6 @@ static int send_message_failure(void *cb, struct gru_message_queue_desc *mqd, void *mesg, int lines) { int substatus, ret = 0; - unsigned long m; substatus = gru_get_cb_message_queue_substatus(cb); switch (substatus) { @@ -534,14 +556,7 @@ static int send_message_failure(void *cb, struct gru_message_queue_desc *mqd, break; case CBSS_PUT_NACKED: STAT(mesq_send_put_nacked); - m = mqd->mq_gpa + (gru_get_amo_value_head(cb) << 6); - gru_vstore(cb, m, gru_get_tri(mesg), XTYPE_CL, lines, 1, IMA); - if (gru_wait(cb) == CBS_IDLE) { - ret = MQE_OK; - send_message_queue_interrupt(mqd); - } else { - ret = MQE_UNEXPECTED_CB_ERR; - } + ret = send_message_put_nacked(cb, mqd, mesg, lines); break; default: BUG(); -- cgit v1.2.3-70-g09d2 From 940229b9c0dcd9b6e1d64d0d26eba00238ddae98 Mon Sep 17 00:00:00 2001 From: Jack Steiner Date: Wed, 17 Jun 2009 16:28:24 -0700 Subject: gru: check context state on reload Check whether the gru state being loaded into a gru is from a new context or a previously unloaded context. If new, simply zero out the hardware context; if unloaded and valid, reload the old state. This change is primarily for reloading kernel contexts where the previous is not required to be saved. Signed-off-by: Jack Steiner Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/misc/sgi-gru/grumain.c | 32 +++++++++++++++++++++++--------- drivers/misc/sgi-gru/grutables.h | 2 ++ 2 files changed, 25 insertions(+), 9 deletions(-) (limited to 'drivers/misc') diff --git a/drivers/misc/sgi-gru/grumain.c b/drivers/misc/sgi-gru/grumain.c index 4e6e8c3554f..afc4c473c79 100644 --- a/drivers/misc/sgi-gru/grumain.c +++ b/drivers/misc/sgi-gru/grumain.c @@ -307,11 +307,12 @@ struct gru_thread_state *gru_alloc_gts(struct vm_area_struct *vma, bytes = DSR_BYTES(dsr_au_count) + CBR_BYTES(cbr_au_count); bytes += sizeof(struct gru_thread_state); - gts = kzalloc(bytes, GFP_KERNEL); + gts = kmalloc(bytes, GFP_KERNEL); if (!gts) return NULL; STAT(gts_alloc); + memset(gts, 0, sizeof(struct gru_thread_state)); /* zero out header */ atomic_set(>s->ts_refcnt, 1); mutex_init(>s->ts_ctxlock); gts->ts_cbr_au_count = cbr_au_count; @@ -458,7 +459,8 @@ static void gru_prefetch_context(void *gseg, void *cb, void *cbe, } static void gru_load_context_data(void *save, void *grubase, int ctxnum, - unsigned long cbrmap, unsigned long dsrmap) + unsigned long cbrmap, unsigned long dsrmap, + int data_valid) { void *gseg, *cb, *cbe; unsigned long length; @@ -471,12 +473,22 @@ static void gru_load_context_data(void *save, void *grubase, int ctxnum, gru_prefetch_context(gseg, cb, cbe, cbrmap, length); for_each_cbr_in_allocation_map(i, &cbrmap, scr) { - save += gru_copy_handle(cb, save); - save += gru_copy_handle(cbe + i * GRU_HANDLE_STRIDE, save); + if (data_valid) { + save += gru_copy_handle(cb, save); + save += gru_copy_handle(cbe + i * GRU_HANDLE_STRIDE, + save); + } else { + memset(cb, 0, GRU_CACHE_LINE_BYTES); + memset(cbe + i * GRU_HANDLE_STRIDE, 0, + GRU_CACHE_LINE_BYTES); + } cb += GRU_HANDLE_STRIDE; } - memcpy(gseg + GRU_DS_BASE, save, length); + if (data_valid) + memcpy(gseg + GRU_DS_BASE, save, length); + else + memset(gseg + GRU_DS_BASE, 0, length); } static void gru_unload_context_data(void *save, void *grubase, int ctxnum, @@ -517,10 +529,12 @@ void gru_unload_context(struct gru_thread_state *gts, int savestate) if (!is_kernel_context(gts)) gru_unload_mm_tracker(gru, gts); - if (savestate) + if (savestate) { gru_unload_context_data(gts->ts_gdata, gru->gs_gru_base_vaddr, ctxnum, gts->ts_cbr_map, gts->ts_dsr_map); + gts->ts_data_valid = 1; + } if (cch_deallocate(cch)) BUG(); @@ -576,7 +590,7 @@ void gru_load_context(struct gru_thread_state *gts) } gru_load_context_data(gts->ts_gdata, gru->gs_gru_base_vaddr, ctxnum, - gts->ts_cbr_map, gts->ts_dsr_map); + gts->ts_cbr_map, gts->ts_dsr_map, gts->ts_data_valid); if (cch_start(cch)) BUG(); @@ -611,8 +625,8 @@ int gru_update_cch(struct gru_thread_state *gts, int force_unload) gts->ts_tlb_int_select = gru_cpu_fault_map_id(); cch->tlb_int_select = gru_cpu_fault_map_id(); cch->tfm_fault_bit_enable = - (gts->ts_user_options == GRU_OPT_MISS_FMM_POLL - || gts->ts_user_options == GRU_OPT_MISS_FMM_INTR); + (gts->ts_user_options == GRU_OPT_MISS_FMM_POLL + || gts->ts_user_options == GRU_OPT_MISS_FMM_INTR); } else { for (i = 0; i < 8; i++) cch->asid[i] = 0; diff --git a/drivers/misc/sgi-gru/grutables.h b/drivers/misc/sgi-gru/grutables.h index 1c85fdcf5d3..5f8f3bda2fa 100644 --- a/drivers/misc/sgi-gru/grutables.h +++ b/drivers/misc/sgi-gru/grutables.h @@ -385,6 +385,8 @@ struct gru_thread_state { after migration */ char ts_cbr_idx[GRU_CBR_AU];/* CBR numbers of each allocated CB */ + int ts_data_valid; /* Indicates if ts_gdata has + valid data */ unsigned long ts_gdata[0]; /* save area for GRU data (CB, DS, CBE) */ }; -- cgit v1.2.3-70-g09d2 From 4a7a17c1188a878e9f00e4ca8dc724c7cff17606 Mon Sep 17 00:00:00 2001 From: Jack Steiner Date: Wed, 17 Jun 2009 16:28:25 -0700 Subject: gru: support instruction completion interrupts Add support for interrupts generated by GRU instruction completion. Previously, the only interrupts were for TLB misses. The hardware also supports interrupts on instruction completion. This will be supported for instructions issued by the kernel. Signed-off-by: Jack Steiner Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/misc/sgi-gru/grufault.c | 27 ++++++++++++++++++++------- drivers/misc/sgi-gru/grumain.c | 4 ++++ drivers/misc/sgi-gru/grutables.h | 5 +++++ 3 files changed, 29 insertions(+), 7 deletions(-) (limited to 'drivers/misc') diff --git a/drivers/misc/sgi-gru/grufault.c b/drivers/misc/sgi-gru/grufault.c index f15152165a9..3220e95be6b 100644 --- a/drivers/misc/sgi-gru/grufault.c +++ b/drivers/misc/sgi-gru/grufault.c @@ -166,7 +166,8 @@ static inline struct gru_state *irq_to_gru(int irq) * the GRU, atomic operations must be used to clear bits. */ static void get_clear_fault_map(struct gru_state *gru, - struct gru_tlb_fault_map *map) + struct gru_tlb_fault_map *imap, + struct gru_tlb_fault_map *dmap) { unsigned long i, k; struct gru_tlb_fault_map *tfm; @@ -177,7 +178,11 @@ static void get_clear_fault_map(struct gru_state *gru, k = tfm->fault_bits[i]; if (k) k = xchg(&tfm->fault_bits[i], 0UL); - map->fault_bits[i] = k; + imap->fault_bits[i] = k; + k = tfm->done_bits[i]; + if (k) + k = xchg(&tfm->done_bits[i], 0UL); + dmap->fault_bits[i] = k; } /* @@ -449,7 +454,7 @@ failactive: irqreturn_t gru_intr(int irq, void *dev_id) { struct gru_state *gru; - struct gru_tlb_fault_map map; + struct gru_tlb_fault_map imap, dmap; struct gru_thread_state *gts; struct gru_tlb_fault_handle *tfh = NULL; int cbrnum, ctxnum; @@ -462,11 +467,19 @@ irqreturn_t gru_intr(int irq, void *dev_id) raw_smp_processor_id(), irq); return IRQ_NONE; } - get_clear_fault_map(gru, &map); - gru_dbg(grudev, "irq %d, gru %x, map 0x%lx\n", irq, gru->gs_gid, - map.fault_bits[0]); + get_clear_fault_map(gru, &imap, &dmap); + gru_dbg(grudev, + "irq %d, gid %d, imap %016lx %016lx, dmap %016lx %016lx\n", + irq, gru->gs_gid, dmap.fault_bits[0], dmap.fault_bits[1], + dmap.fault_bits[0], dmap.fault_bits[1]); + + for_each_cbr_in_tfm(cbrnum, dmap.fault_bits) { + complete(gru->gs_blade->bs_async_wq); + gru_dbg(grudev, "gid %d, cbr_done %d, done %d\n", + gru->gs_gid, cbrnum, gru->gs_blade->bs_async_wq->done); + } - for_each_cbr_in_tfm(cbrnum, map.fault_bits) { + for_each_cbr_in_tfm(cbrnum, imap.fault_bits) { tfh = get_tfh_by_index(gru, cbrnum); prefetchw(tfh); /* Helps on hdw, required for emulator */ diff --git a/drivers/misc/sgi-gru/grumain.c b/drivers/misc/sgi-gru/grumain.c index afc4c473c79..e38a0f1775f 100644 --- a/drivers/misc/sgi-gru/grumain.c +++ b/drivers/misc/sgi-gru/grumain.c @@ -572,8 +572,12 @@ void gru_load_context(struct gru_thread_state *gts) if (is_kernel_context(gts)) { cch->unmap_enable = 1; + cch->tfm_done_bit_enable = 1; + cch->cb_int_enable = 1; } else { cch->unmap_enable = 0; + cch->tfm_done_bit_enable = 0; + cch->cb_int_enable = 0; asid = gru_load_mm_tracker(gru, gts); for (i = 0; i < 8; i++) { cch->asid[i] = asid + i; diff --git a/drivers/misc/sgi-gru/grutables.h b/drivers/misc/sgi-gru/grutables.h index 5f8f3bda2fa..ca81800146f 100644 --- a/drivers/misc/sgi-gru/grutables.h +++ b/drivers/misc/sgi-gru/grutables.h @@ -462,6 +462,11 @@ struct gru_blade_state { struct rw_semaphore bs_kgts_sema; /* lock for kgts */ struct gru_thread_state *bs_kgts; /* GTS for kernel use */ + /* ---- the following are used for managing kernel async GRU CBRs --- */ + int bs_async_dsr_bytes; /* DSRs for async */ + int bs_async_cbrs; /* CBRs AU for async */ + struct completion *bs_async_wq; + /* ---- the following are protected by the bs_lock spinlock ---- */ spinlock_t bs_lock; /* lock used for stealing contexts */ -- cgit v1.2.3-70-g09d2 From 9120dec47f150636d85b3dba03318ccecd181c79 Mon Sep 17 00:00:00 2001 From: Jack Steiner Date: Wed, 17 Jun 2009 16:28:25 -0700 Subject: gru: support for asynchronous gru instructions Add support for asynchronous GRU instructions. Currently, asynchronous instructions are supported only for GRU instructions issued by the kernel. [akpm@linux-foundation.org: build fix] Signed-off-by: Jack Steiner Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/misc/sgi-gru/grufault.c | 4 - drivers/misc/sgi-gru/grukservices.c | 178 ++++++++++++++++++++++++++++++++---- drivers/misc/sgi-gru/grukservices.h | 51 +++++++++++ 3 files changed, 213 insertions(+), 20 deletions(-) (limited to 'drivers/misc') diff --git a/drivers/misc/sgi-gru/grufault.c b/drivers/misc/sgi-gru/grufault.c index 3220e95be6b..8443e90f9f6 100644 --- a/drivers/misc/sgi-gru/grufault.c +++ b/drivers/misc/sgi-gru/grufault.c @@ -468,10 +468,6 @@ irqreturn_t gru_intr(int irq, void *dev_id) return IRQ_NONE; } get_clear_fault_map(gru, &imap, &dmap); - gru_dbg(grudev, - "irq %d, gid %d, imap %016lx %016lx, dmap %016lx %016lx\n", - irq, gru->gs_gid, dmap.fault_bits[0], dmap.fault_bits[1], - dmap.fault_bits[0], dmap.fault_bits[1]); for_each_cbr_in_tfm(cbrnum, dmap.fault_bits) { complete(gru->gs_blade->bs_async_wq); diff --git a/drivers/misc/sgi-gru/grukservices.c b/drivers/misc/sgi-gru/grukservices.c index a0f981022a6..9dff33cb72e 100644 --- a/drivers/misc/sgi-gru/grukservices.c +++ b/drivers/misc/sgi-gru/grukservices.c @@ -52,7 +52,53 @@ * loaded on demand & can be stolen by a user if the user demand exceeds the * kernel demand. The kernel can always reload the kernel context but * a SLEEP may be required!!!. + * + * Async Overview: + * + * Each blade has one "kernel context" that owns GRU kernel resources + * located on the blade. Kernel drivers use GRU resources in this context + * for sending messages, zeroing memory, etc. + * + * The kernel context is dynamically loaded on demand. If it is not in + * use by the kernel, the kernel context can be unloaded & given to a user. + * The kernel context will be reloaded when needed. This may require that + * a context be stolen from a user. + * NOTE: frequent unloading/reloading of the kernel context is + * expensive. We are depending on batch schedulers, cpusets, sane + * drivers or some other mechanism to prevent the need for frequent + * stealing/reloading. + * + * The kernel context consists of two parts: + * - 1 CB & a few DSRs that are reserved for each cpu on the blade. + * Each cpu has it's own private resources & does not share them + * with other cpus. These resources are used serially, ie, + * locked, used & unlocked on each call to a function in + * grukservices. + * (Now that we have dynamic loading of kernel contexts, I + * may rethink this & allow sharing between cpus....) + * + * - Additional resources can be reserved long term & used directly + * by UV drivers located in the kernel. Drivers using these GRU + * resources can use asynchronous GRU instructions that send + * interrupts on completion. + * - these resources must be explicitly locked/unlocked + * - locked resources prevent (obviously) the kernel + * context from being unloaded. + * - drivers using these resource directly issue their own + * GRU instruction and must wait/check completion. + * + * When these resources are reserved, the caller can optionally + * associate a wait_queue with the resources and use asynchronous + * GRU instructions. When an async GRU instruction completes, the + * driver will do a wakeup on the event. + * */ + + +#define ASYNC_HAN_TO_BID(h) ((h) - 1) +#define ASYNC_BID_TO_HAN(b) ((b) + 1) +#define ASYNC_HAN_TO_BS(h) gru_base[ASYNC_HAN_TO_BID(h)] + #define GRU_NUM_KERNEL_CBR 1 #define GRU_NUM_KERNEL_DSR_BYTES 256 #define GRU_NUM_KERNEL_DSR_CL (GRU_NUM_KERNEL_DSR_BYTES / \ @@ -98,20 +144,6 @@ struct message_header { #define HSTATUS(mq, h) ((mq) + offsetof(struct message_queue, hstatus[h])) -/* - * Allocate a kernel context (GTS) for the specified blade. - * - protected by writelock on bs_kgts_sema. - */ -static void gru_alloc_kernel_context(struct gru_blade_state *bs, int blade_id) -{ - int cbr_au_count, dsr_au_count, ncpus; - - ncpus = uv_blade_nr_possible_cpus(blade_id); - cbr_au_count = GRU_CB_COUNT_TO_AU(GRU_NUM_KERNEL_CBR * ncpus); - dsr_au_count = GRU_DS_BYTES_TO_AU(GRU_NUM_KERNEL_DSR_BYTES * ncpus); - bs->bs_kgts = gru_alloc_gts(NULL, cbr_au_count, dsr_au_count, 0, 0); -} - /* * Reload the blade's kernel context into a GRU chiplet. Called holding * the bs_kgts_sema for READ. Will steal user contexts if necessary. @@ -121,17 +153,23 @@ static void gru_load_kernel_context(struct gru_blade_state *bs, int blade_id) struct gru_state *gru; struct gru_thread_state *kgts; void *vaddr; - int ctxnum; + int ctxnum, ncpus; up_read(&bs->bs_kgts_sema); down_write(&bs->bs_kgts_sema); if (!bs->bs_kgts) - gru_alloc_kernel_context(bs, blade_id); + bs->bs_kgts = gru_alloc_gts(NULL, 0, 0, 0, 0); kgts = bs->bs_kgts; if (!kgts->ts_gru) { STAT(load_kernel_context); + ncpus = uv_blade_nr_possible_cpus(blade_id); + kgts->ts_cbr_au_count = GRU_CB_COUNT_TO_AU( + GRU_NUM_KERNEL_CBR * ncpus + bs->bs_async_cbrs); + kgts->ts_dsr_au_count = GRU_DS_BYTES_TO_AU( + GRU_NUM_KERNEL_DSR_BYTES * ncpus + + bs->bs_async_dsr_bytes); while (!gru_assign_gru_context(kgts, blade_id)) { msleep(1); gru_steal_context(kgts, blade_id); @@ -203,6 +241,114 @@ static void gru_free_cpu_resources(void *cb, void *dsr) preempt_enable(); } +/* + * Reserve GRU resources to be used asynchronously. + * Note: currently supports only 1 reservation per blade. + * + * input: + * blade_id - blade on which resources should be reserved + * cbrs - number of CBRs + * dsr_bytes - number of DSR bytes needed + * output: + * handle to identify resource + * (0 = async resources already reserved) + */ +unsigned long gru_reserve_async_resources(int blade_id, int cbrs, int dsr_bytes, + struct completion *cmp) +{ + struct gru_blade_state *bs; + struct gru_thread_state *kgts; + int ret = 0; + + bs = gru_base[blade_id]; + + down_write(&bs->bs_kgts_sema); + + /* Verify no resources already reserved */ + if (bs->bs_async_dsr_bytes + bs->bs_async_cbrs) + goto done; + bs->bs_async_dsr_bytes = dsr_bytes; + bs->bs_async_cbrs = cbrs; + bs->bs_async_wq = cmp; + kgts = bs->bs_kgts; + + /* Resources changed. Unload context if already loaded */ + if (kgts && kgts->ts_gru) + gru_unload_context(kgts, 0); + ret = ASYNC_BID_TO_HAN(blade_id); + +done: + up_write(&bs->bs_kgts_sema); + return ret; +} + +/* + * Release async resources previously reserved. + * + * input: + * han - handle to identify resources + */ +void gru_release_async_resources(unsigned long han) +{ + struct gru_blade_state *bs = ASYNC_HAN_TO_BS(han); + + down_write(&bs->bs_kgts_sema); + bs->bs_async_dsr_bytes = 0; + bs->bs_async_cbrs = 0; + bs->bs_async_wq = NULL; + up_write(&bs->bs_kgts_sema); +} + +/* + * Wait for async GRU instructions to complete. + * + * input: + * han - handle to identify resources + */ +void gru_wait_async_cbr(unsigned long han) +{ + struct gru_blade_state *bs = ASYNC_HAN_TO_BS(han); + + wait_for_completion(bs->bs_async_wq); + mb(); +} + +/* + * Lock previous reserved async GRU resources + * + * input: + * han - handle to identify resources + * output: + * cb - pointer to first CBR + * dsr - pointer to first DSR + */ +void gru_lock_async_resource(unsigned long han, void **cb, void **dsr) +{ + struct gru_blade_state *bs = ASYNC_HAN_TO_BS(han); + int blade_id = ASYNC_HAN_TO_BID(han); + int ncpus; + + gru_lock_kernel_context(blade_id); + ncpus = uv_blade_nr_possible_cpus(blade_id); + if (cb) + *cb = bs->kernel_cb + ncpus * GRU_HANDLE_STRIDE; + if (dsr) + *dsr = bs->kernel_dsr + ncpus * GRU_NUM_KERNEL_DSR_BYTES; +} + +/* + * Unlock previous reserved async GRU resources + * + * input: + * han - handle to identify resources + */ +void gru_unlock_async_resource(unsigned long han) +{ + int blade_id = ASYNC_HAN_TO_BID(han); + + gru_unlock_kernel_context(blade_id); +} + /*----------------------------------------------------------------------*/ int gru_get_cb_exception_detail(void *cb, struct control_block_extended_exc_detail *excdet) diff --git a/drivers/misc/sgi-gru/grukservices.h b/drivers/misc/sgi-gru/grukservices.h index 747ed315d56..d60d34bca44 100644 --- a/drivers/misc/sgi-gru/grukservices.h +++ b/drivers/misc/sgi-gru/grukservices.h @@ -146,4 +146,55 @@ extern void *gru_get_next_message(struct gru_message_queue_desc *mqd); extern int gru_copy_gpa(unsigned long dest_gpa, unsigned long src_gpa, unsigned int bytes); +/* + * Reserve GRU resources to be used asynchronously. + * + * input: + * blade_id - blade on which resources should be reserved + * cbrs - number of CBRs + * dsr_bytes - number of DSR bytes needed + * cmp - completion structure for waiting for + * async completions + * output: + * handle to identify resource + * (0 = no resources) + */ +extern unsigned long gru_reserve_async_resources(int blade_id, int cbrs, int dsr_bytes, + struct completion *cmp); + +/* + * Release async resources previously reserved. + * + * input: + * han - handle to identify resources + */ +extern void gru_release_async_resources(unsigned long han); + +/* + * Wait for async GRU instructions to complete. + * + * input: + * han - handle to identify resources + */ +extern void gru_wait_async_cbr(unsigned long han); + +/* + * Lock previous reserved async GRU resources + * + * input: + * han - handle to identify resources + * output: + * cb - pointer to first CBR + * dsr - pointer to first DSR + */ +extern void gru_lock_async_resource(unsigned long han, void **cb, void **dsr); + +/* + * Unlock previous reserved async GRU resources + * + * input: + * han - handle to identify resources + */ +extern void gru_unlock_async_resource(unsigned long han); + #endif /* __GRU_KSERVICES_H_ */ -- cgit v1.2.3-70-g09d2 From eb5bd5e52a8eafb1ddb42f983d41f97552afa106 Mon Sep 17 00:00:00 2001 From: Jack Steiner Date: Wed, 17 Jun 2009 16:28:26 -0700 Subject: gru: update gru kernel self tests Change the kernel self tests that can be optionally executed on GRU initialization. This is primarily for testing. Eliminate the BUG statements on failure and return bad status. Add ioctl interface to execute the tests on demand. Signed-off-by: Jack Steiner Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/misc/sgi-gru/grufile.c | 3 + drivers/misc/sgi-gru/grukservices.c | 150 ++++++++++++++++++++++++++++++++---- drivers/misc/sgi-gru/grulib.h | 3 + drivers/misc/sgi-gru/grutables.h | 2 +- 4 files changed, 142 insertions(+), 16 deletions(-) (limited to 'drivers/misc') diff --git a/drivers/misc/sgi-gru/grufile.c b/drivers/misc/sgi-gru/grufile.c index b1567ce868e..796ac704795 100644 --- a/drivers/misc/sgi-gru/grufile.c +++ b/drivers/misc/sgi-gru/grufile.c @@ -250,6 +250,9 @@ static long gru_file_unlocked_ioctl(struct file *file, unsigned int req, case GRU_USER_CALL_OS: err = gru_handle_user_call_os(arg); break; + case GRU_KTEST: + err = gru_ktest(arg); + break; case GRU_GET_CONFIG_INFO: err = gru_get_config_info(arg); break; diff --git a/drivers/misc/sgi-gru/grukservices.c b/drivers/misc/sgi-gru/grukservices.c index 9dff33cb72e..7d7952b27e0 100644 --- a/drivers/misc/sgi-gru/grukservices.c +++ b/drivers/misc/sgi-gru/grukservices.c @@ -846,13 +846,14 @@ EXPORT_SYMBOL_GPL(gru_copy_gpa); /* ------------------- KERNEL QUICKTESTS RUN AT STARTUP ----------------*/ /* Temp - will delete after we gain confidence in the GRU */ -int quicktest(void) +static int quicktest0(unsigned long arg) { unsigned long word0; unsigned long word1; void *cb; void *dsr; unsigned long *p; + int ret = -EIO; if (gru_get_cpu_resources(GRU_CACHE_LINE_BYTES, &cb, &dsr)) return MQE_BUG_NO_RESOURCES; @@ -861,26 +862,148 @@ int quicktest(void) word1 = 0; gru_vload(cb, uv_gpa(&word0), gru_get_tri(dsr), XTYPE_DW, 1, 1, IMA); - if (gru_wait(cb) != CBS_IDLE) - BUG(); + if (gru_wait(cb) != CBS_IDLE) { + printk(KERN_DEBUG "GRU quicktest0: CBR failure 1\n"); + goto done; + } - if (*p != MAGIC) - BUG(); + if (*p != MAGIC) { + printk(KERN_DEBUG "GRU: quicktest0 bad magic 0x%lx\n", *p); + goto done; + } gru_vstore(cb, uv_gpa(&word1), gru_get_tri(dsr), XTYPE_DW, 1, 1, IMA); - if (gru_wait(cb) != CBS_IDLE) - BUG(); - gru_free_cpu_resources(cb, dsr); + if (gru_wait(cb) != CBS_IDLE) { + printk(KERN_DEBUG "GRU quicktest0: CBR failure 2\n"); + goto done; + } if (word0 != word1 || word1 != MAGIC) { - printk - ("GRU quicktest err: found 0x%lx, expected 0x%lx\n", + printk(KERN_DEBUG + "GRU quicktest0 err: found 0x%lx, expected 0x%lx\n", word1, MAGIC); - BUG(); /* ZZZ should not be fatal */ + goto done; } + ret = 0; - return 0; +done: + gru_free_cpu_resources(cb, dsr); + return ret; } +#define ALIGNUP(p, q) ((void *)(((unsigned long)(p) + (q) - 1) & ~(q - 1))) + +static int quicktest1(unsigned long arg) +{ + struct gru_message_queue_desc mqd; + void *p, *mq; + unsigned long *dw; + int i, ret = -EIO; + char mes[GRU_CACHE_LINE_BYTES], *m; + + /* Need 1K cacheline aligned that does not cross page boundary */ + p = kmalloc(4096, 0); + mq = ALIGNUP(p, 1024); + memset(mes, 0xee, sizeof(mes)); + dw = mq; + + gru_create_message_queue(&mqd, mq, 8 * GRU_CACHE_LINE_BYTES, 0, 0, 0); + for (i = 0; i < 6; i++) { + mes[8] = i; + do { + ret = gru_send_message_gpa(&mqd, mes, sizeof(mes)); + } while (ret == MQE_CONGESTION); + if (ret) + break; + } + if (ret != MQE_QUEUE_FULL || i != 4) + goto done; + + for (i = 0; i < 6; i++) { + m = gru_get_next_message(&mqd); + if (!m || m[8] != i) + break; + gru_free_message(&mqd, m); + } + ret = (i == 4) ? 0 : -EIO; + +done: + kfree(p); + return ret; +} + +static int quicktest2(unsigned long arg) +{ + static DECLARE_COMPLETION(cmp); + unsigned long han; + int blade_id = 0; + int numcb = 4; + int ret = 0; + unsigned long *buf; + void *cb0, *cb; + int i, k, istatus, bytes; + + bytes = numcb * 4 * 8; + buf = kmalloc(bytes, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = -EBUSY; + han = gru_reserve_async_resources(blade_id, numcb, 0, &cmp); + if (!han) + goto done; + + gru_lock_async_resource(han, &cb0, NULL); + memset(buf, 0xee, bytes); + for (i = 0; i < numcb; i++) + gru_vset(cb0 + i * GRU_HANDLE_STRIDE, uv_gpa(&buf[i * 4]), 0, + XTYPE_DW, 4, 1, IMA_INTERRUPT); + + ret = 0; + for (k = 0; k < numcb; k++) { + gru_wait_async_cbr(han); + for (i = 0; i < numcb; i++) { + cb = cb0 + i * GRU_HANDLE_STRIDE; + istatus = gru_check_status(cb); + if (istatus == CBS_ACTIVE) + continue; + if (istatus == CBS_EXCEPTION) + ret = -EFAULT; + else if (buf[i] || buf[i + 1] || buf[i + 2] || + buf[i + 3]) + ret = -EIO; + } + } + BUG_ON(cmp.done); + + gru_unlock_async_resource(han); + gru_release_async_resources(han); +done: + kfree(buf); + return ret; +} + +/* + * Debugging only. User hook for various kernel tests + * of driver & gru. + */ +int gru_ktest(unsigned long arg) +{ + int ret = -EINVAL; + + switch (arg & 0xff) { + case 0: + ret = quicktest0(arg); + break; + case 1: + ret = quicktest1(arg); + break; + case 2: + ret = quicktest2(arg); + break; + } + return ret; + +} int gru_kservices_init(struct gru_state *gru) { @@ -891,9 +1014,6 @@ int gru_kservices_init(struct gru_state *gru) return 0; init_rwsem(&bs->bs_kgts_sema); - - if (gru_options & GRU_QUICKLOOK) - quicktest(); return 0; } diff --git a/drivers/misc/sgi-gru/grulib.h b/drivers/misc/sgi-gru/grulib.h index 6ab872665e7..87586551d16 100644 --- a/drivers/misc/sgi-gru/grulib.h +++ b/drivers/misc/sgi-gru/grulib.h @@ -56,6 +56,9 @@ /* Get some config options (primarily for tests & emulator) */ #define GRU_GET_CONFIG_INFO _IOWR(GRU_IOCTL_NUM, 51, void *) +/* Various kernel self-tests */ +#define GRU_KTEST _IOWR(GRU_IOCTL_NUM, 52, void *) + #define CONTEXT_WINDOW_BYTES(th) (GRU_GSEG_PAGESIZE * (th)) #define THREAD_POINTER(p, th) (p + GRU_GSEG_PAGESIZE * (th)) diff --git a/drivers/misc/sgi-gru/grutables.h b/drivers/misc/sgi-gru/grutables.h index ca81800146f..6dfb3e69411 100644 --- a/drivers/misc/sgi-gru/grutables.h +++ b/drivers/misc/sgi-gru/grutables.h @@ -258,7 +258,6 @@ extern struct mcs_op_statistic mcs_op_statistics[mcsop_last]; #define OPT_DPRINT 1 #define OPT_STATS 2 -#define GRU_QUICKLOOK 4 #define IRQ_GRU 110 /* Starting IRQ number for interrupts */ @@ -662,6 +661,7 @@ extern int gru_fault(struct vm_area_struct *, struct vm_fault *vmf); extern struct gru_mm_struct *gru_register_mmu_notifier(void); extern void gru_drop_mmu_notifier(struct gru_mm_struct *gms); +extern int gru_ktest(unsigned long arg); extern void gru_flush_tlb_range(struct gru_mm_struct *gms, unsigned long start, unsigned long len); -- cgit v1.2.3-70-g09d2 From 270952a907220c0331fdaecbb55df892921c5e2d Mon Sep 17 00:00:00 2001 From: Jack Steiner Date: Wed, 17 Jun 2009 16:28:27 -0700 Subject: gru: update to rev 0.9 of gru spec Update GRU driver to the latest version of the GRU spec. This consists of minor updates: - changes & additions to error status bits - new restriction on handling of TLB misses while in FMM mode - new field (not used by software) in TFH Signed-off-by: Jack Steiner Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/misc/sgi-gru/gru_instructions.h | 19 ++++++++++++------- drivers/misc/sgi-gru/grufault.c | 8 ++++++-- drivers/misc/sgi-gru/gruhandles.h | 3 ++- drivers/misc/sgi-gru/grukservices.c | 3 ++- drivers/misc/sgi-gru/gruprocfs.c | 1 + drivers/misc/sgi-gru/grutables.h | 1 + 6 files changed, 24 insertions(+), 11 deletions(-) (limited to 'drivers/misc') diff --git a/drivers/misc/sgi-gru/gru_instructions.h b/drivers/misc/sgi-gru/gru_instructions.h index 2feb885ca96..eb9140d32e6 100644 --- a/drivers/misc/sgi-gru/gru_instructions.h +++ b/drivers/misc/sgi-gru/gru_instructions.h @@ -253,32 +253,37 @@ struct gru_instruction { #define CBE_CAUSE_HA_RESPONSE_FATAL (1 << 13) #define CBE_CAUSE_HA_RESPONSE_NON_FATAL (1 << 14) #define CBE_CAUSE_ADDRESS_SPACE_DECODE_ERROR (1 << 15) -#define CBE_CAUSE_RESPONSE_DATA_ERROR (1 << 16) -#define CBE_CAUSE_PROTOCOL_STATE_DATA_ERROR (1 << 17) +#define CBE_CAUSE_PROTOCOL_STATE_DATA_ERROR (1 << 16) +#define CBE_CAUSE_RA_RESPONSE_DATA_ERROR (1 << 17) +#define CBE_CAUSE_HA_RESPONSE_DATA_ERROR (1 << 18) /* CBE cbrexecstatus bits */ #define CBR_EXS_ABORT_OCC_BIT 0 #define CBR_EXS_INT_OCC_BIT 1 #define CBR_EXS_PENDING_BIT 2 #define CBR_EXS_QUEUED_BIT 3 -#define CBR_EXS_TLBHW_BIT 4 +#define CBR_EXS_TLB_INVAL_BIT 4 #define CBR_EXS_EXCEPTION_BIT 5 #define CBR_EXS_ABORT_OCC (1 << CBR_EXS_ABORT_OCC_BIT) #define CBR_EXS_INT_OCC (1 << CBR_EXS_INT_OCC_BIT) #define CBR_EXS_PENDING (1 << CBR_EXS_PENDING_BIT) #define CBR_EXS_QUEUED (1 << CBR_EXS_QUEUED_BIT) -#define CBR_EXS_TLBHW (1 << CBR_EXS_TLBHW_BIT) +#define CBR_TLB_INVAL (1 << CBR_EXS_TLB_INVAL_BIT) #define CBR_EXS_EXCEPTION (1 << CBR_EXS_EXCEPTION_BIT) /* * Exceptions are retried for the following cases. If any OTHER bits are set * in ecause, the exception is not retryable. */ -#define EXCEPTION_RETRY_BITS (CBE_CAUSE_RESPONSE_DATA_ERROR | \ - CBE_CAUSE_RA_REQUEST_TIMEOUT | \ +#define EXCEPTION_RETRY_BITS (CBE_CAUSE_EXECUTION_HW_ERROR | \ CBE_CAUSE_TLBHW_ERROR | \ - CBE_CAUSE_HA_REQUEST_TIMEOUT) + CBE_CAUSE_RA_REQUEST_TIMEOUT | \ + CBE_CAUSE_RA_RESPONSE_NON_FATAL | \ + CBE_CAUSE_HA_RESPONSE_NON_FATAL | \ + CBE_CAUSE_RA_RESPONSE_DATA_ERROR | \ + CBE_CAUSE_HA_RESPONSE_DATA_ERROR \ + ) /* Message queue head structure */ union gru_mesqhead { diff --git a/drivers/misc/sgi-gru/grufault.c b/drivers/misc/sgi-gru/grufault.c index 8443e90f9f6..a489807613f 100644 --- a/drivers/misc/sgi-gru/grufault.c +++ b/drivers/misc/sgi-gru/grufault.c @@ -339,8 +339,12 @@ static int gru_try_dropin(struct gru_thread_state *gts, * Might be a hardware race OR a stupid user. Ignore FMM because FMM * is a transient state. */ - if (tfh->status != TFHSTATUS_EXCEPTION) - goto failnoexception; + if (tfh->status != TFHSTATUS_EXCEPTION) { + gru_flush_cache(tfh); + if (tfh->status != TFHSTATUS_EXCEPTION) + goto failnoexception; + STAT(tfh_stale_on_fault); + } if (tfh->state == TFHSTATE_IDLE) goto failidle; if (tfh->state == TFHSTATE_MISS_FMM && cb) diff --git a/drivers/misc/sgi-gru/gruhandles.h b/drivers/misc/sgi-gru/gruhandles.h index 9f41e2cc09d..99ec82678f5 100644 --- a/drivers/misc/sgi-gru/gruhandles.h +++ b/drivers/misc/sgi-gru/gruhandles.h @@ -255,7 +255,8 @@ struct gru_tlb_fault_handle { unsigned int state:3; unsigned int fill3:1; - unsigned int cause:7; + unsigned int cause:6; + unsigned int cb_int:1; unsigned int fill4:1; unsigned int indexway:12; /* DW 0 - high 32 */ diff --git a/drivers/misc/sgi-gru/grukservices.c b/drivers/misc/sgi-gru/grukservices.c index 7d7952b27e0..ba6fcd963f3 100644 --- a/drivers/misc/sgi-gru/grukservices.c +++ b/drivers/misc/sgi-gru/grukservices.c @@ -406,7 +406,8 @@ static int gru_retry_exception(void *cb) return CBS_IDLE; gru_get_cb_exception_detail(cb, &excdet); - if (excdet.ecause & ~EXCEPTION_RETRY_BITS) + if ((excdet.ecause & ~EXCEPTION_RETRY_BITS) || + (excdet.cbrexecstatus & CBR_EXS_ABORT_OCC)) break; if (retry-- == 0) break; diff --git a/drivers/misc/sgi-gru/gruprocfs.c b/drivers/misc/sgi-gru/gruprocfs.c index 6ef4cb4b84c..b5b9cf5c182 100644 --- a/drivers/misc/sgi-gru/gruprocfs.c +++ b/drivers/misc/sgi-gru/gruprocfs.c @@ -89,6 +89,7 @@ static int statistics_show(struct seq_file *s, void *p) printstat(s, tlb_dropin_fail_fmm); printstat(s, tlb_dropin_fail_no_exception); printstat(s, tlb_dropin_fail_no_exception_war); + printstat(s, tfh_stale_on_fault); printstat(s, mmu_invalidate_range); printstat(s, mmu_invalidate_page); printstat(s, mmu_clear_flush_young); diff --git a/drivers/misc/sgi-gru/grutables.h b/drivers/misc/sgi-gru/grutables.h index 6dfb3e69411..246c63883eb 100644 --- a/drivers/misc/sgi-gru/grutables.h +++ b/drivers/misc/sgi-gru/grutables.h @@ -212,6 +212,7 @@ struct gru_stats_s { atomic_long_t tlb_dropin_fail_fmm; atomic_long_t tlb_dropin_fail_no_exception; atomic_long_t tlb_dropin_fail_no_exception_war; + atomic_long_t tfh_stale_on_fault; atomic_long_t mmu_invalidate_range; atomic_long_t mmu_invalidate_page; atomic_long_t mmu_clear_flush_young; -- cgit v1.2.3-70-g09d2 From 1a2c09e3b41e334b6651d53b39cfe8ceefbc45f8 Mon Sep 17 00:00:00 2001 From: Jack Steiner Date: Wed, 17 Jun 2009 16:28:28 -0700 Subject: gru: fix cache coherency issues with instruction retry Fix two problems related to GRU instruction failures. Cache coherency is not maintained for CBEs except when loading or unloading contexts. When reading a CBE to extract error information, the CBE must first be flushed from the cache. The function that reads kerrnel CBEs was reading the wrong CBE. Signed-off-by: Jack Steiner Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/misc/sgi-gru/grufault.c | 3 ++- drivers/misc/sgi-gru/grufile.c | 2 +- drivers/misc/sgi-gru/grukservices.c | 12 ++++++++++-- drivers/misc/sgi-gru/grutables.h | 1 + 4 files changed, 14 insertions(+), 4 deletions(-) (limited to 'drivers/misc') diff --git a/drivers/misc/sgi-gru/grufault.c b/drivers/misc/sgi-gru/grufault.c index a489807613f..6d0681236db 100644 --- a/drivers/misc/sgi-gru/grufault.c +++ b/drivers/misc/sgi-gru/grufault.c @@ -614,7 +614,7 @@ int gru_get_exception_detail(unsigned long arg) } else if (gts->ts_gru) { cbrnum = thread_cbr_number(gts, ucbnum); cbe = get_cbe_by_index(gts->ts_gru, cbrnum); - prefetchw(cbe);/* Harmless on hardware, required for emulator */ + gru_flush_cache(cbe); /* CBE not coherent */ excdet.opc = cbe->opccpy; excdet.exopc = cbe->exopccpy; excdet.ecause = cbe->ecause; @@ -622,6 +622,7 @@ int gru_get_exception_detail(unsigned long arg) excdet.exceptdet1 = cbe->idef3upd; excdet.cbrstate = cbe->cbrstate; excdet.cbrexecstatus = cbe->cbrexecstatus; + gru_flush_cache(cbe); ret = 0; } else { ret = -EAGAIN; diff --git a/drivers/misc/sgi-gru/grufile.c b/drivers/misc/sgi-gru/grufile.c index 796ac704795..bfc88d1b2a5 100644 --- a/drivers/misc/sgi-gru/grufile.c +++ b/drivers/misc/sgi-gru/grufile.c @@ -46,6 +46,7 @@ struct gru_blade_state *gru_base[GRU_MAX_BLADES] __read_mostly; unsigned long gru_start_paddr __read_mostly; +void *gru_start_vaddr __read_mostly; unsigned long gru_end_paddr __read_mostly; unsigned int gru_max_gids __read_mostly; struct gru_stats_s gru_stats; @@ -376,7 +377,6 @@ static int __init gru_init(void) { int ret, irq, chip; char id[10]; - void *gru_start_vaddr; if (!is_uv_system()) return 0; diff --git a/drivers/misc/sgi-gru/grukservices.c b/drivers/misc/sgi-gru/grukservices.c index ba6fcd963f3..7586b89fd0d 100644 --- a/drivers/misc/sgi-gru/grukservices.c +++ b/drivers/misc/sgi-gru/grukservices.c @@ -98,6 +98,9 @@ #define ASYNC_HAN_TO_BID(h) ((h) - 1) #define ASYNC_BID_TO_HAN(b) ((b) + 1) #define ASYNC_HAN_TO_BS(h) gru_base[ASYNC_HAN_TO_BID(h)] +#define KCB_TO_GID(cb) ((cb - gru_start_vaddr) / \ + (GRU_SIZE * GRU_CHIPLETS_PER_BLADE)) +#define KCB_TO_BS(cb) gru_base[KCB_TO_GID(cb)] #define GRU_NUM_KERNEL_CBR 1 #define GRU_NUM_KERNEL_DSR_BYTES 256 @@ -354,14 +357,19 @@ int gru_get_cb_exception_detail(void *cb, struct control_block_extended_exc_detail *excdet) { struct gru_control_block_extended *cbe; + struct gru_blade_state *bs; + int cbrnum; - cbe = get_cbe(GRUBASE(cb), get_cb_number(cb)); - prefetchw(cbe); /* Harmless on hardware, required for emulator */ + bs = KCB_TO_BS(cb); + cbrnum = thread_cbr_number(bs->bs_kgts, get_cb_number(cb)); + cbe = get_cbe(GRUBASE(cb), cbrnum); + gru_flush_cache(cbe); /* CBE not coherent */ excdet->opc = cbe->opccpy; excdet->exopc = cbe->exopccpy; excdet->ecause = cbe->ecause; excdet->exceptdet0 = cbe->idef1upd; excdet->exceptdet1 = cbe->idef3upd; + gru_flush_cache(cbe); return 0; } diff --git a/drivers/misc/sgi-gru/grutables.h b/drivers/misc/sgi-gru/grutables.h index 246c63883eb..665704683ab 100644 --- a/drivers/misc/sgi-gru/grutables.h +++ b/drivers/misc/sgi-gru/grutables.h @@ -153,6 +153,7 @@ extern struct gru_stats_s gru_stats; extern struct gru_blade_state *gru_base[]; extern unsigned long gru_start_paddr, gru_end_paddr; +extern void *gru_start_vaddr; extern unsigned int gru_max_gids; #define GRU_MAX_BLADES MAX_NUMNODES -- cgit v1.2.3-70-g09d2 From d5826dd6002f23940458860701ce22fba9df2614 Mon Sep 17 00:00:00 2001 From: Jack Steiner Date: Wed, 17 Jun 2009 16:28:28 -0700 Subject: gru: add user request to explicitly unload a gru context Add user function to explicitly unload GRU kernel contexts from the GRU. Only contexts that are not in-use will be unloaded. This function is primarily for testing. It is not expected that this will be used in normal production systems. Signed-off-by: Jack Steiner Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/misc/sgi-gru/grufile.c | 10 +++---- drivers/misc/sgi-gru/grukservices.c | 55 +++++++++++++++++++++++-------------- drivers/misc/sgi-gru/grutables.h | 4 +-- 3 files changed, 41 insertions(+), 28 deletions(-) (limited to 'drivers/misc') diff --git a/drivers/misc/sgi-gru/grufile.c b/drivers/misc/sgi-gru/grufile.c index bfc88d1b2a5..e22012db239 100644 --- a/drivers/misc/sgi-gru/grufile.c +++ b/drivers/misc/sgi-gru/grufile.c @@ -287,7 +287,6 @@ static void gru_init_chiplet(struct gru_state *gru, unsigned long paddr, gru_dbg(grudev, "bid %d, nid %d, gid %d, vaddr %p (0x%lx)\n", bid, nid, gru->gs_gid, gru->gs_gru_base_vaddr, gru->gs_gru_base_paddr); - gru_kservices_init(gru); } static int gru_init_tables(unsigned long gru_base_paddr, void *gru_base_vaddr) @@ -314,6 +313,7 @@ static int gru_init_tables(unsigned long gru_base_paddr, void *gru_base_vaddr) memset(gru_base[bid], 0, sizeof(struct gru_blade_state)); gru_base[bid]->bs_lru_gru = &gru_base[bid]->bs_grus[0]; spin_lock_init(&gru_base[bid]->bs_lock); + init_rwsem(&gru_base[bid]->bs_kgts_sema); dsrbytes = 0; cbrs = 0; @@ -426,6 +426,7 @@ static int __init gru_init(void) printk(KERN_ERR "%s: init tables failed\n", GRU_DRIVER_ID_STR); goto exit3; } + gru_kservices_init(); printk(KERN_INFO "%s: v%s\n", GRU_DRIVER_ID_STR, GRU_DRIVER_VERSION_STR); @@ -444,7 +445,7 @@ exit1: static void __exit gru_exit(void) { - int i, bid, gid; + int i, bid; int order = get_order(sizeof(struct gru_state) * GRU_CHIPLETS_PER_BLADE); @@ -453,10 +454,7 @@ static void __exit gru_exit(void) for (i = 0; i < GRU_CHIPLETS_PER_BLADE; i++) free_irq(IRQ_GRU + i, NULL); - - foreach_gid(gid) - gru_kservices_exit(GID_TO_GRU(gid)); - + gru_kservices_exit(); for (bid = 0; bid < GRU_MAX_BLADES; bid++) free_pages((unsigned long)gru_base[bid], order); diff --git a/drivers/misc/sgi-gru/grukservices.c b/drivers/misc/sgi-gru/grukservices.c index 7586b89fd0d..5078f57da88 100644 --- a/drivers/misc/sgi-gru/grukservices.c +++ b/drivers/misc/sgi-gru/grukservices.c @@ -187,6 +187,34 @@ static void gru_load_kernel_context(struct gru_blade_state *bs, int blade_id) downgrade_write(&bs->bs_kgts_sema); } +/* + * Free all kernel contexts that are not currently in use. + * Returns 0 if all freed, else number of inuse context. + */ +static int gru_free_kernel_contexts(void) +{ + struct gru_blade_state *bs; + struct gru_thread_state *kgts; + int bid, ret = 0; + + for (bid = 0; bid < GRU_MAX_BLADES; bid++) { + bs = gru_base[bid]; + if (!bs) + continue; + if (down_write_trylock(&bs->bs_kgts_sema)) { + kgts = bs->bs_kgts; + if (kgts && kgts->ts_gru) + gru_unload_context(kgts, 0); + kfree(kgts); + bs->bs_kgts = NULL; + up_write(&bs->bs_kgts_sema); + } else { + ret++; + } + } + return ret; +} + /* * Lock & load the kernel context for the specified blade. */ @@ -1009,35 +1037,22 @@ int gru_ktest(unsigned long arg) case 2: ret = quicktest2(arg); break; + case 99: + ret = gru_free_kernel_contexts(); + break; } return ret; } -int gru_kservices_init(struct gru_state *gru) +int gru_kservices_init(void) { - struct gru_blade_state *bs; - - bs = gru->gs_blade; - if (gru != &bs->bs_grus[0]) - return 0; - - init_rwsem(&bs->bs_kgts_sema); return 0; } -void gru_kservices_exit(struct gru_state *gru) +void gru_kservices_exit(void) { - struct gru_blade_state *bs; - struct gru_thread_state *kgts; - - bs = gru->gs_blade; - if (gru != &bs->bs_grus[0]) - return; - - kgts = bs->bs_kgts; - if (kgts && kgts->ts_gru) - gru_unload_context(kgts, 0); - kfree(kgts); + if (gru_free_kernel_contexts()) + BUG(); } diff --git a/drivers/misc/sgi-gru/grutables.h b/drivers/misc/sgi-gru/grutables.h index 665704683ab..9761bfee866 100644 --- a/drivers/misc/sgi-gru/grutables.h +++ b/drivers/misc/sgi-gru/grutables.h @@ -638,8 +638,8 @@ extern void gru_unload_context(struct gru_thread_state *gts, int savestate); extern int gru_update_cch(struct gru_thread_state *gts, int force_unload); extern void gts_drop(struct gru_thread_state *gts); extern void gru_tgh_flush_init(struct gru_state *gru); -extern int gru_kservices_init(struct gru_state *gru); -extern void gru_kservices_exit(struct gru_state *gru); +extern int gru_kservices_init(void); +extern void gru_kservices_exit(void); extern int gru_dump_chiplet_request(unsigned long arg); extern irqreturn_t gru_intr(int irq, void *dev_id); extern int gru_handle_user_call_os(unsigned long address); -- cgit v1.2.3-70-g09d2 From d6e2fbce0d70c2072a1c478dbd37b34d27129d74 Mon Sep 17 00:00:00 2001 From: Jack Steiner Date: Wed, 17 Jun 2009 16:28:29 -0700 Subject: gru: fix automatic retry of gru instruction failures Fix bug in automatic retry of GRU instruction failures. CBR substatus (message queue failure) was being checked incorrectly. Signed-off-by: Jack Steiner Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/misc/sgi-gru/grukservices.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'drivers/misc') diff --git a/drivers/misc/sgi-gru/grukservices.c b/drivers/misc/sgi-gru/grukservices.c index 5078f57da88..eedbf9c3276 100644 --- a/drivers/misc/sgi-gru/grukservices.c +++ b/drivers/misc/sgi-gru/grukservices.c @@ -436,11 +436,10 @@ static int gru_retry_exception(void *cb) int retry = EXCEPTION_RETRY_LIMIT; while (1) { - if (gru_get_cb_message_queue_substatus(cb)) - break; if (gru_wait_idle_or_exception(gen) == CBS_IDLE) return CBS_IDLE; - + if (gru_get_cb_message_queue_substatus(cb)) + return CBS_EXCEPTION; gru_get_cb_exception_detail(cb, &excdet); if ((excdet.ecause & ~EXCEPTION_RETRY_BITS) || (excdet.cbrexecstatus & CBR_EXS_ABORT_OCC)) -- cgit v1.2.3-70-g09d2 From 7e796a72a2691d7094fd62da61097294d0d59ce4 Mon Sep 17 00:00:00 2001 From: Jack Steiner Date: Wed, 17 Jun 2009 16:28:30 -0700 Subject: gru: collect per-context user statistics Collect GRU statistics for each user GRU context. Statistics are kept for TLB misses & content resource contention. Add user request for retrieving the statistics. Signed-off-by: Jack Steiner Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/misc/sgi-gru/grufault.c | 27 +++++++++++++++++++++++++++ drivers/misc/sgi-gru/grufile.c | 3 +++ drivers/misc/sgi-gru/grulib.h | 18 ++++++++++++++++++ drivers/misc/sgi-gru/grumain.c | 1 + drivers/misc/sgi-gru/grutables.h | 3 +++ 5 files changed, 52 insertions(+) (limited to 'drivers/misc') diff --git a/drivers/misc/sgi-gru/grufault.c b/drivers/misc/sgi-gru/grufault.c index 6d0681236db..cdd151b30dc 100644 --- a/drivers/misc/sgi-gru/grufault.c +++ b/drivers/misc/sgi-gru/grufault.c @@ -498,6 +498,7 @@ irqreturn_t gru_intr(int irq, void *dev_id) */ if (!gts->ts_force_cch_reload && down_read_trylock(>s->ts_mm->mmap_sem)) { + gts->ustats.fmm_tlbdropin++; gru_try_dropin(gts, tfh, NULL); up_read(>s->ts_mm->mmap_sem); } else { @@ -516,6 +517,7 @@ static int gru_user_dropin(struct gru_thread_state *gts, struct gru_mm_struct *gms = gts->ts_gms; int ret; + gts->ustats.upm_tlbdropin++; while (1) { wait_event(gms->ms_wait_queue, atomic_read(&gms->ms_range_active) == 0); @@ -718,6 +720,31 @@ int gru_user_flush_tlb(unsigned long arg) return 0; } +/* + * Fetch GSEG statisticss + */ +long gru_get_gseg_statistics(unsigned long arg) +{ + struct gru_thread_state *gts; + struct gru_get_gseg_statistics_req req; + + if (copy_from_user(&req, (void __user *)arg, sizeof(req))) + return -EFAULT; + + gts = gru_find_lock_gts(req.gseg); + if (gts) { + memcpy(&req.stats, >s->ustats, sizeof(gts->ustats)); + gru_unlock_gts(gts); + } else { + memset(&req.stats, 0, sizeof(gts->ustats)); + } + + if (copy_to_user((void __user *)arg, &req, sizeof(req))) + return -EFAULT; + + return 0; +} + /* * Register the current task as the user of the GSEG slice. * Needed for TLB fault interrupt targeting. diff --git a/drivers/misc/sgi-gru/grufile.c b/drivers/misc/sgi-gru/grufile.c index e22012db239..0d1c8b8c1c1 100644 --- a/drivers/misc/sgi-gru/grufile.c +++ b/drivers/misc/sgi-gru/grufile.c @@ -251,6 +251,9 @@ static long gru_file_unlocked_ioctl(struct file *file, unsigned int req, case GRU_USER_CALL_OS: err = gru_handle_user_call_os(arg); break; + case GRU_GET_GSEG_STATISTICS: + err = gru_get_gseg_statistics(arg); + break; case GRU_KTEST: err = gru_ktest(arg); break; diff --git a/drivers/misc/sgi-gru/grulib.h b/drivers/misc/sgi-gru/grulib.h index 87586551d16..c5865dd19ee 100644 --- a/drivers/misc/sgi-gru/grulib.h +++ b/drivers/misc/sgi-gru/grulib.h @@ -50,6 +50,9 @@ /* For dumpping GRU chiplet state */ #define GRU_DUMP_CHIPLET_STATE _IOWR(GRU_IOCTL_NUM, 11, void *) +/* For getting gseg statistics */ +#define GRU_GET_GSEG_STATISTICS _IOWR(GRU_IOCTL_NUM, 12, void *) + /* For user TLB flushing (primarily for tests) */ #define GRU_USER_FLUSH_TLB _IOWR(GRU_IOCTL_NUM, 50, void *) @@ -61,6 +64,21 @@ #define CONTEXT_WINDOW_BYTES(th) (GRU_GSEG_PAGESIZE * (th)) #define THREAD_POINTER(p, th) (p + GRU_GSEG_PAGESIZE * (th)) +#define GSEG_START(cb) ((void *)((unsigned long)(cb) & ~(GRU_GSEG_PAGESIZE - 1))) + +/* + * Statictics kept on a per-GTS basis. + */ +struct gts_statistics { + unsigned long fmm_tlbdropin; + unsigned long upm_tlbdropin; + unsigned long context_stolen; +}; + +struct gru_get_gseg_statistics_req { + unsigned long gseg; + struct gts_statistics stats; +}; /* * Structure used to pass TLB flush parameters to the driver diff --git a/drivers/misc/sgi-gru/grumain.c b/drivers/misc/sgi-gru/grumain.c index e38a0f1775f..347004e4f59 100644 --- a/drivers/misc/sgi-gru/grumain.c +++ b/drivers/misc/sgi-gru/grumain.c @@ -744,6 +744,7 @@ void gru_steal_context(struct gru_thread_state *gts, int blade_id) spin_unlock(&blade->bs_lock); if (ngts) { + gts->ustats.context_stolen++; ngts->ts_steal_jiffies = jiffies; gru_unload_context(ngts, is_kernel_context(ngts) ? 0 : 1); gts_stolen(ngts, blade); diff --git a/drivers/misc/sgi-gru/grutables.h b/drivers/misc/sgi-gru/grutables.h index 9761bfee866..63b76e2732f 100644 --- a/drivers/misc/sgi-gru/grutables.h +++ b/drivers/misc/sgi-gru/grutables.h @@ -148,6 +148,7 @@ #include #include #include "gru.h" +#include "grulib.h" #include "gruhandles.h" extern struct gru_stats_s gru_stats; @@ -388,6 +389,7 @@ struct gru_thread_state { allocated CB */ int ts_data_valid; /* Indicates if ts_gdata has valid data */ + struct gts_statistics ustats; /* User statistics */ unsigned long ts_gdata[0]; /* save area for GRU data (CB, DS, CBE) */ }; @@ -641,6 +643,7 @@ extern void gru_tgh_flush_init(struct gru_state *gru); extern int gru_kservices_init(void); extern void gru_kservices_exit(void); extern int gru_dump_chiplet_request(unsigned long arg); +extern long gru_get_gseg_statistics(unsigned long arg); extern irqreturn_t gru_intr(int irq, void *dev_id); extern int gru_handle_user_call_os(unsigned long address); extern int gru_user_flush_tlb(unsigned long arg); -- cgit v1.2.3-70-g09d2 From 6471cd4d6c2526f3df693895a6bfa70353fad3f7 Mon Sep 17 00:00:00 2001 From: Jack Steiner Date: Wed, 17 Jun 2009 16:28:31 -0700 Subject: gru: delete user request for fetching chiplet status Delete the user request for fetching the status of a GRU chiplet. This request has been made obsolete by other changes. Note: this is not a change to a user API - there are no compatibility issues with this change. Signed-off-by: Jack Steiner Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/misc/sgi-gru/grufile.c | 38 -------------------------------------- drivers/misc/sgi-gru/grulib.h | 3 --- 2 files changed, 41 deletions(-) (limited to 'drivers/misc') diff --git a/drivers/misc/sgi-gru/grufile.c b/drivers/misc/sgi-gru/grufile.c index 0d1c8b8c1c1..2b72629db91 100644 --- a/drivers/misc/sgi-gru/grufile.c +++ b/drivers/misc/sgi-gru/grufile.c @@ -182,41 +182,6 @@ static long gru_get_config_info(unsigned long arg) return 0; } -/* - * Get GRU chiplet status - */ -static long gru_get_chiplet_status(unsigned long arg) -{ - struct gru_state *gru; - struct gru_chiplet_info info; - - if (copy_from_user(&info, (void __user *)arg, sizeof(info))) - return -EFAULT; - - if (info.node == -1) - info.node = numa_node_id(); - if (info.node >= num_possible_nodes() || - info.chiplet >= GRU_CHIPLETS_PER_HUB || - info.node < 0 || info.chiplet < 0) - return -EINVAL; - - info.blade = uv_node_to_blade_id(info.node); - gru = get_gru(info.blade, info.chiplet); - - info.total_dsr_bytes = GRU_NUM_DSR_BYTES; - info.total_cbr = GRU_NUM_CB; - info.total_user_dsr_bytes = GRU_NUM_DSR_BYTES - - gru->gs_reserved_dsr_bytes; - info.total_user_cbr = GRU_NUM_CB - gru->gs_reserved_cbrs; - info.free_user_dsr_bytes = hweight64(gru->gs_dsr_map) * - GRU_DSR_AU_BYTES; - info.free_user_cbr = hweight64(gru->gs_cbr_map) * GRU_CBR_AU_SIZE; - - if (copy_to_user((void __user *)arg, &info, sizeof(info))) - return -EFAULT; - return 0; -} - /* * gru_file_unlocked_ioctl * @@ -242,9 +207,6 @@ static long gru_file_unlocked_ioctl(struct file *file, unsigned int req, case GRU_USER_UNLOAD_CONTEXT: err = gru_user_unload_context(arg); break; - case GRU_GET_CHIPLET_STATUS: - err = gru_get_chiplet_status(arg); - break; case GRU_USER_FLUSH_TLB: err = gru_user_flush_tlb(arg); break; diff --git a/drivers/misc/sgi-gru/grulib.h b/drivers/misc/sgi-gru/grulib.h index c5865dd19ee..8ed6acbc47c 100644 --- a/drivers/misc/sgi-gru/grulib.h +++ b/drivers/misc/sgi-gru/grulib.h @@ -44,9 +44,6 @@ /* For user unload context */ #define GRU_USER_UNLOAD_CONTEXT _IOWR(GRU_IOCTL_NUM, 9, void *) -/* For fetching GRU chiplet status */ -#define GRU_GET_CHIPLET_STATUS _IOWR(GRU_IOCTL_NUM, 10, void *) - /* For dumpping GRU chiplet state */ #define GRU_DUMP_CHIPLET_STATE _IOWR(GRU_IOCTL_NUM, 11, void *) -- cgit v1.2.3-70-g09d2 From 9f2501142bd3c496afa7efdf18583aab56fe3134 Mon Sep 17 00:00:00 2001 From: Jack Steiner Date: Wed, 17 Jun 2009 16:28:31 -0700 Subject: gru: cleanup gru inline functions Cleanup of GRU inline functions to eliminate unnecessary inline code. Update function descriptions. Signed-off-by: Jack Steiner Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/misc/sgi-gru/gru_instructions.h | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) (limited to 'drivers/misc') diff --git a/drivers/misc/sgi-gru/gru_instructions.h b/drivers/misc/sgi-gru/gru_instructions.h index eb9140d32e6..3c9c06618e6 100644 --- a/drivers/misc/sgi-gru/gru_instructions.h +++ b/drivers/misc/sgi-gru/gru_instructions.h @@ -623,9 +623,11 @@ static inline int gru_get_cb_substatus(void *cb) return cbs->isubstatus; } -/* Check the status of a CB. If the CB is in UPM mode, call the - * OS to handle the UPM status. - * Returns the CB status field value (0 for normal completion) +/* + * User interface to check an instruction status. UPM and exceptions + * are handled automatically. However, this function does NOT wait + * for an active instruction to complete. + * */ static inline int gru_check_status(void *cb) { @@ -633,34 +635,31 @@ static inline int gru_check_status(void *cb) int ret; ret = cbs->istatus; - if (ret == CBS_CALL_OS) + if (ret != CBS_ACTIVE) ret = gru_check_status_proc(cb); return ret; } -/* Wait for CB to complete. - * Returns the CB status field value (0 for normal completion) +/* + * User interface (via inline function) to wait for an instruction + * to complete. Completion status (IDLE or EXCEPTION is returned + * to the user. Exception due to hardware errors are automatically + * retried before returning an exception. + * */ static inline int gru_wait(void *cb) { - struct gru_control_block_status *cbs = (void *)cb; - int ret = cbs->istatus; - - if (ret != CBS_IDLE) - ret = gru_wait_proc(cb); - return ret; + return gru_wait_proc(cb); } -/* Wait for CB to complete. Aborts program if error. (Note: error does NOT +/* + * Wait for CB to complete. Aborts program if error. (Note: error does NOT * mean TLB mis - only fatal errors such as memory parity error or user * bugs will cause termination. */ static inline void gru_wait_abort(void *cb) { - struct gru_control_block_status *cbs = (void *)cb; - - if (cbs->istatus != CBS_IDLE) - gru_wait_abort_proc(cb); + gru_wait_abort_proc(cb); } -- cgit v1.2.3-70-g09d2 From 92b39388eeb45326feb0fa8bd69dbbce66c9efbf Mon Sep 17 00:00:00 2001 From: Jack Steiner Date: Wed, 17 Jun 2009 16:28:32 -0700 Subject: gru: generic infrastructure for context options Change the user GRU request for specifying the "task_slice" option to use a generic infrastructure that can be expanded in the future to include additional context options. No new capabilities are added with this patch. Signed-off-by: Jack Steiner Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/misc/sgi-gru/grufault.c | 24 ++++++++++++++++++------ drivers/misc/sgi-gru/grufile.c | 4 ++-- drivers/misc/sgi-gru/grulib.h | 14 ++++++++++++-- drivers/misc/sgi-gru/gruprocfs.c | 2 +- drivers/misc/sgi-gru/grutables.h | 4 ++-- 5 files changed, 35 insertions(+), 13 deletions(-) (limited to 'drivers/misc') diff --git a/drivers/misc/sgi-gru/grufault.c b/drivers/misc/sgi-gru/grufault.c index cdd151b30dc..b894b7ed9c3 100644 --- a/drivers/misc/sgi-gru/grufault.c +++ b/drivers/misc/sgi-gru/grufault.c @@ -749,18 +749,30 @@ long gru_get_gseg_statistics(unsigned long arg) * Register the current task as the user of the GSEG slice. * Needed for TLB fault interrupt targeting. */ -int gru_set_task_slice(long address) +int gru_set_context_option(unsigned long arg) { struct gru_thread_state *gts; + struct gru_set_context_option_req req; + int ret = 0; - STAT(set_task_slice); - gru_dbg(grudev, "address 0x%lx\n", address); - gts = gru_alloc_locked_gts(address); + STAT(set_context_option); + if (copy_from_user(&req, (void __user *)arg, sizeof(req))) + return -EFAULT; + gru_dbg(grudev, "op %d, gseg 0x%lx, value1 0x%lx\n", req.op, req.gseg, req.val1); + + gts = gru_alloc_locked_gts(req.gseg); if (!gts) return -EINVAL; - gts->ts_tgid_owner = current->tgid; + switch (req.op) { + case sco_gseg_owner: + /* Register the current task as the GSEG owner */ + gts->ts_tgid_owner = current->tgid; + break; + default: + ret = -EINVAL; + } gru_unlock_gts(gts); - return 0; + return ret; } diff --git a/drivers/misc/sgi-gru/grufile.c b/drivers/misc/sgi-gru/grufile.c index 2b72629db91..fa2d93a9fb8 100644 --- a/drivers/misc/sgi-gru/grufile.c +++ b/drivers/misc/sgi-gru/grufile.c @@ -198,8 +198,8 @@ static long gru_file_unlocked_ioctl(struct file *file, unsigned int req, case GRU_CREATE_CONTEXT: err = gru_create_new_context(arg); break; - case GRU_SET_TASK_SLICE: - err = gru_set_task_slice(arg); + case GRU_SET_CONTEXT_OPTION: + err = gru_set_context_option(arg); break; case GRU_USER_GET_EXCEPTION_DETAIL: err = gru_get_exception_detail(arg); diff --git a/drivers/misc/sgi-gru/grulib.h b/drivers/misc/sgi-gru/grulib.h index 8ed6acbc47c..8615b904a7c 100644 --- a/drivers/misc/sgi-gru/grulib.h +++ b/drivers/misc/sgi-gru/grulib.h @@ -32,8 +32,8 @@ /* Set Number of Request Blocks */ #define GRU_CREATE_CONTEXT _IOWR(GRU_IOCTL_NUM, 1, void *) -/* Register task as using the slice */ -#define GRU_SET_TASK_SLICE _IOWR(GRU_IOCTL_NUM, 5, void *) +/* Set Context Options */ +#define GRU_SET_CONTEXT_OPTION _IOWR(GRU_IOCTL_NUM, 4, void *) /* Fetch exception detail */ #define GRU_USER_GET_EXCEPTION_DETAIL _IOWR(GRU_IOCTL_NUM, 6, void *) @@ -95,6 +95,16 @@ struct gru_unload_context_req { unsigned long gseg; }; +/* + * Structure used to set context options + */ +enum {sco_gseg_owner}; +struct gru_set_context_option_req { + unsigned long gseg; + int op; + unsigned long val1; +}; + /* * Structure used to pass TLB flush parameters to the driver */ diff --git a/drivers/misc/sgi-gru/gruprocfs.c b/drivers/misc/sgi-gru/gruprocfs.c index b5b9cf5c182..9cbf95bedce 100644 --- a/drivers/misc/sgi-gru/gruprocfs.c +++ b/drivers/misc/sgi-gru/gruprocfs.c @@ -73,7 +73,7 @@ static int statistics_show(struct seq_file *s, void *p) printstat(s, user_flush_tlb); printstat(s, user_unload_context); printstat(s, user_exception); - printstat(s, set_task_slice); + printstat(s, set_context_option); printstat(s, migrate_check); printstat(s, migrated_retarget); printstat(s, migrated_unload); diff --git a/drivers/misc/sgi-gru/grutables.h b/drivers/misc/sgi-gru/grutables.h index 63b76e2732f..ee2f4121db2 100644 --- a/drivers/misc/sgi-gru/grutables.h +++ b/drivers/misc/sgi-gru/grutables.h @@ -198,7 +198,7 @@ struct gru_stats_s { atomic_long_t user_flush_tlb; atomic_long_t user_unload_context; atomic_long_t user_exception; - atomic_long_t set_task_slice; + atomic_long_t set_context_option; atomic_long_t migrate_check; atomic_long_t migrated_retarget; atomic_long_t migrated_unload; @@ -649,7 +649,7 @@ extern int gru_handle_user_call_os(unsigned long address); extern int gru_user_flush_tlb(unsigned long arg); extern int gru_user_unload_context(unsigned long arg); extern int gru_get_exception_detail(unsigned long arg); -extern int gru_set_task_slice(long address); +extern int gru_set_context_option(unsigned long address); extern int gru_cpu_fault_map_id(void); extern struct vm_area_struct *gru_find_vma(unsigned long vaddr); extern void gru_flush_all_tlb(struct gru_state *gru); -- cgit v1.2.3-70-g09d2 From b1b19fcfa417cf62447413d6e8b9b6598adf00b9 Mon Sep 17 00:00:00 2001 From: Jack Steiner Date: Wed, 17 Jun 2009 16:28:33 -0700 Subject: gru: add user request to specify gru slice Add a user request to specify the gru instruction slice parameter for user contexts. Signed-off-by: Jack Steiner Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/misc/sgi-gru/grufault.c | 4 ++++ drivers/misc/sgi-gru/grulib.h | 2 +- drivers/misc/sgi-gru/grumain.c | 7 +++++++ drivers/misc/sgi-gru/grutables.h | 1 + 4 files changed, 13 insertions(+), 1 deletion(-) (limited to 'drivers/misc') diff --git a/drivers/misc/sgi-gru/grufault.c b/drivers/misc/sgi-gru/grufault.c index b894b7ed9c3..1ad360cd318 100644 --- a/drivers/misc/sgi-gru/grufault.c +++ b/drivers/misc/sgi-gru/grufault.c @@ -769,6 +769,10 @@ int gru_set_context_option(unsigned long arg) /* Register the current task as the GSEG owner */ gts->ts_tgid_owner = current->tgid; break; + case sco_cch_req_slice: + /* Set the CCH slice option */ + gts->ts_cch_req_slice = req.val1 & 3; + break; default: ret = -EINVAL; } diff --git a/drivers/misc/sgi-gru/grulib.h b/drivers/misc/sgi-gru/grulib.h index 8615b904a7c..a484a9fee3c 100644 --- a/drivers/misc/sgi-gru/grulib.h +++ b/drivers/misc/sgi-gru/grulib.h @@ -98,7 +98,7 @@ struct gru_unload_context_req { /* * Structure used to set context options */ -enum {sco_gseg_owner}; +enum {sco_gseg_owner, sco_cch_req_slice}; struct gru_set_context_option_req { unsigned long gseg; int op; diff --git a/drivers/misc/sgi-gru/grumain.c b/drivers/misc/sgi-gru/grumain.c index 347004e4f59..0c20be007b2 100644 --- a/drivers/misc/sgi-gru/grumain.c +++ b/drivers/misc/sgi-gru/grumain.c @@ -321,6 +321,7 @@ struct gru_thread_state *gru_alloc_gts(struct vm_area_struct *vma, gts->ts_tsid = tsid; gts->ts_ctxnum = NULLCTX; gts->ts_tlb_int_select = -1; + gts->ts_cch_req_slice = -1; gts->ts_sizeavail = GRU_SIZEAVAIL(PAGE_SHIFT); if (vma) { gts->ts_mm = current->mm; @@ -566,6 +567,12 @@ void gru_load_context(struct gru_thread_state *gts) gts->ts_tlb_int_select = gru_cpu_fault_map_id(); cch->tlb_int_select = gts->ts_tlb_int_select; } + if (gts->ts_cch_req_slice >= 0) { + cch->req_slice_set_enable = 1; + cch->req_slice = gts->ts_cch_req_slice; + } else { + cch->req_slice_set_enable =0; + } cch->tfm_done_bit_enable = 0; cch->dsr_allocation_map = gts->ts_dsr_map; cch->cbr_allocation_map = gts->ts_cbr_map; diff --git a/drivers/misc/sgi-gru/grutables.h b/drivers/misc/sgi-gru/grutables.h index ee2f4121db2..34ab3d45391 100644 --- a/drivers/misc/sgi-gru/grutables.h +++ b/drivers/misc/sgi-gru/grutables.h @@ -380,6 +380,7 @@ struct gru_thread_state { required for contest */ unsigned char ts_cbr_au_count;/* Number of CBR resources required for contest */ + char ts_cch_req_slice;/* CCH packet slice */ char ts_blade; /* If >= 0, migrate context if ref from diferent blade */ char ts_force_cch_reload; -- cgit v1.2.3-70-g09d2 From 1926ee85a903d189c5702eed6531be321e33eb47 Mon Sep 17 00:00:00 2001 From: Jack Steiner Date: Wed, 17 Jun 2009 16:28:33 -0700 Subject: gru: fix potential use-after-free when purging GRU tlbs Fix potential SGI GRU bug that could cause a use-after-free. If one thread in a task is flushing the GRU and another thread destroys the GRU context, there is the potential to access a table after it has been freed. Copy the gms pointer to a local variable before unlocking the gts table. Note that no refcnt is needed for the gms - the reference is held indirectly by the task's mm_struct. Signed-off-by: Jack Steiner Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/misc/sgi-gru/grufault.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers/misc') diff --git a/drivers/misc/sgi-gru/grufault.c b/drivers/misc/sgi-gru/grufault.c index 1ad360cd318..679e0177828 100644 --- a/drivers/misc/sgi-gru/grufault.c +++ b/drivers/misc/sgi-gru/grufault.c @@ -702,6 +702,7 @@ int gru_user_flush_tlb(unsigned long arg) { struct gru_thread_state *gts; struct gru_flush_tlb_req req; + struct gru_mm_struct *gms; STAT(user_flush_tlb); if (copy_from_user(&req, (void __user *)arg, sizeof(req))) @@ -714,8 +715,9 @@ int gru_user_flush_tlb(unsigned long arg) if (!gts) return -EINVAL; - gru_flush_tlb_range(gts->ts_gms, req.vaddr, req.len); + gms = gts->ts_gms; gru_unlock_gts(gts); + gru_flush_tlb_range(gms, req.vaddr, req.len); return 0; } -- cgit v1.2.3-70-g09d2 From 2b702b28addc0f9eb2f37148f0b99f546cadd30a Mon Sep 17 00:00:00 2001 From: Jack Steiner Date: Wed, 17 Jun 2009 16:28:34 -0700 Subject: gru: fixes to grudump utility Minor fixes to the SGI GRU grudump facility: - fix address where user data is written - add gru number to data passed to user - indicate if context is locked Signed-off-by: Jack Steiner Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/misc/sgi-gru/grukdump.c | 8 ++++++-- drivers/misc/sgi-gru/grulib.h | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) (limited to 'drivers/misc') diff --git a/drivers/misc/sgi-gru/grukdump.c b/drivers/misc/sgi-gru/grukdump.c index 7b1bdf3906b..0f9dd3e64b5 100644 --- a/drivers/misc/sgi-gru/grukdump.c +++ b/drivers/misc/sgi-gru/grukdump.c @@ -26,7 +26,7 @@ static int gru_user_copy_handle(void __user **dp, void *s) { - if (copy_to_user(dp, s, GRU_HANDLE_BYTES)) + if (copy_to_user(*dp, s, GRU_HANDLE_BYTES)) return -1; *dp += GRU_HANDLE_BYTES; return 0; @@ -109,7 +109,7 @@ static int gru_dump_context(struct gru_state *gru, int ctxnum, { struct gru_dump_context_header hdr; struct gru_dump_context_header __user *uhdr = ubuf; - struct gru_context_configuration_handle *cch; + struct gru_context_configuration_handle *cch, *ubufcch; struct gru_thread_state *gts; int try, cch_locked, cbrcnt = 0, dsrcnt = 0, bytes = 0, ret = 0; void *grubase; @@ -125,8 +125,11 @@ static int gru_dump_context(struct gru_state *gru, int ctxnum, } ubuf += sizeof(hdr); + ubufcch = ubuf; if (gru_user_copy_handle(&ubuf, cch)) goto fail; + if (cch_locked) + ubufcch->delresp = 0; bytes = sizeof(hdr) + GRU_CACHE_LINE_BYTES; if (cch_locked || !lock_cch) { @@ -155,6 +158,7 @@ static int gru_dump_context(struct gru_state *gru, int ctxnum, return ret; hdr.magic = GRU_DUMP_MAGIC; + hdr.gid = gru->gs_gid; hdr.ctxnum = ctxnum; hdr.cbrcnt = cbrcnt; hdr.dsrcnt = dsrcnt; diff --git a/drivers/misc/sgi-gru/grulib.h b/drivers/misc/sgi-gru/grulib.h index a484a9fee3c..889bc442a3e 100644 --- a/drivers/misc/sgi-gru/grulib.h +++ b/drivers/misc/sgi-gru/grulib.h @@ -120,7 +120,7 @@ struct gru_flush_tlb_req { enum {dcs_pid, dcs_gid}; struct gru_dump_chiplet_state_req { unsigned int op; - int gid; + unsigned int gid; int ctxnum; char data_opt; char lock_cch; @@ -134,7 +134,7 @@ struct gru_dump_chiplet_state_req { #define GRU_DUMP_MAGIC 0x3474ab6c struct gru_dump_context_header { unsigned int magic; - unsigned char gid; + unsigned int gid; unsigned char ctxnum; unsigned char cbrcnt; unsigned char dsrcnt; -- cgit v1.2.3-70-g09d2 From 229a0505f2c4387555318e14c80e1362be2e39f7 Mon Sep 17 00:00:00 2001 From: Jack Steiner Date: Wed, 17 Jun 2009 16:28:35 -0700 Subject: gru: remove references to the obsolete global status handle Delete references to the SGI GRU GSH hardware resources. These GRU resources have been deleted from the hardware. (These resources have never benn used, anyway). Signed-off-by: Jack Steiner Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/misc/sgi-gru/gruhandles.h | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers/misc') diff --git a/drivers/misc/sgi-gru/gruhandles.h b/drivers/misc/sgi-gru/gruhandles.h index 99ec82678f5..f44112242d0 100644 --- a/drivers/misc/sgi-gru/gruhandles.h +++ b/drivers/misc/sgi-gru/gruhandles.h @@ -39,7 +39,6 @@ #define GRU_NUM_CBE 128 #define GRU_NUM_TFH 128 #define GRU_NUM_CCH 16 -#define GRU_NUM_GSH 1 /* Maximum resource counts that can be reserved by user programs */ #define GRU_NUM_USER_CBR GRU_NUM_CBE @@ -56,7 +55,6 @@ #define GRU_CBE_BASE (GRU_MCS_BASE + 0x10000) #define GRU_TFH_BASE (GRU_MCS_BASE + 0x18000) #define GRU_CCH_BASE (GRU_MCS_BASE + 0x20000) -#define GRU_GSH_BASE (GRU_MCS_BASE + 0x30000) /* User gseg constants */ #define GRU_GSEG_STRIDE (4 * 1024 * 1024) -- cgit v1.2.3-70-g09d2 From 8820f27ad9a5ad2a62cdcdf425d7921c31831800 Mon Sep 17 00:00:00 2001 From: Jack Steiner Date: Wed, 17 Jun 2009 16:28:36 -0700 Subject: gru: copyright fixes Fix the copyright statements in a couple of GRU files. No functional changes are being made. Signed-off-by: Jack Steiner Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/misc/sgi-gru/grukdump.c | 18 ++++++++++++++---- drivers/misc/sgi-gru/grumain.c | 18 ++++++++++++++---- 2 files changed, 28 insertions(+), 8 deletions(-) (limited to 'drivers/misc') diff --git a/drivers/misc/sgi-gru/grukdump.c b/drivers/misc/sgi-gru/grukdump.c index 0f9dd3e64b5..55eabfa8558 100644 --- a/drivers/misc/sgi-gru/grukdump.c +++ b/drivers/misc/sgi-gru/grukdump.c @@ -3,11 +3,21 @@ * * Dump GRU State * - * 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 - * for more details. + * Copyright (c) 2008 Silicon Graphics, Inc. All Rights Reserved. * - * Copyright (c) 2008 Silicon Graphics, Inc. All Rights Reserved. + * 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 */ #include diff --git a/drivers/misc/sgi-gru/grumain.c b/drivers/misc/sgi-gru/grumain.c index 0c20be007b2..3bc643dad60 100644 --- a/drivers/misc/sgi-gru/grumain.c +++ b/drivers/misc/sgi-gru/grumain.c @@ -3,11 +3,21 @@ * * DRIVER TABLE MANAGER + GRU CONTEXT LOAD/UNLOAD * - * 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 - * for more details. + * Copyright (c) 2008 Silicon Graphics, Inc. All Rights Reserved. * - * Copyright (c) 2008 Silicon Graphics, Inc. All Rights Reserved. + * 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 */ #include -- cgit v1.2.3-70-g09d2