Linux内核4.14版本——Nand子系统(2)——nand_base.c分析

1. nand_scan_ident

2. nand_scan_tail

2.1 申请内存

2.2 nand_manufacturer_init初始化

2.3 设置oob相关的内存和操作集

2.4 根据EEC mode设置相关操作集,本文以 NAND_ECC_HW 为例

2.5 设置eec/oob操作时的相关参数,如steps、total等

2.6 填充mtd_info结构体

2.7 其他,如fast mode,坏块检查等

3. nand_read->nand_do_read_ops

1. nand_scan_ident

2. nand_scan_tail

我们假设ecc的mode是NAND_ECC_HW来分析这个函数。

2.1 申请内存

int nand_scan_tail(struct mtd_info *mtd)
{
    struct nand_chip *chip = mtd_to_nand(mtd);
    struct nand_ecc_ctrl *ecc = &chip->ecc;
    struct nand_buffers *nbuf = NULL;
    int ret, i;

    .........
    
    /* New bad blocks should be marked in OOB, flash-based BBT, or both */
    if (WARN_ON((chip->bbt_options & NAND_BBT_NO_OOB_BBM) &&
           !(chip->bbt_options & NAND_BBT_USE_FLASH))) {
        return -EINVAL;
    }

    if (invalid_ecc_page_accessors(chip)) {
        pr_err("Invalid ECC page accessors setup\n");
        return -EINVAL;
    }

    if (!(chip->options & NAND_OWN_BUFFERS)) {
        nbuf = kzalloc(sizeof(*nbuf), GFP_KERNEL);
        if (!nbuf)
            return -ENOMEM;

        nbuf->ecccalc = kmalloc(mtd->oobsize, GFP_KERNEL);
        if (!nbuf->ecccalc) {
            ret = -ENOMEM;
            goto err_free_nbuf;
        }

        nbuf->ecccode = kmalloc(mtd->oobsize, GFP_KERNEL);
        if (!nbuf->ecccode) {
            ret = -ENOMEM;
            goto err_free_nbuf;
        }

        nbuf->databuf = kmalloc(mtd->writesize + mtd->oobsize,
                    GFP_KERNEL);
        if (!nbuf->databuf) {
            ret = -ENOMEM;
            goto err_free_nbuf;
        }

        chip->buffers = nbuf;
    } else if (!chip->buffers) {
        return -ENOMEM;
    }
}

2.2 nand_manufacturer_init初始化

int nand_scan_tail(struct mtd_info *mtd)
{


    /*
     * FIXME: some NAND manufacturer drivers expect the first die to be
     * selected when manufacturer->init() is called. They should be fixed
     * to explictly select the relevant die when interacting with the NAND
     * chip.
     */
    chip->select_chip(mtd, 0);
    ret = nand_manufacturer_init(chip);
    chip->select_chip(mtd, -1);
    if (ret)
        goto err_free_nbuf;
}

2.3 设置oob相关的内存和操作集

int nand_scan_tail(struct mtd_info *mtd)
{
    ..........

    /* Set the internal oob buffer location, just after the page data */
    chip->oob_poi = chip->buffers->databuf + mtd->writesize;

    /*
     * If no default placement scheme is given, select an appropriate one.
     */
    if (!mtd->ooblayout &&
        !(ecc->mode == NAND_ECC_SOFT && ecc->algo == NAND_ECC_BCH)) {
        switch (mtd->oobsize) {
        case 8:
        case 16:
            mtd_set_ooblayout(mtd, &nand_ooblayout_sp_ops);
            break;
        case 64:
        case 128:
            mtd_set_ooblayout(mtd, &nand_ooblayout_lp_hamming_ops);
            break;
        default:
            WARN(1, "No oob scheme defined for oobsize %d\n",
                mtd->oobsize);
            ret = -EINVAL;
            goto err_nand_manuf_cleanup;
        }
    }
}

2.4 根据EEC mode设置相关操作集,本文以 NAND_ECC_HW 为例

int nand_scan_tail(struct mtd_info *mtd)
{
    ..........
    /*
     * Check ECC mode, default to software if 3byte/512byte hardware ECC is
     * selected and we have 256 byte pagesize fallback to software ECC
     */MTD_OPS_RAW

    switch (ecc->mode) {
    ............
    case NAND_ECC_HW:
        /* Use standard hwecc read page function? */
        if (!ecc->read_page)
            ecc->read_page = nand_read_page_hwecc;
        if (!ecc->write_page)
            ecc->write_page = nand_write_page_hwecc;
        if (!ecc->read_page_raw)
            ecc->read_page_raw = nand_read_page_raw;
        if (!ecc->write_page_raw)
            ecc->write_page_raw = nand_write_page_raw;
        if (!ecc->read_oob)
            ecc->read_oob = nand_read_oob_std;
        if (!ecc->write_oob)
            ecc->write_oob = nand_write_oob_std;
        if (!ecc->read_subpage)
            ecc->read_subpage = nand_read_subpage;
        if (!ecc->write_subpage && ecc->hwctl && ecc->calculate)
            ecc->write_subpage = nand_write_subpage_hwecc;

        .......
    }
    ...........
}

      这些函数其他事最终操作nand flash的函数,根据eec->mode的不同,会有不同的函数集。

2.5 设置eec/oob操作时的相关参数,如steps、total等

int nand_scan_tail(struct mtd_info *mtd)
{
    ........
    /* For many systems, the standard OOB write also works for raw */
    if (!ecc->read_oob_raw)
        ecc->read_oob_raw = ecc->read_oob;
    if (!ecc->write_oob_raw)
        ecc->write_oob_raw = ecc->write_oob;

    /* propagate ecc info to mtd_info */
    mtd->ecc_strength = ecc->strength;
    mtd->ecc_step_size = ecc->size;

    /*
     * Set the number of read / write steps for one page depending on ECC
     * mode.
     */
    ecc->steps = mtd->writesize / ecc->size;
    if (ecc->steps * ecc->size != mtd->writesize) {
        WARN(1, "Invalid ECC parameters\n");
        ret = -EINVAL;
        goto err_nand_manuf_cleanup;
    }
    ecc->total = ecc->steps * ecc->bytes;
    if (ecc->total > mtd->oobsize) {
        WARN(1, "Total number of ECC bytes exceeded oobsize\n");
        ret = -EINVAL;
        goto err_nand_manuf_cleanup;
    }

    /*
     * The number of bytes available for a client to place data into
     * the out of band area.
     */
    ret = mtd_ooblayout_count_freebytes(mtd);
    if (ret < 0)
        ret = 0;

    mtd->oobavail = ret;

    /* ECC sanity check: warn if it's too weak */
    if (!nand_ecc_strength_good(mtd))
        pr_warn("WARNING: %s: the ECC used on your system is too weak compared to the one required by the NAND chip\n",
            mtd->name);

    /* Allow subpage writes up to ecc.steps. Not possible for MLC flash */
    if (!(chip->options & NAND_NO_SUBPAGE_WRITE) && nand_is_slc(chip)) {
        switch (ecc->steps) {
        case 2:
            mtd->subpage_sft = 1;
            break;
        case 4:
        case 8:
        case 16:
            mtd->subpage_sft = 2;
            break;
        }
    }
    chip->subpagesize = mtd->writesize >> mtd->subpage_sft;

    /* Initialize state */
    chip->state = FL_READY;

    /* Invalidate the pagebuffer reference */
    chip->pagebuf = -1;

    /* Large page NAND with SOFT_ECC should support subpage reads */
    switch (ecc->mode) {
    case NAND_ECC_SOFT:
        if (chip->page_shift > 9)
            chip->options |= NAND_SUBPAGE_READ;
        break;

    default:
        break;
    }
    ............
}

2.6 填充mtd_info结构体

int nand_scan_tail(struct mtd_info *mtd)
{
    ........

    /* Fill in remaining MTD driver data */
    mtd->type = nand_is_slc(chip) ? MTD_NANDFLASH : MTD_MLCNANDFLASH;
    mtd->flags = (chip->options & NAND_ROM) ? MTD_CAP_ROM :
                        MTD_CAP_NANDFLASH;
    mtd->_erase = nand_erase;
    mtd->_point = NULL;
    mtd->_unpoint = NULL;
    mtd->_read = nand_read;
    mtd->_write = nand_write;
    mtd->_panic_write = panic_nand_write;
    mtd->_read_oob = nand_read_oob;
    mtd->_write_oob = nand_write_oob;
    mtd->_sync = nand_sync;
    mtd->_lock = NULL;
    mtd->_unlock = NULL;
    mtd->_suspend = nand_suspend;
    mtd->_resume = nand_resume;
    mtd->_reboot = nand_shutdown;
    mtd->_block_isreserved = nand_block_isreserved;
    mtd->_block_isbad = nand_block_isbad;
    mtd->_block_markbad = nand_block_markbad;
    mtd->_max_bad_blocks = nand_max_bad_blocks;
    mtd->writebufsize = mtd->writesize;
    ...........
}

      填充mtd结构体的相关函数,这些都是mtd子系统需要用到的接口,必须填充它们。

