MTD(Memory Technology Device) -3-

<kernel v4.14>

커널 v4.16부터 NAND subsystem이 다시 정돈(refactoring)되고 있다. 어느 정도 자리 잡고나면 추후 커널 5.x에서 다시 한 번 개선된 기능을 소개할 예정이다.

 

NAND Subsystem Core

NAND 스캔

nand 디바이스의 id 값을 읽어 제조사 정보와 페이지 사이즈(pagesize), 칩 사이즈(chipsize), 블록 사이즈(erasesize), oobsize, ECC step당 에러 교정 비트 수(strength), ECC 스텝 바이트 수(step)  등을 파악하기 위해 다음과 같이 동작한다.

  • id를 nand_ids 테이블과 비교하여 하드 코딩된 구(old) 칩 정보 사용
  • id 값이 “ONFI”인 경우 ONFI 파라메터 정보를 읽어 사용
  • id 값이 “JEDEC”인 경우 JEDEC 파라메터 정보를 읽어 사용

 

 

nand_scan()

drivers/mtd/nand/nand_base.c

/**
 * nand_scan - [NAND Interface] Scan for the NAND device
 * @mtd: MTD device structure
 * @maxchips: number of chips to scan for
 *
 * This fills out all the uninitialized function pointers with the defaults.
 * The flash ID is read and the mtd/chip structures are filled with the
 * appropriate values.
 */
int nand_scan(struct mtd_info *mtd, int maxchips)
{
        int ret;

        ret = nand_scan_ident(mtd, maxchips, NULL);
        if (!ret)
                ret = nand_scan_tail(mtd);
        return ret;
}
EXPORT_SYMBOL(nand_scan);

NAND 디바이스를 스캔한다.

 

NAND 스캔 – Phase I

nand_scan_ident()

drivers/mtd/nand/nand_base.c”

/**
 * nand_scan_ident - [NAND Interface] Scan for the NAND device
 * @mtd: MTD device structure
 * @maxchips: number of chips to scan for
 * @table: alternative NAND ID table
 *
 * This is the first phase of the normal nand_scan() function. It reads the
 * flash ID and sets up MTD fields accordingly.
 *
 */
int nand_scan_ident(struct mtd_info *mtd, int maxchips,
                    struct nand_flash_dev *table)
{
        int i, nand_maf_id, nand_dev_id;
        struct nand_chip *chip = mtd_to_nand(mtd);
        int ret;

        ret = nand_dt_init(chip);
        if (ret)
                return ret;

        if (!mtd->name && mtd->dev.parent)
                mtd->name = dev_name(mtd->dev.parent);

        if ((!chip->cmdfunc || !chip->select_chip) && !chip->cmd_ctrl) {
                /*
                 * Default functions assigned for chip_select() and
                 * cmdfunc() both expect cmd_ctrl() to be populated,
                 * so we need to check that that's the case
                 */
                pr_err("chip.cmd_ctrl() callback is not provided");
                return -EINVAL;
        }
        /* Set the default functions */
        nand_set_defaults(chip);

        /* Read the flash type */
        ret = nand_detect(chip, table);
        if (ret) {
                if (!(chip->options & NAND_SCAN_SILENT_NODEV))
                        pr_warn("No NAND device found\n");
                chip->select_chip(mtd, -1);
                return ret;
        }

        nand_maf_id = chip->id.data[0];
        nand_dev_id = chip->id.data[1];

        chip->select_chip(mtd, -1);

        /* Check for a chip array */
        for (i = 1; i < maxchips; i++) {
                /* See comment in nand_get_flash_type for reset */
                nand_reset(chip, i);

                chip->select_chip(mtd, i);
                /* Send the command for reading device ID */
                chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
                /* Read manufacturer and device IDs */
                if (nand_maf_id != chip->read_byte(mtd) ||
                    nand_dev_id != chip->read_byte(mtd)) {
                        chip->select_chip(mtd, -1);
                        break;
                }
                chip->select_chip(mtd, -1);
        }
        if (i > 1)
                pr_info("%d chips detected\n", i);

        /* Store the number of chips and calc total size for mtd */
        chip->numchips = i;
        mtd->size = i * chip->chipsize;

        return 0;
}
EXPORT_SYMBOL(nand_scan_ident);
  • 코드 라인 18~20에서 디바이스 트리에서 NAND 관련 속성 값을 알아와서 지정한다.
  • 코드 라인 25~33에서 (*cmd_ctrl) 후크 함수가 구현되지 않았을 때 (*cmdfunc) 및 (*select_chip) 후크 함수가 구현되어 있어야 한다.
  • 코드 라인 35에서 낸드 칩 관련 디폴트 구현 함수를 지정한다.
  • 코드 라인 38~44에서 플래시 타입을 읽어온다.
  • 코드 라인 46~66에서 여러 개의 nand chip을 사용하는 경우 READID를 읽어온다.
  • 코드 라인 67~68에서 detect된 칩 수를 출력한다.

 

nand_dt_init()

drivers/mtd/nand/nand_base.c

static int nand_dt_init(struct nand_chip *chip)
{
        struct device_node *dn = nand_get_flash_node(chip);
        int ecc_mode, ecc_algo, ecc_strength, ecc_step;

        if (!dn)
                return 0;

        if (of_get_nand_bus_width(dn) == 16)
                chip->options |= NAND_BUSWIDTH_16;

        if (of_get_nand_on_flash_bbt(dn))
                chip->bbt_options |= NAND_BBT_USE_FLASH;

        ecc_mode = of_get_nand_ecc_mode(dn);
        ecc_algo = of_get_nand_ecc_algo(dn);
        ecc_strength = of_get_nand_ecc_strength(dn);
        ecc_step = of_get_nand_ecc_step_size(dn);

        if (ecc_mode >= 0)
                chip->ecc.mode = ecc_mode;

        if (ecc_algo >= 0)
                chip->ecc.algo = ecc_algo;

        if (ecc_strength >= 0)
                chip->ecc.strength = ecc_strength;

        if (ecc_step > 0)
                chip->ecc.size = ecc_step;

        if (of_property_read_bool(dn, "nand-ecc-maximize"))
                chip->ecc.options |= NAND_ECC_MAXIMIZE;

        return 0;
}

디바이스 트리에서 NAND 관련 속성 값을 알아와서 지정한다.

  • 코드 라인 9~10에서 버스 크기 설정을 위해 “nand-bus-width” 속성 값을 읽어와서 16인 경우 option에 플래그를 설정한다.
    • 8 또는 16
  • 코드 라인 12~13에서 BBT 사용여부를 알아오기 위해 “nand-on-flash-bbt” 속성이 있는지 여부를 알아온다.
  • 코드 라인 15에서 지원되는 ecc 모드를 알아오기 위해  “nand-ecc-mode” 속성 값을 읽어온다.
    • “none”, “soft”, “hw”, “hw_syndrome”, “hw_oob_first”, “on-die”
    • 호환을 위해서 “soft_bch” 속성 값도 지원한다.
  • 코드 라인 16에서 ecc 알고리즘을 알아오기 위해 “nand-ecc-algo” 속성 값을 읽어온다.
    • “hamming”, “bch”
  • 코드 라인 17에서 ecc 스텝당 correction 할 수 있는 최대 비트 수를 알아오기 위해 “nand-ecc-strength” 속성 값을 읽어온다.
  • 코드 라인 18에서 ecc 스텝 사이즈를 알아오기 위해 “nand-ecc-step-size” 속성 값을 읽어온다.
    • 512 또는 1024
  • 코드 라인 32~33에서 “nand-ecc-maximize” 속성 값이 지정된 경우 최고 수준의 ecc strength를 사용하도록 옵션 요청한다.

 

NAND 스캔 – Phase II

nand_scan_tail()

drivers/mtd/nand/nand_base.c -1/4-

/**
 * 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)
{
        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;
        }

        /*
         * 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;

        /* 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;
                }
        }
  • 코드 라인 9~12에서 BBT NAND_BBT_NO_OOB_BBM 옵션이 사용된 경우 NAND_BBT_USE_FLASH 옵션이 같이 사용되지 않은 경우 에러를 반환한다.
  • 코드 라인 14~17에서 표준이 아닌 custom 방식의 ECC 제어를 사용하는 경우 ecc 페이지 접근 후크 함수가 설정되지 않은 경우 에러를 반환한다.
  • 코드 라인 19~46에서 NAND_OWN_BUFFERS 옵션을 사용하지 않는 일반적인 방법인 경우 NAND 코어에서 버퍼를 kmalloc()으로 할당한다.
    • dma 버퍼로 위의 메모리를 사용할 수 없는 시스템인 경우 NAND_OWN_BUFFERS를 사용하여 NAND 호스트 드라이버에서 미리 지정할 수 있다.
  • 코드 라인 54~58 각 제조사의 초기화 후크 함수를 호출한다. 호출 전에 첫 번째 칩을 선택하게한다.
  • 코드 라인 61에서 페이지 데이터 뒤의 내부 oob 버퍼 위치를 지정한다.
  • 코드 라인 66~83에서 대부분 제조사의 드라이버에 ooblayout이 지정되어 있다. 만일 ooblayout이 지정되지 않고, soft 모드도 아니고 bch 알고리즘도 아닌 경우  oobsize에 따라 다음과 같이 디폴트 ooblayout을 지정한다.
    • oobsize가 8 또는 16인 경우 ecc 사용 시 small 페이지 관련 nand_ooblayout_ecc_sp() 함수와 ecc free 시 nand_ooblayout_free_sp() 함수를 호출하게 한다.
      • nand_ooblayout_ecc_sp()
        • oob 섹션이 0개일 때 offset은 0으로 지정되고, length=3(oobsize=8)과 4(oobsize=128)로 지정된다.
        • oob 섹션이 1개이고 oobsize가 16일 때 offset은 6으로 지정되고, length=ecc->total – 4로 지정된다.
        • 그 외의 경우 에러를 리턴한다.
    • oobsize가 64 또는 128인 경우 ecc 사용 시 large 페이지 관련 nand_ooblayout_ecc_lp_hamming() 함수와 ecc free 시 nand_ooblayout_free_lp_hamming() 함수를 호출하게 한다.
      • nand_ooblayout_ecc_lp_hamming()
        • offset은 40(oobsize=64)과 80(oobsize=128)으로 지정되고, length=ecc->total로 지정된다.
        • oob 섹션이 2개 이상인 경우 에러를 리턴한다.

 

다음 그림은 버퍼 메모리를 생성하는 두 가지 방법을 보여준다.

 

 

 

drivers/mtd/nand/nand_base.c -2/4-

.       /*
         * Check ECC mode, default to software if 3byte/512byte hardware ECC is
         * selected and we have 256 byte pagesize fallback to software ECC
         */

        switch (ecc->mode) {
        case NAND_ECC_HW_OOB_FIRST:
                /* Similar to NAND_ECC_HW, but a separate read_page handle */
                if (!ecc->calculate || !ecc->correct || !ecc->hwctl) {
                        WARN(1, "No ECC functions supplied; hardware ECC not possible\n");
                        ret = -EINVAL;
                        goto err_nand_manuf_cleanup;
                }
                if (!ecc->read_page)
                        ecc->read_page = nand_read_page_hwecc_oob_first;

        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;

        case NAND_ECC_HW_SYNDROME:
                if ((!ecc->calculate || !ecc->correct || !ecc->hwctl) &&
                    (!ecc->read_page ||
                     ecc->read_page == nand_read_page_hwecc ||
                     !ecc->write_page ||
                     ecc->write_page == nand_write_page_hwecc)) {
                        WARN(1, "No ECC functions supplied; hardware ECC not possible\n");
                        ret = -EINVAL;
                        goto err_nand_manuf_cleanup;
                }
                /* Use standard syndrome read/write page function? */
                if (!ecc->read_page)
                        ecc->read_page = nand_read_page_syndrome;
                if (!ecc->write_page)
                        ecc->write_page = nand_write_page_syndrome;
                if (!ecc->read_page_raw)
                        ecc->read_page_raw = nand_read_page_raw_syndrome;
                if (!ecc->write_page_raw)
                        ecc->write_page_raw = nand_write_page_raw_syndrome;
                if (!ecc->read_oob)
                        ecc->read_oob = nand_read_oob_syndrome;
                if (!ecc->write_oob)
                        ecc->write_oob = nand_write_oob_syndrome;

                if (mtd->writesize >= ecc->size) {
                        if (!ecc->strength) {
                                WARN(1, "Driver must set ecc.strength when using hardware ECC\n");
                                ret = -EINVAL;
                                goto err_nand_manuf_cleanup;
                        }
                        break;
                }
                pr_warn("%d byte HW ECC not possible on %d byte page size, fallback to SW ECC\n",
                        ecc->size, mtd->writesize);
                ecc->mode = NAND_ECC_SOFT;
                ecc->algo = NAND_ECC_HAMMING;

        case NAND_ECC_SOFT:
                ret = nand_set_ecc_soft_ops(mtd);
                if (ret) {
                        ret = -EINVAL;
                        goto err_nand_manuf_cleanup;
                }
                break;

        case NAND_ECC_ON_DIE:
                if (!ecc->read_page || !ecc->write_page) {
                        WARN(1, "No ECC functions supplied; on-die ECC not possible\n");
                        ret = -EINVAL;
                        goto err_nand_manuf_cleanup;
                }
                if (!ecc->read_oob)
                        ecc->read_oob = nand_read_oob_std;
                if (!ecc->write_oob)
                        ecc->write_oob = nand_write_oob_std;
                break;

        case NAND_ECC_NONE:
                pr_warn("NAND_ECC_NONE selected by board driver. This is not recommended!\n");
                ecc->read_page = nand_read_page_raw;
                ecc->write_page = nand_write_page_raw;
                ecc->read_oob = nand_read_oob_std;
                ecc->read_page_raw = nand_read_page_raw;
                ecc->write_page_raw = nand_write_page_raw;
                ecc->write_oob = nand_write_oob_std;
                ecc->size = mtd->writesize;
                ecc->bytes = 0;
                ecc->strength = 0;
                break;

        default:
                WARN(1, "Invalid NAND_ECC_MODE %d\n", ecc->mode);
                ret = -EINVAL;
                goto err_nand_manuf_cleanup;
        }
  • 코드 라인 6~ ecc 모드에 따라 각 제조사 드라이버에서 지원하지 않는 후크 함수를 디폴트 함수로 지정한다.

 

다음 그림은 제조사 드라이버가 특별히 지정하지 않는 경우 모드에 따라 지정되는 디폴트 함수를 보여준다.

 

drivers/mtd/nand/nand_base.c -3/4-

.       /* 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 ree
quired 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;
        }

 

  • 코드 라인 8~9에서 ecc의 strength와 size 정보로 mtd 정보를 채운다.
  • 코드 라인 15~20에서 ecc->steps는 페이지를 ecc 사이즈로 나눈 개수이다.
    • 예) mtd->writesize=2048 (2K), ecc->size=512
      • ecc->steps=4
  • 코드 라인 21~26에서 ecc->total에는 페이지에 사용될 바이트 수가 들어간다.
    • 예) ecc->steps=4, ecc->bytes=512
      • ecc->total=2048 (2K)
  • 코드 라인 32~36에서 mtd->oobavail에 모든 ecc 섹션에서 사용할 수 있는 바이트 수를 반환한다.
    • 모든 ecc 섹션의 length를 더한 값을 반환한다.
  • 코드 라인 39~42에서 ecc strength에 대한 sanity 체크를 수행한다.
  • 코드 라인 45~57에서 subpage 사이즈는 페이지 사이즈(writesize) / mtd->subpage_sht이다. 그러나 slc가 아닌 nand에서 subpage를 사용하는 경우 ecc 스텝이 2인 경우 subpage는 writesize와 동일하게 사용해야 하고, ecc 스텝이 4, 8, 16인 경우 페이지 사이즈의 절반까지 사용할 수 있게 한다.
  • 코드 라인 60에서 칩 디바이스를 읽고 쓸 때마다 state 상태를 사용하여 동기화를 수행한다. 칩 초기 상태를 FL_READY로 한다.
  • 코드 라인 63에서 페이지 버퍼를 일단 invalidate 상태로 둔다.
  • 코드 라인 66~74에서 soft 모드를 사용하는 경우 large 페이지(ecc 스텝 크기가 1024 이상)인 경우 드라이버 옵션에 서브페이지 읽기 옵션을 추가한다.

 

drivers/mtd/nand/nand_base.c -4/4-

        /* 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;

        /*
         * 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;

        return 0;

err_nand_data_iface_cleanup:
        nand_release_data_interface(chip);

err_nand_manuf_cleanup:
        nand_manufacturer_cleanup(chip);

err_free_nbuf:
        if (nbuf) {
                kfree(nbuf->databuf);
                kfree(nbuf->ecccode);
                kfree(nbuf->ecccalc);
                kfree(nbuf);
        }

        return ret;
}
EXPORT_SYMBOL(nand_scan_tail);

nand 스캔의 2번째 phase로 초기화되지 않은 변수 및 나머지 펑션들을 지정하고, 각 칩의 데이터 인터페이스를 셋업한 후 bbt를 스캔한다.

  • 코드 라인 2에서 낸드 드라이버의 mtd 타입을 지정한다.
    • slc의 경우 MTD_NANDFLASH(4), 그 외의 경우 MTD_MLCNANDFLASH(8)
  • 코드 라인 3~4에서 NAND_ROM 옵션이 있는 경우 MTD_CAP_ROM 플래그를 추가하고 그렇지 않은 경우 MTD_CAP_NANDFLASH 플래그를 추가한다.
  • 코드 라인 5~23에서 mtd의 디폴트 nand 후크 함수 등을 지정한다.
  • 코드 라인 30~31에서 scan_bbt()를 통해 bitflip 스레졸드 값이 설정되지 않은 경우 ecc_strength의 3배로 지정하되 4 단위로 올림 처리한다.
    • fallback 예) mtd->ecc_strength=1
      • mtd->bitflip_threshold=4
  • 코드 라인 34~36에서 nand 데이터 버스 타이밍 관련 많은 값들을 설정한다. ONFI 호환 칩인 경우 지원하는 타이밍 모드의 ONFI 파라메터를 읽어 사용한다. ONFI 호환 칩이 아닌 경우 제조사 NAND 호스트 드라이버의 (*setup_data_interface) 후크 함수를 호출하여 타이밍 관련 값을 사용한다.
  •  코드 라인 39~46에서 모든 칩을 한번 씩 선택 하고 데이터 인터페이스를 셋업하기 위해 제조사의 (*setup_data_interface) 후크 함수를 호출한다. ONFI 호환 칩인 경우에는 타이밍 모드에 대한 ONFI 관련 feature 를 설정하기 위해 제조사의 (*onfi_set_features) 후크 함수를 호출한다.
  • 코드 라인 49~50에서 NAND_SKIP_BBTSCAN 옵션이 사용된 경우 bbt 스캔을 하지 않고 함수를 정상적으로 빠져나간다.
  • 코드 라인 53~55에서 bbt를 스캔한다.

 

NAND Detection

nand_detect()

drivers/mtd/nand/nand_base.c -1/3-

/*
 * Get the flash and manufacturer id and lookup if the type is supported.
 */
static int nand_detect(struct nand_chip *chip, struct nand_flash_dev *type)
{
        const struct nand_manufacturer *manufacturer;
        struct mtd_info *mtd = nand_to_mtd(chip);
        int busw;
        int i;
        u8 *id_data = chip->id.data;
        u8 maf_id, dev_id;

        /*
         * Reset the chip, required by some chips (e.g. Micron MT29FxGxxxxx)
         * after power-up.
         */
        nand_reset(chip, 0);

        /* Select the device */
        chip->select_chip(mtd, 0);

        /* Send the command for reading device ID */
        chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);

        /* Read manufacturer and device IDs */
        maf_id = chip->read_byte(mtd);
        dev_id = chip->read_byte(mtd);

        /*
         * Try again to make sure, as some systems the bus-hold or other
         * interface concerns can cause random data which looks like a
         * possibly credible NAND flash to appear. If the two results do
         * not match, ignore the device completely.
         */

        chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);

        /* Read entire ID string */
        for (i = 0; i < ARRAY_SIZE(chip->id.data); i++)
                id_data[i] = chip->read_byte(mtd);

        if (id_data[0] != maf_id || id_data[1] != dev_id) {
                pr_info("second ID read did not match %02x,%02x against %02x,%02x\n",
                        maf_id, dev_id, id_data[0], id_data[1]);
                return -ENODEV;
        }

        chip->id.len = nand_id_len(id_data, ARRAY_SIZE(chip->id.data));

        /* Try to identify manufacturer */
        manufacturer = nand_get_manufacturer(maf_id);
        chip->manufacturer.desc = manufacturer;

        if (!type)
                type = nand_flash_ids;
  • 코드 라인 17에서 Micron 사 특정 칩들의 경우 power-up한 후 reset을 필요로하는 경우가 있다.
  • 코드 라인 20~27에서 첫 디바이스를 선택하고 READID 명령을 전송한 후 제조사 ID 1 바이트와 디바이스 ID 1 바이트를 수신한다.
  • 코드 라인 36~46에서 다시 한 번 확인하기 위해 READID 명령을 전송한 후 ID 문자열 전부를 읽어낸다. 만일 처음에 읽은 ID와 다른 경우 에러를 출력하고 함수를 반환한다.
  • 코드 라인 48에서 id 길이를 저장한다.
  • 코드 라인 51~52에서 제조사 ID로 제조사 정보를 파악해온다.
  • 코드 라인 54~55에서 인자로 받은 디바이스 리스트(type)가 없는 경우 nand_flash_ids[]에 담긴 낸드 플래시 디바이스 리스트를 사용하도록 한다.
    • 낸드 플래시 디바이스 리스트에는 이름, id, pagesize, chipsize, erasesize, oobsize, strength, step 정보 등이 담겨있다.

 

drivers/mtd/nand/nand_base.c -2/3-

        /*
         * Save the NAND_BUSWIDTH_16 flag before letting auto-detection logic
         * override it.
         * This is required to make sure initial NAND bus width set by the
         * NAND controller driver is coherent with the real NAND bus width
         * (extracted by auto-detection code).
         */
        busw = chip->options & NAND_BUSWIDTH_16;

        /*
         * The flag is only set (never cleared), reset it to its default value
         * before starting auto-detection.
         */
        chip->options &= ~NAND_BUSWIDTH_16;

        for (; type->name != NULL; type++) {
                if (is_full_id_nand(type)) {
                        if (find_full_id_nand(chip, type))
                                goto ident_done;
                } else if (dev_id == type->dev_id) {
                        break;
                }
        }

        chip->onfi_version = 0;
        if (!type->name || !type->pagesize) {
                /* Check if the chip is ONFI compliant */
                if (nand_flash_detect_onfi(chip))
                        goto ident_done;

                /* Check if the chip is JEDEC compliant */
                if (nand_flash_detect_jedec(chip))
                        goto ident_done;
        }

        if (!type->name)
                return -ENODEV;

        if (!mtd->name)
                mtd->name = type->name;

        chip->chipsize = (uint64_t)type->chipsize << 20;

        if (!type->pagesize)
                nand_manufacturer_detect(chip);
        else
                nand_decode_id(chip, type);

        /* Get chip options */
        chip->options |= type->options;
  • 코드 라인 8에서 버스 폭이 16bit인지 여부를 알아온다.
  • 코드 라인 14에서 버스 폭 정보는 auto-dection을 통해 알아올 계획이므로 버스폭 정보를 option 플래그에서 제거한다.
  • 코드 라인 16~23에서 NAND 디바이스 리스트에서 매치되는 ID를 찾는데 다음 두 케이스로 나뉘어 진행한다.
    • NAND 디바이스 리스트 중 일부에 full ID를 제공하는 경우가 있으며 이 때에는 ID를 전부 비교하고 같은 경우 칩 인식이 완료되었으므로 해당 칩의 정보를 읽어온 후 ident_done 레이블로 이동한다.
  • 코드 라인 25~34에서 디바이스 리스트에서 디바이스 ID를 찾아오지 못하였거나 pagesize를 잧아오지 못한 경우에는 ONFI 또는 JEDEC 정보를 읽어서 확인한다. 확인된 경우 ident_done 레이블로 이동한다.
  • 코드 라인 36~37 칩 정보를 알아내지 못한 경우 에러 결과로 함수를 빠져나간다.
  • 코드 라인 42에서 type->chipsize를 메가 단위로 컨버팅하여 chip->chipsize에 담는다.
  • 코드 라인 44~47에서 이름은 결정되었지만 pagesize가 결정되지 않은 경우 제조사 정보를 통해 인식하도록 한다.
  • 코드 라인 50에서 칩의 options 정보에 디바이스가 제공하는 옵션 들을 추가한다.

 

drivers/mtd/nand/nand_base.c -3/3-

ident_done:

        if (chip->options & NAND_BUSWIDTH_AUTO) {
                WARN_ON(busw & NAND_BUSWIDTH_16);
                nand_set_defaults(chip);
        } else if (busw != (chip->options & NAND_BUSWIDTH_16)) {
                /*
                 * Check, if buswidth is correct. Hardware drivers should set
                 * chip correct!
                 */
                pr_info("device found, Manufacturer ID: 0x%02x, Chip ID: 0x%02x\n",
                        maf_id, dev_id);
                pr_info("%s %s\n", nand_manufacturer_name(manufacturer),
                        mtd->name);
                pr_warn("bus width %d instead of %d bits\n", busw ? 16 : 8,
                        (chip->options & NAND_BUSWIDTH_16) ? 16 : 8);
                return -EINVAL;
        }

        nand_decode_bbm_options(chip);

        /* Calculate the address shift from the page size */
        chip->page_shift = ffs(mtd->writesize) - 1;
        /* Convert chipsize to number of pages per chip -1 */
        chip->pagemask = (chip->chipsize >> chip->page_shift) - 1;

        chip->bbt_erase_shift = chip->phys_erase_shift =
                ffs(mtd->erasesize) - 1;
        if (chip->chipsize & 0xffffffff)
                chip->chip_shift = ffs((unsigned)chip->chipsize) - 1;
        else {
                chip->chip_shift = ffs((unsigned)(chip->chipsize >> 32));
                chip->chip_shift += 32 - 1;
        }

        chip->badblockbits = 8;
        chip->erase = single_erase;

        /* Do not replace user supplied command function! */
        if (mtd->writesize > 512 && chip->cmdfunc == nand_command)
                chip->cmdfunc = nand_command_lp;

        pr_info("device found, Manufacturer ID: 0x%02x, Chip ID: 0x%02x\n",
                maf_id, dev_id);

        if (chip->onfi_version)
                pr_info("%s %s\n", nand_manufacturer_name(manufacturer),
                        chip->onfi_params.model);
        else if (chip->jedec_version)
                pr_info("%s %s\n", nand_manufacturer_name(manufacturer),
                        chip->jedec_params.model);
        else
                pr_info("%s %s\n", nand_manufacturer_name(manufacturer),
                        type->name);

        pr_info("%d MiB, %s, erase size: %d KiB, page size: %d, OOB size: %d\n",
                (int)(chip->chipsize >> 20), nand_is_slc(chip) ? "SLC" : "MLC",
                mtd->erasesize >> 10, mtd->writesize, mtd->oobsize);
        return 0;
}
  • 코드 라인 1~18에서 칩 정보가 인식되어 ident_done: 레이블로 왔다. 버스 폭을 자동으로 요청한 경우 칩이 인식한 정보를 사용하게한다. 그리고 디폴트 설정 등을 준비한다. 만일 처음 옵션으로 요청 받은 버스 크기와 인식한 칩의 버스 크기가 다른 경우 에러를 출력하고 함수를 빠져나간다.
  • 코드 라인 20에서 배드 블럭 마커의 포지션 정보를 알아온다.
    • 페이지가 512를 초과하거나 16bit 버스를 사용하는 경우 offset=0, 그 외의 경우 offset=5
    • 참고로 OneNAND의 경우 offset=0
  • 코드 라인 23~25에서 writesize로 page_shift를 산출하고, chipsize와 page_shift로 pagemask도 산출한다.
    • 예) writesize=2K, chipsize=2G
      • page_shift=11
      • pagemask=0xfffff (1M-1)
  • 코드 라인 27~28에서 mtd->erasesize로 chip->phys_erase_shift 및 chip_>bbt_erase_shift를 산출한다.
    • 예) erasesize=256K
      • phys_erase_shift & bbt_erase_shift = 18
  • 코드 라인 29~34에서 chipsize로 chip_shift를 산출한다.
    • 예) chipsize=2G
      • chip_shift=31
  • 코드 라인 36~37에서 배드블럭 표기 비트에 8비트를 사용하고, erase 후크 함수에 standard erase 명령과 연결한다.
  • 코드 라인 40~41에서 wrtiesize가 512를 초과하는 Large Page이면서 nand_command() 함수를 호출하는 cmdfunc 후크인 경우 Large Page용 nand_command_lp() 함수로 변경한다.
  • 코드 라인 43~58에서 인식한 낸드 칩 정보를 출력한다.
    • 예) Micron 3rd Gen – 8Gb (x8)
    • nand: device found, Manufacturer ID: 0x2c, Chip ID: 0xd3
    • nand: Micron MT29F8G08ABACAWP
    • nand: 1024 MiB, SLC, erase size: 256 KiB, page size: 4096, OOB size: 224

 

nand_get_manufacturer()

drivers/mtd/nand/nand_ids.c

/**
 * nand_get_manufacturer - Get manufacturer information from the manufacturer
 *                         ID
 * @id: manufacturer ID
 *
 * Returns a pointer a nand_manufacturer object if the manufacturer is defined
 * in the NAND manufacturers database, NULL otherwise.
 */
const struct nand_manufacturer *nand_get_manufacturer(u8 id)
{
        int i;

        for (i = 0; i < ARRAY_SIZE(nand_manufacturers); i++)
                if (nand_manufacturers[i].id == id)
                        return &nand_manufacturers[i];

        return NULL;
}

아래 NAND 제조사 테이블에서 1 바이트로 만들어진 id로 검색하여 찾아온다.

 

drivers/mtd/nand/nand_ids.c

/* Manufacturer IDs */
static const struct nand_manufacturer nand_manufacturers[] = {
        {NAND_MFR_TOSHIBA, "Toshiba", &toshiba_nand_manuf_ops},
        {NAND_MFR_ESMT, "ESMT"},
        {NAND_MFR_SAMSUNG, "Samsung", &samsung_nand_manuf_ops},
        {NAND_MFR_FUJITSU, "Fujitsu"},
        {NAND_MFR_NATIONAL, "National"},
        {NAND_MFR_RENESAS, "Renesas"},
        {NAND_MFR_STMICRO, "ST Micro"},
        {NAND_MFR_HYNIX, "Hynix", &hynix_nand_manuf_ops},
        {NAND_MFR_MICRON, "Micron", &micron_nand_manuf_ops},
        {NAND_MFR_AMD, "AMD/Spansion", &amd_nand_manuf_ops},
        {NAND_MFR_MACRONIX, "Macronix", &macronix_nand_manuf_ops},
        {NAND_MFR_EON, "Eon"},
        {NAND_MFR_SANDISK, "SanDisk"},
        {NAND_MFR_INTEL, "Intel"},
        {NAND_MFR_ATO, "ATO"},
        {NAND_MFR_WINBOND, "Winbond"},
};

 

nand_flash_detect_onfi()

drivers/mtd/nand/nand_base.c -1/2-

/*
 * Check if the NAND chip is ONFI compliant, returns 1 if it is, 0 otherwise.
 */
static int nand_flash_detect_onfi(struct nand_chip *chip)
{
        struct mtd_info *mtd = nand_to_mtd(chip);
        struct nand_onfi_params *p = &chip->onfi_params;
        int i, j;
        int val;

        /* Try ONFI for unknown chip or LP */
        chip->cmdfunc(mtd, NAND_CMD_READID, 0x20, -1);
        if (chip->read_byte(mtd) != 'O' || chip->read_byte(mtd) != 'N' ||
                chip->read_byte(mtd) != 'F' || chip->read_byte(mtd) != 'I')
                return 0;

        chip->cmdfunc(mtd, NAND_CMD_PARAM, 0, -1);
        for (i = 0; i < 3; i++) {
                for (j = 0; j < sizeof(*p); j++)
                        ((uint8_t *)p)[j] = chip->read_byte(mtd);
                if (onfi_crc16(ONFI_CRC_BASE, (uint8_t *)p, 254) ==
                                le16_to_cpu(p->crc)) {
                        break;
                }
        }

        if (i == 3) {
                pr_err("Could not find valid ONFI parameter page; aborting\n");
                return 0;
        }

        /* Check version */
        val = le16_to_cpu(p->revision);
        if (val & (1 << 5))
                chip->onfi_version = 23;
        else if (val & (1 << 4))
                chip->onfi_version = 22;
        else if (val & (1 << 3))
                chip->onfi_version = 21;
        else if (val & (1 << 2))
                chip->onfi_version = 20;
        else if (val & (1 << 1))
                chip->onfi_version = 10;

        if (!chip->onfi_version) {
                pr_info("unsupported ONFI version: %d\n", val);
                return 0;
        }

        sanitize_string(p->manufacturer, sizeof(p->manufacturer));
        sanitize_string(p->model, sizeof(p->model));
        if (!mtd->name)
                mtd->name = p->model;

        mtd->writesize = le32_to_cpu(p->byte_per_page);

ID 정보를 읽어 ONFI 호환 NAND인 경우 ONFI 파라메터들을 읽어 칩 정보를 갱신한다.

  • 코드 라인 12~15에서 READID 명령을 전송 한 후 읽은 ID 값이 “ONFI”가 아닌 경우 결과를 0으로 함수를 빠져나간다.
  • 코드 라인 17~30에서 PARAM 명령을 전송하고 ONFI 파라메터들을 모두 읽어 nand_onfi_params 구조체를 채운다. 그 후 crc 값을 체크하여 잘못된 경우 최대 3회까지 재시도한다.
  • 코드 라인 33~48에서 onfi_version을 확인하여 10, 20~23이 아닌 경우 “unsupported ONFI version” 메시지를 출력하고 결과를 0으로 함수를 빠져나간다.
  • 코드 라인 50~51에서 manufacturer와 model 명에서 출력할 수 없는 부분을  ‘?’ 문자로 변경하고 trime 한다.
  • 코드 라인 52~53에서 mtd 명이 없는 경우 모델명을 사용한다.
  • 코드 라인 55에서 mtd의 wirtesize를 결정한다.

 

drivers/mtd/nand/nand_base.c -2/2-

        /*
         * pages_per_block and blocks_per_lun may not be a power-of-2 size
         * (don't ask me who thought of this...). MTD assumes that these
         * dimensions will be power-of-2, so just truncate the remaining area.
         */
        mtd->erasesize = 1 << (fls(le32_to_cpu(p->pages_per_block)) - 1);
        mtd->erasesize *= mtd->writesize;

        mtd->oobsize = le16_to_cpu(p->spare_bytes_per_page);

        /* See erasesize comment */
        chip->chipsize = 1 << (fls(le32_to_cpu(p->blocks_per_lun)) - 1);
        chip->chipsize *= (uint64_t)mtd->erasesize * p->lun_count;
        chip->bits_per_cell = p->bits_per_cell;

        chip->max_bb_per_die = le16_to_cpu(p->bb_per_lun);
        chip->blocks_per_die = le32_to_cpu(p->blocks_per_lun);

        if (onfi_feature(chip) & ONFI_FEATURE_16_BIT_BUS)
                chip->options |= NAND_BUSWIDTH_16;

        if (p->ecc_bits != 0xff) {
                chip->ecc_strength_ds = p->ecc_bits;
                chip->ecc_step_ds = 512;
        } else if (chip->onfi_version >= 21 &&
                (onfi_feature(chip) & ONFI_FEATURE_EXT_PARAM_PAGE)) {

                /*
                 * The nand_flash_detect_ext_param_page() uses the
                 * Change Read Column command which maybe not supported
                 * by the chip->cmdfunc. So try to update the chip->cmdfunc
                 * now. We do not replace user supplied command function.
                 */
                if (mtd->writesize > 512 && chip->cmdfunc == nand_command)
                        chip->cmdfunc = nand_command_lp;

                /* The Extended Parameter Page is supported since ONFI 2.1. */
                if (nand_flash_detect_ext_param_page(chip, p))
                        pr_warn("Failed to detect ONFI extended param page\n");
        } else {
                pr_warn("Could not retrieve ONFI ECC requirements\n");
        }

        return 1;
}
  • 코드 라인 6~7에서 ONFI 파라메터로 읽은 pages_per_block 크기가 2의 제곱승 단위가 아닐 때가 있으므로 이 값을 2의 제곱승 단위 내림 값으로 제한한다.
    • 예) pages_per_block=65 (0b1000001), writesize=0x800 (2K)
      • erasesize=0x40 * 0x800  = 0x2_0000 (128K)
  • 코드 라인 9에서 ONFI 파라메터에서 읽은 값으로 oobsize를 결정한다.
  • 코드 라인 12~13에서 ONFI 파라메터로 읽은 blocks_per_lun 크기가 2의 제곱승 단위가 아닐 때가 있으므로 이 값을 2의 제곱승 단위 내림 값으로 제한한다.
    • 예) blocks_per_lun=4097(0b10000_00000001), erasesize=0x2_0000
      • chipsize=0x1000 * 0x2_0000 = 0x2000_0000 (512M)
  • 코드 라인 14에서 셀당 저장 가능한 비트 수를 알아온다.
    • SLC=1, MLC=2, TLC=3, QLC=4
  • 코드 라인 16~17에서 die 당 최대 배드 블럭 수와 die 당 블럭 수를 알아온다.
  • 코드 라인 19~20에서 버스폭을 알아온다.
  • 코드 라인 22~24에서 ecc correction 가능한 bits를 알아올 수 있는 경우 step 사이즈를 512로 결정한다.
  • 코드 라인 25~42에서 ONIF 버전이 21 이상일 때 ONFI 확장 파라메터를 읽어들인다. 이 때 ecc_strength_ds 와 ecc_step_ds를 결정한다.

 

nand_flash_detect_jedec()

drivers/mtd/nand/nand_base.c

/*
 * Check if the NAND chip is JEDEC compliant, returns 1 if it is, 0 otherwise.
 */
static int nand_flash_detect_jedec(struct nand_chip *chip)
{
        struct mtd_info *mtd = nand_to_mtd(chip);
        struct nand_jedec_params *p = &chip->jedec_params;
        struct jedec_ecc_info *ecc;
        int val;
        int i, j;

        /* Try JEDEC for unknown chip or LP */
        chip->cmdfunc(mtd, NAND_CMD_READID, 0x40, -1);
        if (chip->read_byte(mtd) != 'J' || chip->read_byte(mtd) != 'E' ||
                chip->read_byte(mtd) != 'D' || chip->read_byte(mtd) != 'E' ||
                chip->read_byte(mtd) != 'C')
                return 0;

        chip->cmdfunc(mtd, NAND_CMD_PARAM, 0x40, -1);
        for (i = 0; i < 3; i++) {
                for (j = 0; j < sizeof(*p); j++)
                        ((uint8_t *)p)[j] = chip->read_byte(mtd);

                if (onfi_crc16(ONFI_CRC_BASE, (uint8_t *)p, 510) ==
                                le16_to_cpu(p->crc))
                        break;
        }

        if (i == 3) {
                pr_err("Could not find valid JEDEC parameter page; aborting\n");
                return 0;
        }

        /* Check version */
        val = le16_to_cpu(p->revision);
        if (val & (1 << 2))
                chip->jedec_version = 10;
        else if (val & (1 << 1))
                chip->jedec_version = 1; /* vendor specific version */

        if (!chip->jedec_version) {
                pr_info("unsupported JEDEC version: %d\n", val);
                return 0;
        }

        sanitize_string(p->manufacturer, sizeof(p->manufacturer));
        sanitize_string(p->model, sizeof(p->model));
        if (!mtd->name)
                mtd->name = p->model;

        mtd->writesize = le32_to_cpu(p->byte_per_page);

        /* Please reference to the comment for nand_flash_detect_onfi. */
        mtd->erasesize = 1 << (fls(le32_to_cpu(p->pages_per_block)) - 1);
        mtd->erasesize *= mtd->writesize;

        mtd->oobsize = le16_to_cpu(p->spare_bytes_per_page);

        /* Please reference to the comment for nand_flash_detect_onfi. */
        chip->chipsize = 1 << (fls(le32_to_cpu(p->blocks_per_lun)) - 1);
        chip->chipsize *= (uint64_t)mtd->erasesize * p->lun_count;
        chip->bits_per_cell = p->bits_per_cell;

        if (jedec_feature(chip) & JEDEC_FEATURE_16_BIT_BUS)
                chip->options |= NAND_BUSWIDTH_16;

        /* ECC info */
        ecc = &p->ecc_info[0];

        if (ecc->codeword_size >= 9) {
                chip->ecc_strength_ds = ecc->ecc_bits;
                chip->ecc_step_ds = 1 << ecc->codeword_size;
        } else {
                pr_warn("Invalid codeword size\n");
        }

        return 1;
}

ID 정보를 읽어 JEDEC 호환 NAND인 경우 JEDEC 파라메터들을 읽어 칩 정보를 갱신한다.

  • 코드 라인 13~17에서 READID 명령을 전송 한 후 읽은 ID 값이 “JEDEC”이 아닌 경우 결과를 0으로 함수를 빠져나간다.
  • 코드 라인 19~32에서 PARAM 명령을 전송하고 JEDEC 파라메터들을 모두 읽어 nand_jedec_params 구조체를 채운다. 그 후 crc 값을 체크하여 잘못된 경우 최대 3회까지 재시도한다.
  • 코드 라인 35~44에서 jedec_version을 확인하여 1 또는 10이 아닌 경우 “unsupported JEDEC version” 메시지를 출력하고 결과를 0으로 함수를 빠져나간다.
  • 코드 라인 46~47에서 manufacturer와 model 명에서 출력할 수 없는 부분을  ‘?’ 문자로 변경하고 trime 한다.
  • 코드 라인 48~49에서 mtd 명이 없는 경우 모델명을 사용한다.
  • 코드 라인 51에서 mtd의 wirtesize를 결정한다.
  • 코드 라인 54~55에서 JEDEC 파라메터로 읽은 pages_per_block 크기가 2의 제곱승 단위가 아닐 때가 있으므로 이 값을 2의 제곱승 단위 내림 값으로 제한한다.
    • 예) pages_per_block=65 (0b1000001), writesize=0x800 (2K)
      • erasesize=0x40 * 0x800  = 0x2_0000 (128K)
  • 코드 라인 57에서 JEDEC 파라메터에서 읽은 값으로 oobsize를 결정한다.
  • 코드 라인 60~61에서 JEDEC 파라메터로 읽은 blocks_per_lun 크기가 2의 제곱승 단위가 아닐 때가 있으므로 이 값을 2의 제곱승 단위 내림 값으로 제한한다.
    • 예) blocks_per_lun=4097(0b10000_00000001), erasesize=0x2_0000
      • chipsize=0x1000 * 0x2_0000 = 0x2000_0000 (512M)
  • 코드 라인 62에서 셀당 저장 가능한 비트 수를 알아온다.
    • SLC=1, MLC=2, TLC=3, QLC=4
  • 코드 라인 64~65에서 버스폭을 알아온다.
  • 코드 라인 68~75에서 ecc correction 가능한 bits 수 및 step 사이즈를 알아온다.

 

nand_manufacturer_detect()

drivers/mtd/nand/nand_base.c

/*
 * Manufacturer detection. Only used when the NAND is not ONFI or JEDEC
 * compliant and does not have a full-id or legacy-id entry in the nand_ids
 * table.
 */
static void nand_manufacturer_detect(struct nand_chip *chip)
{
        /*
         * Try manufacturer detection if available and use
         * nand_decode_ext_id() otherwise.
         */
        if (chip->manufacturer.desc && chip->manufacturer.desc->ops &&
            chip->manufacturer.desc->ops->detect) {
                /* The 3rd id byte holds MLC / multichip data */
                chip->bits_per_cell = nand_get_bits_per_cell(chip->id.data[2]);
                chip->manufacturer.desc->ops->detect(chip);
        } else {
                nand_decode_ext_id(chip);
        }
}

ONFI 또는 JEDEC 호환 NAND가 아닌 경우 제조사의 NAND 드라이버에서 구현한 방법으로 제품을 식별하도록 하다. 만일 제조사 드라이버에 detect가 구현되지 않은 경우 아래 nand_decode_ext_id() 함수를 통해 칩 정보를 결정한다.

 

nand_decode_ext_id()

drivers/mtd/nand/nand_base.c

/*
 * Many new NAND share similar device ID codes, which represent the size of the
 * chip. The rest of the parameters must be decoded according to generic or
 * manufacturer-specific "extended ID" decoding patterns.
 */
void nand_decode_ext_id(struct nand_chip *chip)
{
        struct mtd_info *mtd = nand_to_mtd(chip);
        int extid;
        u8 *id_data = chip->id.data;
        /* The 3rd id byte holds MLC / multichip data */
        chip->bits_per_cell = nand_get_bits_per_cell(id_data[2]);
        /* The 4th id byte is the important one */
        extid = id_data[3];

        /* Calc pagesize */
        mtd->writesize = 1024 << (extid & 0x03);
        extid >>= 2;
        /* Calc oobsize */
        mtd->oobsize = (8 << (extid & 0x01)) * (mtd->writesize >> 9);
        extid >>= 2;
        /* Calc blocksize. Blocksize is multiples of 64KiB */
        mtd->erasesize = (64 * 1024) << (extid & 0x03);
        extid >>= 2;
        /* Get buswidth information */
        if (extid & 0x1)
                chip->options |= NAND_BUSWIDTH_16;
}
EXPORT_SYMBOL_GPL(nand_decode_ext_id);

제조사의 NAND 드라이버에서 구현한 방법으로도 제품 식별이 안되는 경우는 id값의 3번째와 4번째 값으로 writesize, oobsize, erasesize, 버스 폭등을 알아낸다.

  • 코드 라인 12에서 3번째 id 값으로 셀당 비트 수를 결정한다.
  • 코드 라인 14~27에서 4번째 id 값의 각 비트들이 다음을 결정한다.
    • 페이지 사이즈(writesize)
      • extid의 bit0..1 값 x 1K
    • oobsize
      • extid의 bit2 값 * 8 * (writesize / 512)
    • erasesize
      • extid의 64K * bit4..5 값
    • 버스폭
      • extid의 bit6 값

 

nand_decode_id()

drivers/mtd/nand/nand_base.c

/*
 * Old devices have chip data hardcoded in the device ID table. nand_decode_id
 * decodes a matching ID table entry and assigns the MTD size parameters for
 * the chip.
 */
static void nand_decode_id(struct nand_chip *chip, struct nand_flash_dev *type)
{
        struct mtd_info *mtd = nand_to_mtd(chip);

        mtd->erasesize = type->erasesize;
        mtd->writesize = type->pagesize;
        mtd->oobsize = mtd->writesize / 32;

        /* All legacy ID NAND are small-page, SLC */
        chip->bits_per_cell = 1;
}

기존 오래된 디바이스들은 device ID 테이블에 하드 코드된 데이터가 있다. 이를 이용하여 erasesize, writesize, oobsize 및 셀당 비스 수를 알아온다.

 

NAND용 MTD Operations

 

  • (*_read)
    • nand_read()
  • (*_write)
    • nand_write()
  • (*_read_oob)
    • nand_read_oob()
  • (*_write_oob)
    • nand_write_oob()
  • (*_erase)
    • nand_erase()
  • (*_block_isbad)
    • nand_block_isbad()
  • (*_block_markbad)
    • nand_block_markbad()

 

다음 그림은 application이 마운트된 storage 디바이스에서 데이터를 읽거나 쓸 경우 DMA 동작과 연동하여 보여주고있다.

  • NAND 드라이버도 마운트되어 사용 시 아래와  동일하게 동작한다.

 

nand_read()

drivers/mtd/nand/nand_base.c

/**
 * 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 디바이스의 offset 위치부터 len 만큼 읽어 buf에 담아온다.

  • 코드 라인 7에서 디바이스가 FL_READY 상태이면 FL_READING 상태로 변경한다. 디바이스가 준비되지 않은 경우 대기한다.
  • 코드 라인 8~12에서 ops를 준비한 후 read 동작을 수행한다.
  • 코드 라인 14에서 디바이스의 사용이 완료되면 FL_READY 상태로 변경한다.

 

nand_do_read_ops()

drivers/mtd/nand/nand_base.c -1/3-

/**
 * 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);

        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;
  • 코드 라인 17~18에서 from offset 값의 크기를 보고 요청한 칩을 선택한다.
  • 코드 라인 20~21에서 realpage에 페이지 번호를 알아오고, page에는 해당 블럭에 대한 페이지 번호만을 대입한다.
    • 예) from=0x12_3456, page_shift=12 (4K 페이지), pagemask=0x3f (블럭당 64개 페이지)
      • realpage=0x123, page=0x23
  • 코드 라인 23에서 offset에 해당하는 페이지 내의 col offset을 알아온다.
    • 예) from=0x12_3456, writesize=0x1000 (4K)
      • col=0x456
  • 코드 라인 25~27에서 data용 버퍼와 oob용 버퍼를 준비한다.
  • 코드 라인 29~33에서 페이지 내에서 아직 읽지 못한 바이트 수와 읽어야 할 수 중 작은 수를 읽을 바이트로 선택한다. 읽을 바이트가 1 페이지 수와 같은 경우 aligned는 true가 된다.
  • 코드 라인 35~42에서 partial 페이지(서브 페이지)를 읽는 경우는 use_bufpoi=1이 되고 그렇지 않은 경우 0이 된다. 단 1 페이지 전체를 읽는 경우에도 bounce buffer를 사용하는 칩의 사용할 버퍼가 aligned되지 않은 경우에도 use_bufpoi=1이된다.

 

drivers/mtd/nand/nand_base.c -2/3-

.               /* 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);

                        /*
                         * 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,
                                                          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)) {
                                int toread = min(oobreadlen, max_oobsize);

                                if (toread) {
                                        oob = nand_transfer_oob(mtd,
                                                oob, ops, toread);
                                        oobreadlen -= toread;
                                }
                        }
  • 코드 라인 2~7에서 디바이스가 요청한 페이지가 현재 버퍼에 있는 경우가 아니거나 oob 버퍼가 지정된 경우 버퍼 위치를 칩이 준비해놓은 버퍼나 인자로 받은 ops의 버퍼를 사용한다.
  • 코드 라인 10~11에서 낸드 컨트롤러가 custom 페이지 액서서를 사용하지 않고 표준 페이지 액세서를 사용하는 경우에만 READ0 명령으로 cmdfunc()을  호출한다.
    • 컨트롤러가 자체 custom 기능으로 send 또는 read 요청을 하므로 프레임워크(코어)가 send 또는 read 요청을 또 할 필요 없다.
  • 코드 라인 17~34에서 낸드 드라이버를 통해 버퍼(bufpoi)에 다음 중 하나의 방법으로 읽어온다. ret에는 corrected bit 수가 담긴다.
    • ecc.read_page_raw()
      • raw 모드로 한 페이지를 읽어온다.
    • ecc.read_subpage()
      • 서브 페이지를 col 위치부터 bytes 만큼 읽어온다.
    • ecc.read_page()
      • 완전한 1 페이지를 읽어온다.
  • 코드 라인 37~48에서 드라이버에 미리 할당된 별도의 바운스 버퍼를 사용하는 경우 1 페이지의 데이터만 읽은 경우 pagebuf에 읽은 페이지 번호를 기록하고, pagebuf_bitflips에는 corrected bit 수를 대입한다. 그 외의 조건에서는 pagebuf에 -1을 담아두어 캐시 역활을 하지 못하게 한다. 마지막으로 버퍼에 바운스 버퍼 용도로 사용한 칩 버퍼를 복사한다.
  • 코드 라인 50~58에서 낮은 확률로 oob 버퍼가 준비되어 있는 경우 oob 데이터를 읽어 oob 버퍼에 위치한다. 반환되는 oob 주소는 처음 oob 버퍼 주소 + 읽은 oob 길이가된다.

 

drivers/mtd/nand/nand_base.c -3/3-

                        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) {
                                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);

        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~7에서 일부 남아있는 small-page 낸드 디바이스의 경우 읽기 동작에 지연이 필요하다. 이러한 디바이스에서는 NAND_NEED_READRDY 옵션을 사용하는데 드라이버에 (*dev_ready) 후크 함수가 구현되어 있는 경우 ready가 될 때 까지 기다린다. 구현되어 있지 않은 경우에는 chip_delay 만큼 딜레이한다.
  • 코드 라인 9~24에서 nand read 과정에서 failed 카운터가 증가된 경우 retry를 시도하고, 진행할 수 없는 경우 ecc_fail에 true 값을 대입한다.
  • 코드 라인 26~35에서 버퍼 포인터를 읽은 바이트 수 만큼 이동시킨다. max_bitflips에는 corrected bit 수를 추가한다.
  • 코드 라인 38~43에서 특정 nand에서는 많은 bit flips가 일어나는 경우 threashold를 변경하여 시도한다.
  • 코드 라인 45~46에서 다 읽어서 더 이상 읽을 데이터가 없는 경우 루프를 탈출한다.
  • 코드 라인 49~59에서 다음 페이지를 읽을 준비를 한다. 페이지가 칩 경계를 넘어가는 경우에는 다음 칩을 선택한다.
  • 코드 라인 61에서 칩 선택을 해제한다.
  • 코드 라인 63~65에서 읽은 길이를 retlen에 대입한다. 만일 oob 버퍼가 있는 경우 oob를 읽은 길이도 oobretlen에 대입한다.
  • 코드 라인 67~73에서 에러가 발생한 경우 에러 값을 리턴하고, ecc_fail인 경우에는 -EBADMSG를 반환한다. 정상적으로 데이터를 읽어낸 경우에는 bit flip한 비트 수를 반환한다.

 

operation 후크 함수 들 중 나머지 read/write 함수들의 동작은 거의 위의 read 함수와 유사하므로 코드 해석은 하지 않는다.

 

참고

 

  • White Paper: Linux® Storage System Analysis for e.MMC With Command Queuing | Micron – 다운로드 pdf

 

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다