static int nand_do_read_ops()

in nand/raw/nand_base.c [3398:3570]


static int nand_do_read_ops(struct nand_chip *chip, loff_t from,
			    struct mtd_oob_ops *ops)
{
	int chipnr, page, realpage, col, bytes, aligned, oob_required;
	struct mtd_info *mtd = nand_to_mtd(chip);
	int ret = 0;
	uint32_t readlen = ops->len;
	uint32_t oobreadlen = ops->ooblen;
	uint32_t max_oobsize = mtd_oobavail(mtd, ops);

	uint8_t *bufpoi, *oob, *buf;
	int use_bounce_buf;
	unsigned int max_bitflips = 0;
	int retry_mode = 0;
	bool ecc_fail = false;

	/* Check if the region is secured */
	if (nand_region_is_secured(chip, from, readlen))
		return -EIO;

	chipnr = (int)(from >> chip->chip_shift);
	nand_select_target(chip, chipnr);

	realpage = (int)(from >> chip->page_shift);
	page = realpage & chip->pagemask;

	col = (int)(from & (mtd->writesize - 1));

	buf = ops->datbuf;
	oob = ops->oobbuf;
	oob_required = oob ? 1 : 0;

	while (1) {
		struct mtd_ecc_stats ecc_stats = mtd->ecc_stats;

		bytes = min(mtd->writesize - col, readlen);
		aligned = (bytes == mtd->writesize);

		if (!aligned)
			use_bounce_buf = 1;
		else if (chip->options & NAND_USES_DMA)
			use_bounce_buf = !virt_addr_valid(buf) ||
					 !IS_ALIGNED((unsigned long)buf,
						     chip->buf_align);
		else
			use_bounce_buf = 0;

		/* Is the current page in the buffer? */
		if (realpage != chip->pagecache.page || oob) {
			bufpoi = use_bounce_buf ? chip->data_buf : buf;

			if (use_bounce_buf && aligned)
				pr_debug("%s: using read bounce buffer for buf@%p\n",
						 __func__, buf);

read_retry:
			/*
			 * Now read the page into the buffer.  Absent an error,
			 * the read methods return max bitflips per ecc step.
			 */
			if (unlikely(ops->mode == MTD_OPS_RAW))
				ret = chip->ecc.read_page_raw(chip, bufpoi,
							      oob_required,
							      page);
			else if (!aligned && NAND_HAS_SUBPAGE_READ(chip) &&
				 !oob)
				ret = chip->ecc.read_subpage(chip, col, bytes,
							     bufpoi, page);
			else
				ret = chip->ecc.read_page(chip, bufpoi,
							  oob_required, page);
			if (ret < 0) {
				if (use_bounce_buf)
					/* Invalidate page cache */
					chip->pagecache.page = -1;
				break;
			}

			/*
			 * Copy back the data in the initial buffer when reading
			 * partial pages or when a bounce buffer is required.
			 */
			if (use_bounce_buf) {
				if (!NAND_HAS_SUBPAGE_READ(chip) && !oob &&
				    !(mtd->ecc_stats.failed - ecc_stats.failed) &&
				    (ops->mode != MTD_OPS_RAW)) {
					chip->pagecache.page = realpage;
					chip->pagecache.bitflips = ret;
				} else {
					/* Invalidate page cache */
					chip->pagecache.page = -1;
				}
				memcpy(buf, bufpoi + col, bytes);
			}

			if (unlikely(oob)) {
				int toread = min(oobreadlen, max_oobsize);

				if (toread) {
					oob = nand_transfer_oob(chip, oob, ops,
								toread);
					oobreadlen -= toread;
				}
			}

			nand_wait_readrdy(chip);

			if (mtd->ecc_stats.failed - ecc_stats.failed) {
				if (retry_mode + 1 < chip->read_retries) {
					retry_mode++;
					ret = nand_setup_read_retry(chip,
							retry_mode);
					if (ret < 0)
						break;

					/* Reset ecc_stats; retry */
					mtd->ecc_stats = ecc_stats;
					goto read_retry;
				} else {
					/* No more retry modes; real failure */
					ecc_fail = true;
				}
			}

			buf += bytes;
			max_bitflips = max_t(unsigned int, max_bitflips, ret);
		} else {
			memcpy(buf, chip->data_buf + col, bytes);
			buf += bytes;
			max_bitflips = max_t(unsigned int, max_bitflips,
					     chip->pagecache.bitflips);
		}

		readlen -= bytes;

		/* Reset to retry mode 0 */
		if (retry_mode) {
			ret = nand_setup_read_retry(chip, 0);
			if (ret < 0)
				break;
			retry_mode = 0;
		}

		if (!readlen)
			break;

		/* For subsequent reads align to page boundary */
		col = 0;
		/* Increment page address */
		realpage++;

		page = realpage & chip->pagemask;
		/* Check, if we cross a chip boundary */
		if (!page) {
			chipnr++;
			nand_deselect_target(chip);
			nand_select_target(chip, chipnr);
		}
	}
	nand_deselect_target(chip);

	ops->retlen = ops->len - (size_t) readlen;
	if (oob)
		ops->oobretlen = ops->ooblen - oobreadlen;

	if (ret < 0)
		return ret;

	if (ecc_fail)
		return -EBADMSG;

	return max_bitflips;
}