2.7 其他,如fast mode,坏块检查等

/**
 * nand_scan_tail - [NAND Interface] Scan for the NAND device
 * @mtd: MTD device structure
 *
 * This is the second phase of the normal nand_scan() function. It fills out
 * all the uninitialized function pointers with the defaults and scans for a
 * bad block table if appropriate.
 */
int nand_scan_tail(struct mtd_info *mtd)
{
    ........

    /*
     * Initialize bitflip_threshold to its default prior scan_bbt() call.
     * scan_bbt() might invoke mtd_read(), thus bitflip_threshold must be
     * properly set.
     */
    if (!mtd->bitflip_threshold)
        mtd->bitflip_threshold = DIV_ROUND_UP(mtd->ecc_strength * 3, 4);

    /* Initialize the ->data_interface field. */
    ret = nand_init_data_interface(chip);
    if (ret)
        goto err_nand_manuf_cleanup;

    /* Enter fastest possible mode on all dies. */
    for (i = 0; i < chip->numchips; i++) {
        chip->select_chip(mtd, i);
        ret = nand_setup_data_interface(chip, i);
        chip->select_chip(mtd, -1);

        if (ret)
            goto err_nand_data_iface_cleanup;
    }

    /* Check, if we should skip the bad block table scan */
    if (chip->options & NAND_SKIP_BBTSCAN)
        return 0;

    /* Build bad block table */
    ret = chip->scan_bbt(mtd);
    if (ret)
        goto err_nand_data_iface_cleanup;
    ............

}

3. nand_read->nand_do_read_ops

      上层调用读nand flash的操作时,最终调用mtd->_read--->nand_read。我们分析这个函数的实现。

/**
 * nand_read - [MTD Interface] MTD compatibility function for nand_do_read_ecc
 * @mtd: MTD device structure
 * @from: offset to read from
 * @len: number of bytes to read
 * @retlen: pointer to variable to store the number of read bytes
 * @buf: the databuffer to put data
 *
 * Get hold of the chip and call nand_do_read.
 */
static int nand_read(struct mtd_info *mtd, loff_t from, size_t len,
             size_t *retlen, uint8_t *buf)
{
    struct mtd_oob_ops ops;
    int ret;

    nand_get_device(mtd, FL_READING);
    memset(&ops, 0, sizeof(ops));
    ops.len = len;
    ops.datbuf = buf;
    ops.mode = MTD_OPS_PLACE_OOB;
    ret = nand_do_read_ops(mtd, from, &ops);
    *retlen = ops.retlen;
    nand_release_device(mtd);
    return ret;
}

      最终调用nand_do_read_ops函数,注意ops.mode = MTD_OPS_PLACE_OOB。

/**
 * nand_do_read_ops - [INTERN] Read data with ECC
 * @mtd: MTD device structure
 * @from: offset to read from
 * @ops: oob ops structure
 *
 * Internal function. Called with chip held.
 */
static int nand_do_read_ops(struct mtd_info *mtd, loff_t from,
                struct mtd_oob_ops *ops)
{
    int chipnr, page, realpage, col, bytes, aligned, oob_required;
    struct nand_chip *chip = mtd_to_nand(mtd);
    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_bufpoi;
    unsigned int max_bitflips = 0;
    int retry_mode = 0;
    bool ecc_fail = false;

    chipnr = (int)(from >> chip->chip_shift);
    chip->select_chip(mtd, chipnr);                         // (1)

    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) {
        unsigned int ecc_failures = mtd->ecc_stats.failed;

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

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

        /* Is the current page in the buffer? */
        if (realpage != chip->pagebuf || oob) {
            bufpoi = use_bufpoi ? chip->buffers->databuf : buf;

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

read_retry:
            if (nand_standard_page_accessors(&chip->ecc))
                chip->cmdfunc(mtd, NAND_CMD_READ0, 0x00, page);     // (2)

            /*
             * 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(mtd, chip, bufpoi,
                                  oob_required,
                                  page);
            else if (!aligned && NAND_HAS_SUBPAGE_READ(chip) &&
                 !oob)
                ret = chip->ecc.read_subpage(mtd, chip,
                            col, bytes, bufpoi,
                            page);
            else
                ret = chip->ecc.read_page(mtd, chip, bufpoi,         // (3)
                              oob_required, page);
            if (ret < 0) {
                if (use_bufpoi)
                    /* Invalidate page cache */
                    chip->pagebuf = -1;
                break;
            }

            /* Transfer not aligned data */
            if (use_bufpoi) {
                if (!NAND_HAS_SUBPAGE_READ(chip) && !oob &&
                    !(mtd->ecc_stats.failed - ecc_failures) &&
                    (ops->mode != MTD_OPS_RAW)) {
                    chip->pagebuf = realpage;
                    chip->pagebuf_bitflips = ret;
                } else {
                    /* Invalidate page cache */
                    chip->pagebuf = -1;
                }
                memcpy(buf, chip->buffers->databuf + col, bytes);
            }

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

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

            if (chip->options & NAND_NEED_READRDY) {
                /* Apply delay or wait for ready/busy pin */
                if (!chip->dev_ready)
                    udelay(chip->chip_delay);
                else
                    nand_wait_ready(mtd);
            }

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

                    /* Reset failures; retry */
                    mtd->ecc_stats.failed = ecc_failures;
                    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->buffers->databuf + col, bytes);
            buf += bytes;
            max_bitflips = max_t(unsigned int, max_bitflips,
                         chip->pagebuf_bitflips);
        }

        readlen -= bytes;

        /* Reset to retry mode 0 */
        if (retry_mode) {
            ret = nand_setup_read_retry(mtd, 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++;
            chip->select_chip(mtd, -1);
            chip->select_chip(mtd, chipnr);
        }
    }
    chip->select_chip(mtd, -1);                                // (6)

    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;
}

(1) 选中芯片
(2) 发送读命令
(3) 把数据读到内存中,这个函数最终调用的是在nand_scan_tail中设置的nand_read_page_hwecc函数.

/**
 * nand_read_page_hwecc - [REPLACEABLE] hardware ECC based page read function
 * @mtd: mtd info structure
 * @chip: nand chip info structure
 * @buf: buffer to store read data
 * @oob_required: caller requires OOB data read to chip->oob_poi
 * @page: page number to read
 *
 * Not for syndrome calculating ECC controllers which need a special oob layout.
 */
static int nand_read_page_hwecc(struct mtd_info *mtd, struct nand_chip *chip,
                uint8_t *buf, int oob_required, int page)
{
    int i, eccsize = chip->ecc.size, ret;
    int eccbytes = chip->ecc.bytes;
    int eccsteps = chip->ecc.steps;
    uint8_t *p = buf;
    uint8_t *ecc_calc = chip->buffers->ecccalc;
    uint8_t *ecc_code = chip->buffers->ecccode;
    unsigned int max_bitflips = 0;

    for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
        chip->ecc.hwctl(mtd, NAND_ECC_READ);
        chip->read_buf(mtd, p, eccsize);
        chip->ecc.calculate(mtd, p, &ecc_calc[i]);
    }
    chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);

    ret = mtd_ooblayout_get_eccbytes(mtd, ecc_code, chip->oob_poi, 0,
                     chip->ecc.total);
    if (ret)
        return ret;

    eccsteps = chip->ecc.steps;
    p = buf;

    for (i = 0 ; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
        int stat;

        stat = chip->ecc.correct(mtd, p, &ecc_code[i], &ecc_calc[i]);
        if (stat == -EBADMSG &&
            (chip->ecc.options & NAND_ECC_GENERIC_ERASED_CHECK)) {
            /* check for empty pages with bitflips */
            stat = nand_check_erased_ecc_chunk(p, eccsize,
                        &ecc_code[i], eccbytes,
                        NULL, 0,
                        chip->ecc.strength);
        }

        if (stat < 0) {
            mtd->ecc_stats.failed++;
        } else {
            mtd->ecc_stats.corrected += stat;
            max_bitflips = max_t(unsigned int, max_bitflips, stat);
        }
    }
    return max_bitflips;
}

(4) oob的操作
(5) eec校验
(6) 取消片选

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值