📄 cfi_cmdset_0001.c
字号:
* Right now a L18 flash is assumed if multiple operations is * detected. * * To support multiple partitions when available, we simply arrange * for each of them to have their own flchip structure even if they * are on the same physical chip. This means completely recreating * a new cfi_private structure right here which is a blatent code * layering violation, but this is still the least intrusive * arrangement at this point. This can be rearranged in the future * if someone feels motivated enough. --nico */ if (extp && extp->FeatureSupport & (1 << 9)) { struct cfi_private *newcfi; struct flchip *chip; struct flchip_shared *shared; int numparts, partshift, numvirtchips, i, j; /* * The L18 flash memory array is divided * into multiple 8-Mbit partitions. */ numparts = 1 << (cfi->cfiq->DevSize - 20); partshift = 20 + __ffs(cfi->interleave); numvirtchips = cfi->numchips * numparts; newcfi = kmalloc(sizeof(struct cfi_private) + numvirtchips * sizeof(struct flchip), GFP_KERNEL); if (!newcfi) return -ENOMEM; shared = kmalloc(sizeof(struct flchip_shared) * cfi->numchips, GFP_KERNEL); if (!shared) { kfree(newcfi); return -ENOMEM; } memcpy(newcfi, cfi, sizeof(struct cfi_private)); newcfi->numchips = numvirtchips; newcfi->chipshift = partshift; chip = &newcfi->chips[0]; for (i = 0; i < cfi->numchips; i++) { shared[i].writing = shared[i].erasing = NULL; spin_lock_init(&shared[i].lock); for (j = 0; j < numparts; j++) { *chip = cfi->chips[i]; chip->start += j << partshift; chip->priv = &shared[i]; /* those should be reset too since they create memory references. */ init_waitqueue_head(&chip->wq); spin_lock_init(&chip->_spinlock); chip->mutex = &chip->_spinlock; chip++; } } printk(KERN_DEBUG "%s: %d sets of %d interleaved chips " "--> %d partitions of %#x bytes\n", map->name, cfi->numchips, cfi->interleave, newcfi->numchips, 1<<newcfi->chipshift); map->fldrv_priv = newcfi; *pcfi = newcfi; kfree(cfi); } return 0;}/* * *********** CHIP ACCESS FUNCTIONS *********** */static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr, int mode){ DECLARE_WAITQUEUE(wait, current); struct cfi_private *cfi = map->fldrv_priv; map_word status, status_OK = CMD(0x80), status_PWS = CMD(0x01); unsigned long timeo; struct cfi_pri_intelext *cfip = cfi->cmdset_priv; resettime: timeo = jiffies + HZ; retry: if (chip->priv && (mode == FL_WRITING || mode == FL_ERASING)) { /* * OK. We have possibility for contension on the write/erase * operations which are global to the real chip and not per * partition. So let's fight it over in the partition which * currently has authority on the operation. * * The rules are as follows: * * - any write operation must own shared->writing. * * - any erase operation must own _both_ shared->writing and * shared->erasing. * * - contension arbitration is handled in the owner's context. * * The 'shared' struct can be read when its lock is taken. * However any writes to it can only be made when the current * owner's lock is also held. */ struct flchip_shared *shared = chip->priv; struct flchip *contender; spin_lock(&shared->lock); contender = shared->writing; if (contender && contender != chip) { /* * The engine to perform desired operation on this * partition is already in use by someone else. * Let's fight over it in the context of the chip * currently using it. If it is possible to suspend, * that other partition will do just that, otherwise * it'll happily send us to sleep. In any case, when * get_chip returns success we're clear to go ahead. */ int ret = spin_trylock(contender->mutex); spin_unlock(&shared->lock); if (!ret) goto retry; spin_unlock(chip->mutex); ret = get_chip(map, contender, contender->start, mode); spin_lock(chip->mutex); if (ret) { spin_unlock(contender->mutex); return ret; } timeo = jiffies + HZ; spin_lock(&shared->lock); } /* We now own it */ shared->writing = chip; if (mode == FL_ERASING) shared->erasing = chip; if (contender && contender != chip) spin_unlock(contender->mutex); spin_unlock(&shared->lock); } switch (chip->state) { case FL_STATUS: for (;;) { status = map_read(map, adr); if (map_word_andequal(map, status, status_OK, status_OK)) break; /* At this point we're fine with write operations in other partitions as they don't conflict. */ if (chip->priv && map_word_andequal(map, status, status_PWS, status_PWS)) break; if (time_after(jiffies, timeo)) { printk(KERN_ERR "Waiting for chip to be ready timed out. Status %lx\n", status.x[0]); return -EIO; } spin_unlock(chip->mutex); cfi_udelay(1); spin_lock(chip->mutex); /* Someone else might have been playing with it. */ goto retry; } case FL_READY: case FL_CFI_QUERY: case FL_JEDEC_QUERY: return 0; case FL_ERASING: if (!cfip || !(cfip->FeatureSupport & 2) || !(mode == FL_READY || mode == FL_POINT || (mode == FL_WRITING && (cfip->SuspendCmdSupport & 1)))) goto sleep; /* Erase suspend */ map_write(map, CMD(0xB0), adr); /* If the flash has finished erasing, then 'erase suspend' * appears to make some (28F320) flash devices switch to * 'read' mode. Make sure that we switch to 'read status' * mode so we get the right data. --rmk */ map_write(map, CMD(0x70), adr); chip->oldstate = FL_ERASING; chip->state = FL_ERASE_SUSPENDING; chip->erase_suspended = 1; for (;;) { status = map_read(map, adr); if (map_word_andequal(map, status, status_OK, status_OK)) break; if (time_after(jiffies, timeo)) { /* Urgh. Resume and pretend we weren't here. */ map_write(map, CMD(0xd0), adr); /* Make sure we're in 'read status' mode if it had finished */ map_write(map, CMD(0x70), adr); chip->state = FL_ERASING; chip->oldstate = FL_READY; printk(KERN_ERR "Chip not ready after erase " "suspended: status = 0x%lx\n", status.x[0]); return -EIO; } spin_unlock(chip->mutex); cfi_udelay(1); spin_lock(chip->mutex); /* Nobody will touch it while it's in state FL_ERASE_SUSPENDING. So we can just loop here. */ } chip->state = FL_STATUS; return 0; case FL_POINT: /* Only if there's no operation suspended... */ if (mode == FL_READY && chip->oldstate == FL_READY) return 0; default: sleep: set_current_state(TASK_UNINTERRUPTIBLE); add_wait_queue(&chip->wq, &wait); spin_unlock(chip->mutex); schedule(); remove_wait_queue(&chip->wq, &wait); spin_lock(chip->mutex); goto resettime; }}static void put_chip(struct map_info *map, struct flchip *chip, unsigned long adr){ struct cfi_private *cfi = map->fldrv_priv; if (chip->priv) { struct flchip_shared *shared = chip->priv; spin_lock(&shared->lock); if (shared->writing == chip) { /* We own the ability to write, but we're done */ shared->writing = shared->erasing; if (shared->writing && shared->writing != chip) { /* give back ownership to who we loaned it from */ struct flchip *loaner = shared->writing; spin_lock(loaner->mutex); spin_unlock(&shared->lock); spin_unlock(chip->mutex); put_chip(map, loaner, loaner->start); spin_lock(chip->mutex); spin_unlock(loaner->mutex); } else { if (chip->oldstate != FL_ERASING) { shared->erasing = NULL; if (chip->oldstate != FL_WRITING) shared->writing = NULL; } spin_unlock(&shared->lock); } } else { spin_unlock(&shared->lock); } } switch(chip->oldstate) { case FL_ERASING: chip->state = chip->oldstate; /* What if one interleaved chip has finished and the other hasn't? The old code would leave the finished one in READY mode. That's bad, and caused -EROFS errors to be returned from do_erase_oneblock because that's the only bit it checked for at the time. As the state machine appears to explicitly allow sending the 0x70 (Read Status) command to an erasing chip and expecting it to be ignored, that's what we do. */ map_write(map, CMD(0xd0), adr); map_write(map, CMD(0x70), adr); chip->oldstate = FL_READY; chip->state = FL_ERASING; break; case FL_READY: case FL_STATUS: case FL_JEDEC_QUERY: /* We should really make set_vpp() count, rather than doing this */ DISABLE_VPP(map); break; default: printk(KERN_ERR "put_chip() called with oldstate %d!!\n", chip->oldstate); } wake_up(&chip->wq);}static int do_point_onechip (struct map_info *map, struct flchip *chip, loff_t adr, size_t len){ unsigned long cmd_addr; struct cfi_private *cfi = map->fldrv_priv; int ret = 0; adr += chip->start; /* Ensure cmd read/writes are aligned. */ cmd_addr = adr & ~(map_bankwidth(map)-1); spin_lock(chip->mutex); ret = get_chip(map, chip, cmd_addr, FL_POINT); if (!ret) { if (chip->state != FL_POINT && chip->state != FL_READY) map_write(map, CMD(0xff), cmd_addr); chip->state = FL_POINT; chip->ref_point_counter++; } spin_unlock(chip->mutex); return ret;}static int cfi_intelext_point (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char **mtdbuf){ struct map_info *map = mtd->priv; struct cfi_private *cfi = map->fldrv_priv; unsigned long ofs; int chipnum; int ret = 0; if (!map->virt || (from + len > mtd->size)) return -EINVAL; *mtdbuf = (void *)map->virt + from; *retlen = 0; /* Now lock the chip(s) to POINT state */ /* ofs: offset within the first chip that the first read should start */ chipnum = (from >> cfi->chipshift); ofs = from - (chipnum << cfi->chipshift); while (len) { unsigned long thislen; if (chipnum >= cfi->numchips) break; if ((len + ofs -1) >> cfi->chipshift) thislen = (1<<cfi->chipshift) - ofs; else thislen = len; ret = do_point_onechip(map, &cfi->chips[chipnum], ofs, thislen); if (ret) break; *retlen += thislen; len -= thislen; ofs = 0; chipnum++; } return 0;}static void cfi_intelext_unpoint (struct mtd_info *mtd, u_char *addr, loff_t from, size_t len){ struct map_info *map = mtd->priv; struct cfi_private *cfi = map->fldrv_priv; unsigned long ofs; int chipnum; /* Now unlock the chip(s) POINT state */ /* ofs: offset within the first chip that the first read should start */ chipnum = (from >> cfi->chipshift); ofs = from - (chipnum << cfi->chipshift); while (len) { unsigned long thislen; struct flchip *chip; chip = &cfi->chips[chipnum];
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -