X-Git-Url: https://gerrit.opnfv.org/gerrit/gitweb?a=blobdiff_plain;f=kernel%2Fdrivers%2Fblock%2Fumem.c;fp=kernel%2Fdrivers%2Fblock%2Fumem.c;h=4cf81b5bf0f7fba42a243a7e717ead98b2144759;hb=9ca8dbcc65cfc63d6f5ef3312a33184e1d726e00;hp=0000000000000000000000000000000000000000;hpb=98260f3884f4a202f9ca5eabed40b1354c489b29;p=kvmfornfv.git diff --git a/kernel/drivers/block/umem.c b/kernel/drivers/block/umem.c new file mode 100644 index 000000000..4cf81b5bf --- /dev/null +++ b/kernel/drivers/block/umem.c @@ -0,0 +1,1136 @@ +/* + * mm.c - Micro Memory(tm) PCI memory board block device driver - v2.3 + * + * (C) 2001 San Mehat + * (C) 2001 Johannes Erdfelt + * (C) 2001 NeilBrown + * + * This driver for the Micro Memory PCI Memory Module with Battery Backup + * is Copyright Micro Memory Inc 2001-2002. All rights reserved. + * + * This driver is released to the public under the terms of the + * GNU GENERAL PUBLIC LICENSE version 2 + * See the file COPYING for details. + * + * This driver provides a standard block device interface for Micro Memory(tm) + * PCI based RAM boards. + * 10/05/01: Phap Nguyen - Rebuilt the driver + * 10/22/01: Phap Nguyen - v2.1 Added disk partitioning + * 29oct2001:NeilBrown - Use make_request_fn instead of request_fn + * - use stand disk partitioning (so fdisk works). + * 08nov2001:NeilBrown - change driver name from "mm" to "umem" + * - incorporate into main kernel + * 08apr2002:NeilBrown - Move some of interrupt handle to tasklet + * - use spin_lock_bh instead of _irq + * - Never block on make_request. queue + * bh's instead. + * - unregister umem from devfs at mod unload + * - Change version to 2.3 + * 07Nov2001:Phap Nguyen - Select pci read command: 06, 12, 15 (Decimal) + * 07Jan2002: P. Nguyen - Used PCI Memory Write & Invalidate for DMA + * 15May2002:NeilBrown - convert to bio for 2.5 + * 17May2002:NeilBrown - remove init_mem initialisation. Instead detect + * - a sequence of writes that cover the card, and + * - set initialised bit then. + */ + +#undef DEBUG /* #define DEBUG if you want debugging info (pr_debug) */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include /* O_ACCMODE */ +#include /* HDIO_GETGEO */ + +#include "umem.h" + +#include +#include + +#define MM_MAXCARDS 4 +#define MM_RAHEAD 2 /* two sectors */ +#define MM_BLKSIZE 1024 /* 1k blocks */ +#define MM_HARDSECT 512 /* 512-byte hardware sectors */ +#define MM_SHIFT 6 /* max 64 partitions on 4 cards */ + +/* + * Version Information + */ + +#define DRIVER_NAME "umem" +#define DRIVER_VERSION "v2.3" +#define DRIVER_AUTHOR "San Mehat, Johannes Erdfelt, NeilBrown" +#define DRIVER_DESC "Micro Memory(tm) PCI memory board block driver" + +static int debug; +/* #define HW_TRACE(x) writeb(x,cards[0].csr_remap + MEMCTRLSTATUS_MAGIC) */ +#define HW_TRACE(x) + +#define DEBUG_LED_ON_TRANSFER 0x01 +#define DEBUG_BATTERY_POLLING 0x02 + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Debug bitmask"); + +static int pci_read_cmd = 0x0C; /* Read Multiple */ +module_param(pci_read_cmd, int, 0); +MODULE_PARM_DESC(pci_read_cmd, "PCI read command"); + +static int pci_write_cmd = 0x0F; /* Write and Invalidate */ +module_param(pci_write_cmd, int, 0); +MODULE_PARM_DESC(pci_write_cmd, "PCI write command"); + +static int pci_cmds; + +static int major_nr; + +#include +#include + +struct cardinfo { + struct pci_dev *dev; + + unsigned char __iomem *csr_remap; + unsigned int mm_size; /* size in kbytes */ + + unsigned int init_size; /* initial segment, in sectors, + * that we know to + * have been written + */ + struct bio *bio, *currentbio, **biotail; + struct bvec_iter current_iter; + + struct request_queue *queue; + + struct mm_page { + dma_addr_t page_dma; + struct mm_dma_desc *desc; + int cnt, headcnt; + struct bio *bio, **biotail; + struct bvec_iter iter; + } mm_pages[2]; +#define DESC_PER_PAGE ((PAGE_SIZE*2)/sizeof(struct mm_dma_desc)) + + int Active, Ready; + + struct tasklet_struct tasklet; + unsigned int dma_status; + + struct { + int good; + int warned; + unsigned long last_change; + } battery[2]; + + spinlock_t lock; + int check_batteries; + + int flags; +}; + +static struct cardinfo cards[MM_MAXCARDS]; +static struct timer_list battery_timer; + +static int num_cards; + +static struct gendisk *mm_gendisk[MM_MAXCARDS]; + +static void check_batteries(struct cardinfo *card); + +static int get_userbit(struct cardinfo *card, int bit) +{ + unsigned char led; + + led = readb(card->csr_remap + MEMCTRLCMD_LEDCTRL); + return led & bit; +} + +static int set_userbit(struct cardinfo *card, int bit, unsigned char state) +{ + unsigned char led; + + led = readb(card->csr_remap + MEMCTRLCMD_LEDCTRL); + if (state) + led |= bit; + else + led &= ~bit; + writeb(led, card->csr_remap + MEMCTRLCMD_LEDCTRL); + + return 0; +} + +/* + * NOTE: For the power LED, use the LED_POWER_* macros since they differ + */ +static void set_led(struct cardinfo *card, int shift, unsigned char state) +{ + unsigned char led; + + led = readb(card->csr_remap + MEMCTRLCMD_LEDCTRL); + if (state == LED_FLIP) + led ^= (1<csr_remap + MEMCTRLCMD_LEDCTRL); + +} + +#ifdef MM_DIAG +static void dump_regs(struct cardinfo *card) +{ + unsigned char *p; + int i, i1; + + p = card->csr_remap; + for (i = 0; i < 8; i++) { + printk(KERN_DEBUG "%p ", p); + + for (i1 = 0; i1 < 16; i1++) + printk("%02x ", *p++); + + printk("\n"); + } +} +#endif + +static void dump_dmastat(struct cardinfo *card, unsigned int dmastat) +{ + dev_printk(KERN_DEBUG, &card->dev->dev, "DMAstat - "); + if (dmastat & DMASCR_ANY_ERR) + printk(KERN_CONT "ANY_ERR "); + if (dmastat & DMASCR_MBE_ERR) + printk(KERN_CONT "MBE_ERR "); + if (dmastat & DMASCR_PARITY_ERR_REP) + printk(KERN_CONT "PARITY_ERR_REP "); + if (dmastat & DMASCR_PARITY_ERR_DET) + printk(KERN_CONT "PARITY_ERR_DET "); + if (dmastat & DMASCR_SYSTEM_ERR_SIG) + printk(KERN_CONT "SYSTEM_ERR_SIG "); + if (dmastat & DMASCR_TARGET_ABT) + printk(KERN_CONT "TARGET_ABT "); + if (dmastat & DMASCR_MASTER_ABT) + printk(KERN_CONT "MASTER_ABT "); + if (dmastat & DMASCR_CHAIN_COMPLETE) + printk(KERN_CONT "CHAIN_COMPLETE "); + if (dmastat & DMASCR_DMA_COMPLETE) + printk(KERN_CONT "DMA_COMPLETE "); + printk("\n"); +} + +/* + * Theory of request handling + * + * Each bio is assigned to one mm_dma_desc - which may not be enough FIXME + * We have two pages of mm_dma_desc, holding about 64 descriptors + * each. These are allocated at init time. + * One page is "Ready" and is either full, or can have request added. + * The other page might be "Active", which DMA is happening on it. + * + * Whenever IO on the active page completes, the Ready page is activated + * and the ex-Active page is clean out and made Ready. + * Otherwise the Ready page is only activated when it becomes full. + * + * If a request arrives while both pages a full, it is queued, and b_rdev is + * overloaded to record whether it was a read or a write. + * + * The interrupt handler only polls the device to clear the interrupt. + * The processing of the result is done in a tasklet. + */ + +static void mm_start_io(struct cardinfo *card) +{ + /* we have the lock, we know there is + * no IO active, and we know that card->Active + * is set + */ + struct mm_dma_desc *desc; + struct mm_page *page; + int offset; + + /* make the last descriptor end the chain */ + page = &card->mm_pages[card->Active]; + pr_debug("start_io: %d %d->%d\n", + card->Active, page->headcnt, page->cnt - 1); + desc = &page->desc[page->cnt-1]; + + desc->control_bits |= cpu_to_le32(DMASCR_CHAIN_COMP_EN); + desc->control_bits &= ~cpu_to_le32(DMASCR_CHAIN_EN); + desc->sem_control_bits = desc->control_bits; + + + if (debug & DEBUG_LED_ON_TRANSFER) + set_led(card, LED_REMOVE, LED_ON); + + desc = &page->desc[page->headcnt]; + writel(0, card->csr_remap + DMA_PCI_ADDR); + writel(0, card->csr_remap + DMA_PCI_ADDR + 4); + + writel(0, card->csr_remap + DMA_LOCAL_ADDR); + writel(0, card->csr_remap + DMA_LOCAL_ADDR + 4); + + writel(0, card->csr_remap + DMA_TRANSFER_SIZE); + writel(0, card->csr_remap + DMA_TRANSFER_SIZE + 4); + + writel(0, card->csr_remap + DMA_SEMAPHORE_ADDR); + writel(0, card->csr_remap + DMA_SEMAPHORE_ADDR + 4); + + offset = ((char *)desc) - ((char *)page->desc); + writel(cpu_to_le32((page->page_dma+offset) & 0xffffffff), + card->csr_remap + DMA_DESCRIPTOR_ADDR); + /* Force the value to u64 before shifting otherwise >> 32 is undefined C + * and on some ports will do nothing ! */ + writel(cpu_to_le32(((u64)page->page_dma)>>32), + card->csr_remap + DMA_DESCRIPTOR_ADDR + 4); + + /* Go, go, go */ + writel(cpu_to_le32(DMASCR_GO | DMASCR_CHAIN_EN | pci_cmds), + card->csr_remap + DMA_STATUS_CTRL); +} + +static int add_bio(struct cardinfo *card); + +static void activate(struct cardinfo *card) +{ + /* if No page is Active, and Ready is + * not empty, then switch Ready page + * to active and start IO. + * Then add any bh's that are available to Ready + */ + + do { + while (add_bio(card)) + ; + + if (card->Active == -1 && + card->mm_pages[card->Ready].cnt > 0) { + card->Active = card->Ready; + card->Ready = 1-card->Ready; + mm_start_io(card); + } + + } while (card->Active == -1 && add_bio(card)); +} + +static inline void reset_page(struct mm_page *page) +{ + page->cnt = 0; + page->headcnt = 0; + page->bio = NULL; + page->biotail = &page->bio; +} + +/* + * If there is room on Ready page, take + * one bh off list and add it. + * return 1 if there was room, else 0. + */ +static int add_bio(struct cardinfo *card) +{ + struct mm_page *p; + struct mm_dma_desc *desc; + dma_addr_t dma_handle; + int offset; + struct bio *bio; + struct bio_vec vec; + int rw; + + bio = card->currentbio; + if (!bio && card->bio) { + card->currentbio = card->bio; + card->current_iter = card->bio->bi_iter; + card->bio = card->bio->bi_next; + if (card->bio == NULL) + card->biotail = &card->bio; + card->currentbio->bi_next = NULL; + return 1; + } + if (!bio) + return 0; + + rw = bio_rw(bio); + if (card->mm_pages[card->Ready].cnt >= DESC_PER_PAGE) + return 0; + + vec = bio_iter_iovec(bio, card->current_iter); + + dma_handle = pci_map_page(card->dev, + vec.bv_page, + vec.bv_offset, + vec.bv_len, + (rw == READ) ? + PCI_DMA_FROMDEVICE : PCI_DMA_TODEVICE); + + p = &card->mm_pages[card->Ready]; + desc = &p->desc[p->cnt]; + p->cnt++; + if (p->bio == NULL) + p->iter = card->current_iter; + if ((p->biotail) != &bio->bi_next) { + *(p->biotail) = bio; + p->biotail = &(bio->bi_next); + bio->bi_next = NULL; + } + + desc->data_dma_handle = dma_handle; + + desc->pci_addr = cpu_to_le64((u64)desc->data_dma_handle); + desc->local_addr = cpu_to_le64(card->current_iter.bi_sector << 9); + desc->transfer_size = cpu_to_le32(vec.bv_len); + offset = (((char *)&desc->sem_control_bits) - ((char *)p->desc)); + desc->sem_addr = cpu_to_le64((u64)(p->page_dma+offset)); + desc->zero1 = desc->zero2 = 0; + offset = (((char *)(desc+1)) - ((char *)p->desc)); + desc->next_desc_addr = cpu_to_le64(p->page_dma+offset); + desc->control_bits = cpu_to_le32(DMASCR_GO|DMASCR_ERR_INT_EN| + DMASCR_PARITY_INT_EN| + DMASCR_CHAIN_EN | + DMASCR_SEM_EN | + pci_cmds); + if (rw == WRITE) + desc->control_bits |= cpu_to_le32(DMASCR_TRANSFER_READ); + desc->sem_control_bits = desc->control_bits; + + + bio_advance_iter(bio, &card->current_iter, vec.bv_len); + if (!card->current_iter.bi_size) + card->currentbio = NULL; + + return 1; +} + +static void process_page(unsigned long data) +{ + /* check if any of the requests in the page are DMA_COMPLETE, + * and deal with them appropriately. + * If we find a descriptor without DMA_COMPLETE in the semaphore, then + * dma must have hit an error on that descriptor, so use dma_status + * instead and assume that all following descriptors must be re-tried. + */ + struct mm_page *page; + struct bio *return_bio = NULL; + struct cardinfo *card = (struct cardinfo *)data; + unsigned int dma_status = card->dma_status; + + spin_lock_bh(&card->lock); + if (card->Active < 0) + goto out_unlock; + page = &card->mm_pages[card->Active]; + + while (page->headcnt < page->cnt) { + struct bio *bio = page->bio; + struct mm_dma_desc *desc = &page->desc[page->headcnt]; + int control = le32_to_cpu(desc->sem_control_bits); + int last = 0; + struct bio_vec vec; + + if (!(control & DMASCR_DMA_COMPLETE)) { + control = dma_status; + last = 1; + } + + page->headcnt++; + vec = bio_iter_iovec(bio, page->iter); + bio_advance_iter(bio, &page->iter, vec.bv_len); + + if (!page->iter.bi_size) { + page->bio = bio->bi_next; + if (page->bio) + page->iter = page->bio->bi_iter; + } + + pci_unmap_page(card->dev, desc->data_dma_handle, + vec.bv_len, + (control & DMASCR_TRANSFER_READ) ? + PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE); + if (control & DMASCR_HARD_ERROR) { + /* error */ + clear_bit(BIO_UPTODATE, &bio->bi_flags); + dev_printk(KERN_WARNING, &card->dev->dev, + "I/O error on sector %d/%d\n", + le32_to_cpu(desc->local_addr)>>9, + le32_to_cpu(desc->transfer_size)); + dump_dmastat(card, control); + } else if ((bio->bi_rw & REQ_WRITE) && + le32_to_cpu(desc->local_addr) >> 9 == + card->init_size) { + card->init_size += le32_to_cpu(desc->transfer_size) >> 9; + if (card->init_size >> 1 >= card->mm_size) { + dev_printk(KERN_INFO, &card->dev->dev, + "memory now initialised\n"); + set_userbit(card, MEMORY_INITIALIZED, 1); + } + } + if (bio != page->bio) { + bio->bi_next = return_bio; + return_bio = bio; + } + + if (last) + break; + } + + if (debug & DEBUG_LED_ON_TRANSFER) + set_led(card, LED_REMOVE, LED_OFF); + + if (card->check_batteries) { + card->check_batteries = 0; + check_batteries(card); + } + if (page->headcnt >= page->cnt) { + reset_page(page); + card->Active = -1; + activate(card); + } else { + /* haven't finished with this one yet */ + pr_debug("do some more\n"); + mm_start_io(card); + } + out_unlock: + spin_unlock_bh(&card->lock); + + while (return_bio) { + struct bio *bio = return_bio; + + return_bio = bio->bi_next; + bio->bi_next = NULL; + bio_endio(bio, 0); + } +} + +static void mm_unplug(struct blk_plug_cb *cb, bool from_schedule) +{ + struct cardinfo *card = cb->data; + + spin_lock_irq(&card->lock); + activate(card); + spin_unlock_irq(&card->lock); + kfree(cb); +} + +static int mm_check_plugged(struct cardinfo *card) +{ + return !!blk_check_plugged(mm_unplug, card, sizeof(struct blk_plug_cb)); +} + +static void mm_make_request(struct request_queue *q, struct bio *bio) +{ + struct cardinfo *card = q->queuedata; + pr_debug("mm_make_request %llu %u\n", + (unsigned long long)bio->bi_iter.bi_sector, + bio->bi_iter.bi_size); + + spin_lock_irq(&card->lock); + *card->biotail = bio; + bio->bi_next = NULL; + card->biotail = &bio->bi_next; + if (bio->bi_rw & REQ_SYNC || !mm_check_plugged(card)) + activate(card); + spin_unlock_irq(&card->lock); + + return; +} + +static irqreturn_t mm_interrupt(int irq, void *__card) +{ + struct cardinfo *card = (struct cardinfo *) __card; + unsigned int dma_status; + unsigned short cfg_status; + +HW_TRACE(0x30); + + dma_status = le32_to_cpu(readl(card->csr_remap + DMA_STATUS_CTRL)); + + if (!(dma_status & (DMASCR_ERROR_MASK | DMASCR_CHAIN_COMPLETE))) { + /* interrupt wasn't for me ... */ + return IRQ_NONE; + } + + /* clear COMPLETION interrupts */ + if (card->flags & UM_FLAG_NO_BYTE_STATUS) + writel(cpu_to_le32(DMASCR_DMA_COMPLETE|DMASCR_CHAIN_COMPLETE), + card->csr_remap + DMA_STATUS_CTRL); + else + writeb((DMASCR_DMA_COMPLETE|DMASCR_CHAIN_COMPLETE) >> 16, + card->csr_remap + DMA_STATUS_CTRL + 2); + + /* log errors and clear interrupt status */ + if (dma_status & DMASCR_ANY_ERR) { + unsigned int data_log1, data_log2; + unsigned int addr_log1, addr_log2; + unsigned char stat, count, syndrome, check; + + stat = readb(card->csr_remap + MEMCTRLCMD_ERRSTATUS); + + data_log1 = le32_to_cpu(readl(card->csr_remap + + ERROR_DATA_LOG)); + data_log2 = le32_to_cpu(readl(card->csr_remap + + ERROR_DATA_LOG + 4)); + addr_log1 = le32_to_cpu(readl(card->csr_remap + + ERROR_ADDR_LOG)); + addr_log2 = readb(card->csr_remap + ERROR_ADDR_LOG + 4); + + count = readb(card->csr_remap + ERROR_COUNT); + syndrome = readb(card->csr_remap + ERROR_SYNDROME); + check = readb(card->csr_remap + ERROR_CHECK); + + dump_dmastat(card, dma_status); + + if (stat & 0x01) + dev_printk(KERN_ERR, &card->dev->dev, + "Memory access error detected (err count %d)\n", + count); + if (stat & 0x02) + dev_printk(KERN_ERR, &card->dev->dev, + "Multi-bit EDC error\n"); + + dev_printk(KERN_ERR, &card->dev->dev, + "Fault Address 0x%02x%08x, Fault Data 0x%08x%08x\n", + addr_log2, addr_log1, data_log2, data_log1); + dev_printk(KERN_ERR, &card->dev->dev, + "Fault Check 0x%02x, Fault Syndrome 0x%02x\n", + check, syndrome); + + writeb(0, card->csr_remap + ERROR_COUNT); + } + + if (dma_status & DMASCR_PARITY_ERR_REP) { + dev_printk(KERN_ERR, &card->dev->dev, + "PARITY ERROR REPORTED\n"); + pci_read_config_word(card->dev, PCI_STATUS, &cfg_status); + pci_write_config_word(card->dev, PCI_STATUS, cfg_status); + } + + if (dma_status & DMASCR_PARITY_ERR_DET) { + dev_printk(KERN_ERR, &card->dev->dev, + "PARITY ERROR DETECTED\n"); + pci_read_config_word(card->dev, PCI_STATUS, &cfg_status); + pci_write_config_word(card->dev, PCI_STATUS, cfg_status); + } + + if (dma_status & DMASCR_SYSTEM_ERR_SIG) { + dev_printk(KERN_ERR, &card->dev->dev, "SYSTEM ERROR\n"); + pci_read_config_word(card->dev, PCI_STATUS, &cfg_status); + pci_write_config_word(card->dev, PCI_STATUS, cfg_status); + } + + if (dma_status & DMASCR_TARGET_ABT) { + dev_printk(KERN_ERR, &card->dev->dev, "TARGET ABORT\n"); + pci_read_config_word(card->dev, PCI_STATUS, &cfg_status); + pci_write_config_word(card->dev, PCI_STATUS, cfg_status); + } + + if (dma_status & DMASCR_MASTER_ABT) { + dev_printk(KERN_ERR, &card->dev->dev, "MASTER ABORT\n"); + pci_read_config_word(card->dev, PCI_STATUS, &cfg_status); + pci_write_config_word(card->dev, PCI_STATUS, cfg_status); + } + + /* and process the DMA descriptors */ + card->dma_status = dma_status; + tasklet_schedule(&card->tasklet); + +HW_TRACE(0x36); + + return IRQ_HANDLED; +} + +/* + * If both batteries are good, no LED + * If either battery has been warned, solid LED + * If both batteries are bad, flash the LED quickly + * If either battery is bad, flash the LED semi quickly + */ +static void set_fault_to_battery_status(struct cardinfo *card) +{ + if (card->battery[0].good && card->battery[1].good) + set_led(card, LED_FAULT, LED_OFF); + else if (card->battery[0].warned || card->battery[1].warned) + set_led(card, LED_FAULT, LED_ON); + else if (!card->battery[0].good && !card->battery[1].good) + set_led(card, LED_FAULT, LED_FLASH_7_0); + else + set_led(card, LED_FAULT, LED_FLASH_3_5); +} + +static void init_battery_timer(void); + +static int check_battery(struct cardinfo *card, int battery, int status) +{ + if (status != card->battery[battery].good) { + card->battery[battery].good = !card->battery[battery].good; + card->battery[battery].last_change = jiffies; + + if (card->battery[battery].good) { + dev_printk(KERN_ERR, &card->dev->dev, + "Battery %d now good\n", battery + 1); + card->battery[battery].warned = 0; + } else + dev_printk(KERN_ERR, &card->dev->dev, + "Battery %d now FAILED\n", battery + 1); + + return 1; + } else if (!card->battery[battery].good && + !card->battery[battery].warned && + time_after_eq(jiffies, card->battery[battery].last_change + + (HZ * 60 * 60 * 5))) { + dev_printk(KERN_ERR, &card->dev->dev, + "Battery %d still FAILED after 5 hours\n", battery + 1); + card->battery[battery].warned = 1; + + return 1; + } + + return 0; +} + +static void check_batteries(struct cardinfo *card) +{ + /* NOTE: this must *never* be called while the card + * is doing (bus-to-card) DMA, or you will need the + * reset switch + */ + unsigned char status; + int ret1, ret2; + + status = readb(card->csr_remap + MEMCTRLSTATUS_BATTERY); + if (debug & DEBUG_BATTERY_POLLING) + dev_printk(KERN_DEBUG, &card->dev->dev, + "checking battery status, 1 = %s, 2 = %s\n", + (status & BATTERY_1_FAILURE) ? "FAILURE" : "OK", + (status & BATTERY_2_FAILURE) ? "FAILURE" : "OK"); + + ret1 = check_battery(card, 0, !(status & BATTERY_1_FAILURE)); + ret2 = check_battery(card, 1, !(status & BATTERY_2_FAILURE)); + + if (ret1 || ret2) + set_fault_to_battery_status(card); +} + +static void check_all_batteries(unsigned long ptr) +{ + int i; + + for (i = 0; i < num_cards; i++) + if (!(cards[i].flags & UM_FLAG_NO_BATT)) { + struct cardinfo *card = &cards[i]; + spin_lock_bh(&card->lock); + if (card->Active >= 0) + card->check_batteries = 1; + else + check_batteries(card); + spin_unlock_bh(&card->lock); + } + + init_battery_timer(); +} + +static void init_battery_timer(void) +{ + init_timer(&battery_timer); + battery_timer.function = check_all_batteries; + battery_timer.expires = jiffies + (HZ * 60); + add_timer(&battery_timer); +} + +static void del_battery_timer(void) +{ + del_timer(&battery_timer); +} + +/* + * Note no locks taken out here. In a worst case scenario, we could drop + * a chunk of system memory. But that should never happen, since validation + * happens at open or mount time, when locks are held. + * + * That's crap, since doing that while some partitions are opened + * or mounted will give you really nasty results. + */ +static int mm_revalidate(struct gendisk *disk) +{ + struct cardinfo *card = disk->private_data; + set_capacity(disk, card->mm_size << 1); + return 0; +} + +static int mm_getgeo(struct block_device *bdev, struct hd_geometry *geo) +{ + struct cardinfo *card = bdev->bd_disk->private_data; + int size = card->mm_size * (1024 / MM_HARDSECT); + + /* + * get geometry: we have to fake one... trim the size to a + * multiple of 2048 (1M): tell we have 32 sectors, 64 heads, + * whatever cylinders. + */ + geo->heads = 64; + geo->sectors = 32; + geo->cylinders = size / (geo->heads * geo->sectors); + return 0; +} + +static const struct block_device_operations mm_fops = { + .owner = THIS_MODULE, + .getgeo = mm_getgeo, + .revalidate_disk = mm_revalidate, +}; + +static int mm_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + int ret = -ENODEV; + struct cardinfo *card = &cards[num_cards]; + unsigned char mem_present; + unsigned char batt_status; + unsigned int saved_bar, data; + unsigned long csr_base; + unsigned long csr_len; + int magic_number; + static int printed_version; + + if (!printed_version++) + printk(KERN_INFO DRIVER_VERSION " : " DRIVER_DESC "\n"); + + ret = pci_enable_device(dev); + if (ret) + return ret; + + pci_write_config_byte(dev, PCI_LATENCY_TIMER, 0xF8); + pci_set_master(dev); + + card->dev = dev; + + csr_base = pci_resource_start(dev, 0); + csr_len = pci_resource_len(dev, 0); + if (!csr_base || !csr_len) + return -ENODEV; + + dev_printk(KERN_INFO, &dev->dev, + "Micro Memory(tm) controller found (PCI Mem Module (Battery Backup))\n"); + + if (pci_set_dma_mask(dev, DMA_BIT_MASK(64)) && + pci_set_dma_mask(dev, DMA_BIT_MASK(32))) { + dev_printk(KERN_WARNING, &dev->dev, "NO suitable DMA found\n"); + return -ENOMEM; + } + + ret = pci_request_regions(dev, DRIVER_NAME); + if (ret) { + dev_printk(KERN_ERR, &card->dev->dev, + "Unable to request memory region\n"); + goto failed_req_csr; + } + + card->csr_remap = ioremap_nocache(csr_base, csr_len); + if (!card->csr_remap) { + dev_printk(KERN_ERR, &card->dev->dev, + "Unable to remap memory region\n"); + ret = -ENOMEM; + + goto failed_remap_csr; + } + + dev_printk(KERN_INFO, &card->dev->dev, + "CSR 0x%08lx -> 0x%p (0x%lx)\n", + csr_base, card->csr_remap, csr_len); + + switch (card->dev->device) { + case 0x5415: + card->flags |= UM_FLAG_NO_BYTE_STATUS | UM_FLAG_NO_BATTREG; + magic_number = 0x59; + break; + + case 0x5425: + card->flags |= UM_FLAG_NO_BYTE_STATUS; + magic_number = 0x5C; + break; + + case 0x6155: + card->flags |= UM_FLAG_NO_BYTE_STATUS | + UM_FLAG_NO_BATTREG | UM_FLAG_NO_BATT; + magic_number = 0x99; + break; + + default: + magic_number = 0x100; + break; + } + + if (readb(card->csr_remap + MEMCTRLSTATUS_MAGIC) != magic_number) { + dev_printk(KERN_ERR, &card->dev->dev, "Magic number invalid\n"); + ret = -ENOMEM; + goto failed_magic; + } + + card->mm_pages[0].desc = pci_alloc_consistent(card->dev, + PAGE_SIZE * 2, + &card->mm_pages[0].page_dma); + card->mm_pages[1].desc = pci_alloc_consistent(card->dev, + PAGE_SIZE * 2, + &card->mm_pages[1].page_dma); + if (card->mm_pages[0].desc == NULL || + card->mm_pages[1].desc == NULL) { + dev_printk(KERN_ERR, &card->dev->dev, "alloc failed\n"); + goto failed_alloc; + } + reset_page(&card->mm_pages[0]); + reset_page(&card->mm_pages[1]); + card->Ready = 0; /* page 0 is ready */ + card->Active = -1; /* no page is active */ + card->bio = NULL; + card->biotail = &card->bio; + + card->queue = blk_alloc_queue(GFP_KERNEL); + if (!card->queue) + goto failed_alloc; + + blk_queue_make_request(card->queue, mm_make_request); + card->queue->queue_lock = &card->lock; + card->queue->queuedata = card; + + tasklet_init(&card->tasklet, process_page, (unsigned long)card); + + card->check_batteries = 0; + + mem_present = readb(card->csr_remap + MEMCTRLSTATUS_MEMORY); + switch (mem_present) { + case MEM_128_MB: + card->mm_size = 1024 * 128; + break; + case MEM_256_MB: + card->mm_size = 1024 * 256; + break; + case MEM_512_MB: + card->mm_size = 1024 * 512; + break; + case MEM_1_GB: + card->mm_size = 1024 * 1024; + break; + case MEM_2_GB: + card->mm_size = 1024 * 2048; + break; + default: + card->mm_size = 0; + break; + } + + /* Clear the LED's we control */ + set_led(card, LED_REMOVE, LED_OFF); + set_led(card, LED_FAULT, LED_OFF); + + batt_status = readb(card->csr_remap + MEMCTRLSTATUS_BATTERY); + + card->battery[0].good = !(batt_status & BATTERY_1_FAILURE); + card->battery[1].good = !(batt_status & BATTERY_2_FAILURE); + card->battery[0].last_change = card->battery[1].last_change = jiffies; + + if (card->flags & UM_FLAG_NO_BATT) + dev_printk(KERN_INFO, &card->dev->dev, + "Size %d KB\n", card->mm_size); + else { + dev_printk(KERN_INFO, &card->dev->dev, + "Size %d KB, Battery 1 %s (%s), Battery 2 %s (%s)\n", + card->mm_size, + batt_status & BATTERY_1_DISABLED ? "Disabled" : "Enabled", + card->battery[0].good ? "OK" : "FAILURE", + batt_status & BATTERY_2_DISABLED ? "Disabled" : "Enabled", + card->battery[1].good ? "OK" : "FAILURE"); + + set_fault_to_battery_status(card); + } + + pci_read_config_dword(dev, PCI_BASE_ADDRESS_1, &saved_bar); + data = 0xffffffff; + pci_write_config_dword(dev, PCI_BASE_ADDRESS_1, data); + pci_read_config_dword(dev, PCI_BASE_ADDRESS_1, &data); + pci_write_config_dword(dev, PCI_BASE_ADDRESS_1, saved_bar); + data &= 0xfffffff0; + data = ~data; + data += 1; + + if (request_irq(dev->irq, mm_interrupt, IRQF_SHARED, DRIVER_NAME, + card)) { + dev_printk(KERN_ERR, &card->dev->dev, + "Unable to allocate IRQ\n"); + ret = -ENODEV; + goto failed_req_irq; + } + + dev_printk(KERN_INFO, &card->dev->dev, + "Window size %d bytes, IRQ %d\n", data, dev->irq); + + spin_lock_init(&card->lock); + + pci_set_drvdata(dev, card); + + if (pci_write_cmd != 0x0F) /* If not Memory Write & Invalidate */ + pci_write_cmd = 0x07; /* then Memory Write command */ + + if (pci_write_cmd & 0x08) { /* use Memory Write and Invalidate */ + unsigned short cfg_command; + pci_read_config_word(dev, PCI_COMMAND, &cfg_command); + cfg_command |= 0x10; /* Memory Write & Invalidate Enable */ + pci_write_config_word(dev, PCI_COMMAND, cfg_command); + } + pci_cmds = (pci_read_cmd << 28) | (pci_write_cmd << 24); + + num_cards++; + + if (!get_userbit(card, MEMORY_INITIALIZED)) { + dev_printk(KERN_INFO, &card->dev->dev, + "memory NOT initialized. Consider over-writing whole device.\n"); + card->init_size = 0; + } else { + dev_printk(KERN_INFO, &card->dev->dev, + "memory already initialized\n"); + card->init_size = card->mm_size; + } + + /* Enable ECC */ + writeb(EDC_STORE_CORRECT, card->csr_remap + MEMCTRLCMD_ERRCTRL); + + return 0; + + failed_req_irq: + failed_alloc: + if (card->mm_pages[0].desc) + pci_free_consistent(card->dev, PAGE_SIZE*2, + card->mm_pages[0].desc, + card->mm_pages[0].page_dma); + if (card->mm_pages[1].desc) + pci_free_consistent(card->dev, PAGE_SIZE*2, + card->mm_pages[1].desc, + card->mm_pages[1].page_dma); + failed_magic: + iounmap(card->csr_remap); + failed_remap_csr: + pci_release_regions(dev); + failed_req_csr: + + return ret; +} + +static void mm_pci_remove(struct pci_dev *dev) +{ + struct cardinfo *card = pci_get_drvdata(dev); + + tasklet_kill(&card->tasklet); + free_irq(dev->irq, card); + iounmap(card->csr_remap); + + if (card->mm_pages[0].desc) + pci_free_consistent(card->dev, PAGE_SIZE*2, + card->mm_pages[0].desc, + card->mm_pages[0].page_dma); + if (card->mm_pages[1].desc) + pci_free_consistent(card->dev, PAGE_SIZE*2, + card->mm_pages[1].desc, + card->mm_pages[1].page_dma); + blk_cleanup_queue(card->queue); + + pci_release_regions(dev); + pci_disable_device(dev); +} + +static const struct pci_device_id mm_pci_ids[] = { + {PCI_DEVICE(PCI_VENDOR_ID_MICRO_MEMORY, PCI_DEVICE_ID_MICRO_MEMORY_5415CN)}, + {PCI_DEVICE(PCI_VENDOR_ID_MICRO_MEMORY, PCI_DEVICE_ID_MICRO_MEMORY_5425CN)}, + {PCI_DEVICE(PCI_VENDOR_ID_MICRO_MEMORY, PCI_DEVICE_ID_MICRO_MEMORY_6155)}, + { + .vendor = 0x8086, + .device = 0xB555, + .subvendor = 0x1332, + .subdevice = 0x5460, + .class = 0x050000, + .class_mask = 0, + }, { /* end: all zeroes */ } +}; + +MODULE_DEVICE_TABLE(pci, mm_pci_ids); + +static struct pci_driver mm_pci_driver = { + .name = DRIVER_NAME, + .id_table = mm_pci_ids, + .probe = mm_pci_probe, + .remove = mm_pci_remove, +}; + +static int __init mm_init(void) +{ + int retval, i; + int err; + + retval = pci_register_driver(&mm_pci_driver); + if (retval) + return -ENOMEM; + + err = major_nr = register_blkdev(0, DRIVER_NAME); + if (err < 0) { + pci_unregister_driver(&mm_pci_driver); + return -EIO; + } + + for (i = 0; i < num_cards; i++) { + mm_gendisk[i] = alloc_disk(1 << MM_SHIFT); + if (!mm_gendisk[i]) + goto out; + } + + for (i = 0; i < num_cards; i++) { + struct gendisk *disk = mm_gendisk[i]; + sprintf(disk->disk_name, "umem%c", 'a'+i); + spin_lock_init(&cards[i].lock); + disk->major = major_nr; + disk->first_minor = i << MM_SHIFT; + disk->fops = &mm_fops; + disk->private_data = &cards[i]; + disk->queue = cards[i].queue; + set_capacity(disk, cards[i].mm_size << 1); + add_disk(disk); + } + + init_battery_timer(); + printk(KERN_INFO "MM: desc_per_page = %ld\n", DESC_PER_PAGE); +/* printk("mm_init: Done. 10-19-01 9:00\n"); */ + return 0; + +out: + pci_unregister_driver(&mm_pci_driver); + unregister_blkdev(major_nr, DRIVER_NAME); + while (i--) + put_disk(mm_gendisk[i]); + return -ENOMEM; +} + +static void __exit mm_cleanup(void) +{ + int i; + + del_battery_timer(); + + for (i = 0; i < num_cards ; i++) { + del_gendisk(mm_gendisk[i]); + put_disk(mm_gendisk[i]); + } + + pci_unregister_driver(&mm_pci_driver); + + unregister_blkdev(major_nr, DRIVER_NAME); +} + +module_init(mm_init); +module_exit(mm_cleanup); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL");