setup_per_cpu_pageset()

 

setup_per_cpu_pageset()

mm/page_alloc.c

/*
 * Allocate per cpu pagesets and initialize them.
 * Before this call only boot pagesets were available.
 */
void __init setup_per_cpu_pageset(void)
{
        struct pglist_data *pgdat;
        struct zone *zone;

        for_each_populated_zone(zone)
                setup_zone_pageset(zone);

        for_each_online_pgdat(pgdat)
                pgdat->per_cpu_nodestats =
                        alloc_percpu(struct per_cpu_nodestat);
}

Per-CPU Page Frame Cache를 각 zone별로 할당한다. 그리고 노드별로 통계를 per-cpu로 할당한다.

 

setup_pageset()

mm/page_alloc.c

/*
 * Boot pageset table. One per cpu which is going to be used for all
 * zones and all nodes. The parameters will be set in such a way
 * that an item put on a list will immediately be handed over to
 * the buddy list. This is safe since pageset manipulation is done
 * with interrupts disabled.
 *
 * The boot_pagesets must be kept even after bootup is complete for
 * unused processors and/or zones. They do play a role for bootstrapping
 * hotplugged processors.
 *
 * zoneinfo_show() and maybe other functions do
 * not check if the processor is online before following the pageset pointer.
 * Other parts of the kernel may not check if the zone is available.
 */
static void setup_pageset(struct per_cpu_pageset *p, unsigned long batch)
{
        pageset_init(p);
        pageset_set_batch(p, batch);
}

부트 페이지셋 테이블을 할당 받고 초기화한다.

 

pageset_init()

mm/page_alloc.c

static void pageset_init(struct per_cpu_pageset *p)
{
        struct per_cpu_pages *pcp;
        int migratetype;

        memset(p, 0, sizeof(*p));

        pcp = &p->pcp;
        pcp->count = 0;
        for (migratetype = 0; migratetype < MIGRATE_PCPTYPES; migratetype++)
                INIT_LIST_HEAD(&pcp->lists[migratetype]);
}
  • memset(p, 0, sizeof(*p));
    • 인수로 지정된 per_cpu_pageset  구조체 p를 0으로 clear 한다.
  • pcp->count = 0;
    • 페이지 셋의 페이지들 카운트를 0으로 초기화한다.
  • for (migratetype = 0; migratetype < MIGRATE_PCPTYPES; migratetype++) INIT_LIST_HEAD(&pcp->lists[migratetype]);
    • p->pcp->lists[] 배열 중 MIGRATE_PCPTYPES 까지 만큼만 리스트를 초기화한다.

 

pageset_set_batch()

mm/page_alloc.c

/* a companion to pageset_set_high() */
static void pageset_set_batch(struct per_cpu_pageset *p, unsigned long batch)
{
        pageset_update(&p->pcp, 6 * batch, max(1UL, 1 * batch));
}

pcp의 high 값으로 batch의 6배를 설정한다. 요청한 batch 값은 최소 1 이상 값으로 설정한다.

 

pageset_update()

mm/page_alloc.c

/*
 * pcp->high and pcp->batch values are related and dependent on one another:
 * ->batch must never be higher then ->high.
 * The following function updates them in a safe manner without read side
 * locking.
 *
 * Any new users of pcp->batch and pcp->high should ensure they can cope with
 * those fields changing asynchronously (acording the the above rule).
 *
 * mutex_is_locked(&pcp_batch_high_lock) required when calling this function
 * outside of boot time (or some other assurance that no concurrent updaters
 * exist).
 */
static void pageset_update(struct per_cpu_pages *pcp, unsigned long high,
                unsigned long batch)
{
       /* start with a fail safe value for batch */
        pcp->batch = 1;
        smp_wmb();

       /* Update high, then batch, in order */
        pcp->high = high;
        smp_wmb();

        pcp->batch = batch;
}
  • 지정된 순서 대로 batch를 1로 한 후 high와 batch를 대입한다.

 

setup_zone_pageset()

mm/page_alloc.c

static void __meminit setup_zone_pageset(struct zone *zone)
{
        int cpu;
        zone->pageset = alloc_percpu(struct per_cpu_pageset);
        for_each_possible_cpu(cpu)
                zone_pageset_init(zone, cpu);
}
  • zone->pageset = alloc_percpu(struct per_cpu_pageset);
    • 지정된 zone의 pageset에 percpu 형태로 메모리를 할당한다.
  • for_each_possible_cpu(cpu) zone_pageset_init(zone, cpu);
    • 모든 possible cpu에 대해 루프를 돌며 zone에 대한 페이지셋을 초기화한다.

 

zone_pageset_init()

mm/page_alloc.c

static void __meminit zone_pageset_init(struct zone *zone, int cpu)
{
        struct per_cpu_pageset *pcp = per_cpu_ptr(zone->pageset, cpu);

        pageset_init(pcp);
        pageset_set_high_and_batch(zone, pcp);
}
  • struct per_cpu_pageset *pcp = per_cpu_ptr(zone->pageset, cpu);
    • zone에 대한 pageset per-cpu 데이터를 가져온다.
  • pageset_init(pcp);
    • pageset을 초기화한다.

 

pageset_set_high_and_batch()

mm/page_alloc.c

static void pageset_set_high_and_batch(struct zone *zone,
                                       struct per_cpu_pageset *pcp)
{
        if (percpu_pagelist_fraction)
                pageset_set_high(pcp,
                        (zone->managed_pages /
                                percpu_pagelist_fraction));
        else
                pageset_set_batch(pcp, zone_batchsize(zone));
}

 

pageset_set_high()

mm/page_alloc.c

/*
 * pageset_set_high() sets the high water mark for hot per_cpu_pagelist
 * to the value high for the pageset p.
 */
static void pageset_set_high(struct per_cpu_pageset *p,
                                unsigned long high)
{
        unsigned long batch = max(1UL, high / 4);
        if ((high / 4) > (PAGE_SHIFT * 8))
                batch = PAGE_SHIFT * 8;

        pageset_update(&p->pcp, high, batch);
}

페이지셋에 high값과 batch 값을 갱신하는데 batch 값은 high /4 값으로 하되 1~96 범위로 한정한다.

  • unsigned long batch = max(1UL, high / 4);
    • batch에 high / 4 값을 대입하되 최소 1보다 커야 한다.
  • if ((high / 4) > (PAGE_SHIFT * 8)) batch = PAGE_SHIFT * 8;
    • high / 4 값이 PAGE_SHIFT(12) * 8 보다 큰 경우 batch 값을 PAGE_SHIFT(12) * 8로 제한한다.
  • pageset_update(&p->pcp, high, batch);
    • 페이지셋의 high, batch 값을 갱신한다.

 

참고

 

page_ext_init()

<kernel v4.14>

 

page_ext_init()

mm/page_ext.c

void __init page_ext_init(void)
{
        unsigned long pfn;
        int nid;

        if (!invoke_need_callbacks())
                return;

        for_each_node_state(nid, N_MEMORY) {
                unsigned long start_pfn, end_pfn;

                start_pfn = node_start_pfn(nid);
                end_pfn = node_end_pfn(nid);
                /*
                 * start_pfn and end_pfn may not be aligned to SECTION and the
                 * page->flags of out of node pages are not initialized.  So we
                 * scan [start_pfn, the biggest section's pfn < end_pfn) here.
                 */
                for (pfn = start_pfn; pfn < end_pfn;
                        pfn = ALIGN(pfn + 1, PAGES_PER_SECTION)) {

                        if (!pfn_valid(pfn))
                                continue;
                        /*
                         * Nodes's pfns can be overlapping.
                         * We know some arch can have a nodes layout such as
                         * -------------pfn-------------->
                         * N0 | N1 | N2 | N0 | N1 | N2|....
                         *
                         * Take into account DEFERRED_STRUCT_PAGE_INIT.
                         */
                        if (early_pfn_to_nid(pfn) != nid)
                                continue;
                        if (init_section_page_ext(pfn, nid))
                                goto oom;
                        cond_resched();
                }
        }
        hotplug_memory_notifier(page_ext_callback, 0);
        pr_info("allocated %ld bytes of page_ext\n", total_usage);
        invoke_init_callbacks();
        return;

oom:
        panic("Out of memory");
}
  • 코드 라인 6~7에서 page_ext 사용 요청이 없으면 함수를 빠져나간다.
  • 코드 라인 9~13에서 루프를 돌며 전체 노드를 처리하는데, 현재 노드에 해당하는 시작 pfn과 끝 pfn을 구해온다.
  • 코드 라인 19~23에서 시작 pfn부터 끝 pfn까지 섹션 단위로 초기화를 하려는데 해당 pfn의 메모리가 존재하지 않으면 skip한다.
  • 코드 라인 32~33에서 현재 처리할 노드에 해당하지 않는 페이지는 skip한다.
  • 코드 라인 34~35에서 현재 섹션에 대한 page_ext를 초기화한다.
  • 코드 라인 39에서 메모리 hotplug에 대한 변동이 있을 때마다 page_ext의 초기화 수행할 수 있게 notifier 콜백함수를 등록한다.
  • 코드 라인 41에서 page_ext_ops 엔트리 수 만큼 루프를 돌며 초기화 핸들러 함수를 호출한다.

 

early_pfn_to_nid()

mm/page_alloc.c

#if defined(CONFIG_HAVE_ARCH_EARLY_PFN_TO_NID) || \
        defined(CONFIG_HAVE_MEMBLOCK_NODE_MAP)

static struct mminit_pfnnid_cache early_pfnnid_cache __meminitdata;

int __meminit early_pfn_to_nid(unsigned long pfn)
{
        static DEFINE_SPINLOCK(early_pfn_lock);
        int nid;

        spin_lock(&early_pfn_lock);
        nid = __early_pfn_to_nid(pfn, &early_pfnnid_cache);
        if (nid < 0)
                nid = first_online_node;
        spin_unlock(&early_pfn_lock);

        return nid;
}
#endif

페이지 디스크립터들이 초기화되어 사용가능한 상태가 아니면 pfn_to_nid() 함수를 사용할 수 없다. 이렇게 페이지 디스크립터가 준비되기 전에 느리더라도 memblock을 스캔하여 pfn에 대한 노드 id를 알아오도록 수행한다.

 

init_section_page_ext()

mm/page_ext.c

static int __meminit init_section_page_ext(unsigned long pfn, int nid)
{
        struct mem_section *section;
        struct page_ext *base;
        unsigned long table_size;

        section = __pfn_to_section(pfn);

        if (section->page_ext)
                return 0;

        table_size = get_entry_size() * PAGES_PER_SECTION;
        base = alloc_page_ext(table_size, nid);

        /*
         * The value stored in section->page_ext is (base - pfn)
         * and it does not point to the memory block allocated above,
         * causing kmemleak false positives.
         */
        kmemleak_not_leak(base);

        if (!base) {
                pr_err("page ext allocation failure\n");
                return -ENOMEM;
        }

        /*
         * The passed "pfn" may not be aligned to SECTION.  For the calculation
         * we need to apply a mask.
         */
        pfn &= PAGE_SECTION_MASK;
        section->page_ext = (void *)base - get_entry_size() * pfn;
        total_usage += table_size;
        return 0;
}
  • 코드 라인 7~10에서 요청한 pfn에 대한 섹션이 이미 page_ext가 할당된 경우 skip한다.
  • 코드 라인 12~13에서 한 개 섹션에 해당하는 page_ext를 위한 메모리 할당을 한다.
  • 코드 라인 31~32에서 section->page_ext에는 할당된 page_ext 배열에서 섹션의 첫 pfn을 뺀 주소를 가리키게한다.
    • base – 섹션의 첫 pfn

 

alloc_page_ext()

mm/page_ext.c

static void *__meminit alloc_page_ext(size_t size, int nid)
{
        gfp_t flags = GFP_KERNEL | __GFP_ZERO | __GFP_NOWARN;
        void *addr = NULL;

        addr = alloc_pages_exact_nid(nid, size, flags);
        if (addr) {
                kmemleak_alloc(addr, size, 1, flags);
                return addr;
        }

        addr = vzalloc_node(size, nid);

        return addr;
}

page_ext를 위해 요청한 사이즈 만큼 메모리 할당을 한다.

 

참고

 

fork_init()

<kernel v4.14>

 

fork_init()

kernel/fork.c

void __init fork_init(void)
{
        int i;
#ifndef CONFIG_ARCH_TASK_STRUCT_ALLOCATOR
#ifndef ARCH_MIN_TASKALIGN
#define ARCH_MIN_TASKALIGN      0
#endif
        int align = max_t(int, L1_CACHE_BYTES, ARCH_MIN_TASKALIGN);

        /* create a slab on which task_structs can be allocated */
        task_struct_cachep = kmem_cache_create("task_struct",
                        arch_task_struct_size, align,
                        SLAB_PANIC|SLAB_NOTRACK|SLAB_ACCOUNT, NULL);
#endif

        /* do the arch specific task caches init */
        arch_task_cache_init();

        set_max_threads(MAX_THREADS);

        init_task.signal->rlim[RLIMIT_NPROC].rlim_cur = max_threads/2;
        init_task.signal->rlim[RLIMIT_NPROC].rlim_max = max_threads/2;
        init_task.signal->rlim[RLIMIT_SIGPENDING] =
                init_task.signal->rlim[RLIMIT_NPROC];

        for (i = 0; i < UCOUNT_COUNTS; i++) {
                init_user_ns.ucount_max[i] = max_threads/2;
        }

#ifdef CONFIG_VMAP_STACK
        cpuhp_setup_state(CPUHP_BP_PREPARE_DYN, "fork:vm_stack_cache",
                          NULL, free_vm_stack_cache);
#endif

        lockdep_init_task(&init_task);
}
  • 코드 라인 4에서 ia64 아키텍처에서는 다른 아키텍처처럼 slab을 사용한 kmem 캐시를 사용하지 않고 별도의 task_struct 구조체를 위한 할당자를 사용한다.
    • CONFIG_ARCH_TASK_STRUCT_ALLOCATOR 옵션은 현재 ia64 아키텍처만 사용한다.
  • 코드 라인 5~8에서 task_struct 구조체 할당 시 아키텍처가 지정한 사이즈만큼 align하여 할당하여 ww_mutex가 기잘 동작하기 위한 fixup이다. 만일 지정되지 않은 경우 0으로 하여 디폴트로 L1 캐시 라인 사이즈만큼 align 하도록한다.
  • 코드 라인 11~13에서 task_struct 구조체를 위해 디폴트 할당자인 슬랩을 사용한 kmem 캐시를 준비한다.
  • 코드 라인 17에서 아키텍처 고유의 task 캐시 초기화 함수가 있는 경우 호출한다.
    • arm, arm64는 사용하지 않는다.
  • 코드 라인 19에서 사용가능한 메모리 크기에 맞춰 최대 스레드 수를 산출하여 max_threads 전역 변수에 설정한다.
  • 코드 라인 21~22에서 최대 프로세스 수 제한에대한 cur, max 값으로 위에서 산출한 최대 스레드 수의 절반으로 지정한다.
  • 코드 라인 23~24에서 최대 펜딩 시그널 수로 위에서 설정한 최대 프로세스 수로 지정한다.
  • 코드 라인 26~28에서 ucount 항목들의 최대 제한 수를 모두 최대 스레드 수의 절반 값으로 지정한다.
  • 코드 라인 30~33에서 보안성 향상을 위해 특정 아키텍처들은 커널 스택을 vmalloc 영역에 가상 매핑하여 사용할 수 있다. cpu offline 시 해당 cpu에서 사용되고 있는 두 개의 캐시된 vm 스택 영역 정보를 삭제하는 역할을 수행하기 위해 호출될 콜백함수 free_vm_stack_cache()를 지정한다.
  • 코드 라인 35에서 디버그 lockdep을 위해 초기화를 한다.

 

set_max_threads()

kernel/fork.c

/*
 * set_max_threads
 */
static void set_max_threads(unsigned int max_threads_suggested)
{
        u64 threads;

        /*
         * The number of threads shall be limited such that the thread
         * structures may only consume a small part of the available memory.
         */
        if (fls64(totalram_pages) + fls64(PAGE_SIZE) > 64)
                threads = MAX_THREADS;
        else
                threads = div64_u64((u64) totalram_pages * (u64) PAGE_SIZE,
                                    (u64) THREAD_SIZE * 8UL);

        if (threads > max_threads_suggested)
                threads = max_threads_suggested;

        max_threads = clamp_t(u64, threads, MIN_THREADS, MAX_THREADS);
}

사용 가능한 최대 스레드 수를 설정한다. 내부 메모리를 판단하여 최소 20개 ~ 인자로 제안한 스레드 수 범위내에서 지정한다. 결정된 값은 max_threads 전역 변수에 지정된다.

  • 참고로 “/proc/sys/kernel/threads-max” 를 수정하여 설정할 수 있다.
  • 예) totalram_pages=0x8_0000 (2GB, 4K 페이지)
    • 2GB / 스레드 크기(arm64 디폴트=16K)의 8배 = 16K (16,384)

 

ucounts

유저별 namespace 제한 값을 설정하는 기능은 커널 v4.9에 추가되었다.

  • “/proc/ucounts” 파일을 통해 ucount 항목들의 cur값과 max값을 볼 수 있다.
    • user
      • 최대 user namespace 제한
    • pid
      • 최대 pid namespace 제한
    • uts
      • 최대 uts namespace 제한
    • ipc
      • 최대 ipc namespace 제한
    • net
      • 최대 net namespace 제한
    • mnt
      • 최대 mount namespace 제한
    • cgroup
      • 최대 cgroup namespace 제한
    • 기타
      •  inotify_instances
        • “/proc/sys/fs/inotify/max_user_instances”파일로 설정
      • inotify_watches
        • “/proc/sys/fs/inotify/max_user_watches”파일로 설정

 

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

 

MTD(Memory Technology Device) -2-

<kernel v4.14>

NAND 디바이스 트리

NAND 호스트 컨트롤러 관련 기본 속성들

  • compatible
    • 드라이버 compatiblity 문자열
    • 예) “brcm,brcmnand-v6.1”
  • reg
    • 첫 번째 기재된 주소는 NAND 레지스터 시작 물리 주소
    • 그외 옵션
      • DMA를 사용하는 경우 Flash DMA 레지스터 시작 물리 주소
      • NAND 캐시를 사용하는 경우 캐시 레지스터 시작 물리 주소
  • reg-names
    • 위의 주어진 레지스터를 설명하는 문자열
    • 예) “nand”, “flash-dma”, nand-cache”
  • interrupts
    • NAND 컨트롤러와 연결될 인터럽트 컨트롤러와 인터럽트 번호
  • interrupt-names
    • 위의 인터럽트를 설명하는 문자열
    • 예) “nand-ctlrdy”, “flash_dma_done” 등
  • interrupt-parent
    • 상위 인터럽트 컨트롤러
  • #address-cells
    • 칩 셀렉트 번호로 항상 1
  • #size-cells
    • 항상 0

 

NAND 칩 관련 서브 노드 속성들

  • compatible
    • “brcm,nandcs”
  • reg
    • 칩 셀렉트 번호로 0, 1, 2…
  • #address-calls
    • 하위 노드인 파티션 노드의 주소 표현에 필요한 4바이트 수
      • 1 = 32bit 주소(4 바이트)
      • 2 = 64bit 주소(8 바이트)
  • #size-cells
    • 하위 노드인 파티션 노드의 사이즈 표현에 필요한 4바이트 수
      • 1 = 32bit 주소(4 바이트)
      • 2 = 64bit 주소(8 바이트)
  • nand-on-flash-bbt
    • NAND에 BBT(Bad Block Table)를 지원하는지 여부
      • “true” or “false”
  • nand-ecc-mode
    • NAND ECC 모드 문자열로 다음과 같다.
      • “none”
      • “soft”
        • 부트로더 또는 커널 드라이버 software가 관리
      • “hw”
        • 호스트 컨트롤러가 관리
      • “hw_syndrome”
        • sunxi 사의 sun7i에 탑재된 nand 호스트 컨트롤러에서 관리
      • “hw_oob_first”
        • OOB를 먼저 읽어야 하는 경우 사용한다.
      • “on-die”
        • 퓨젼 NAND 칩에 내장된 ECC 컨트롤러가 관리
      • “soft_bch”
        • deprecate
  • nand-ecc-algo
    • NAND ECC 알고리즘 문자열로 다음과 같다.
      • “hamming”
      • “bch”
  • nand-bus-width
    • NAND 데이터 버스 사이즈
      • 8 = x8
      • 16 = x16
  • nand-ecc-strength
    • ECC 한 스텝당 correction 가능한 비트 수
    • 1, 5, 6, 8, 10, 12, 14, 16, …
  • nand-ecc-step-size
    • ECC 한 스텝 사이즈
      • 512 또는 1024
  • nand-ecc-maximize
    • ECC strength를 최대치로 사용할지의 여부
    • “true” or “false”

 

파티션 관련 서브 노드 속성들

  • compatible
    • “fixed-partions”만 지원한다.
  • reg
    • 파티션 시작 주소 및 사이즈

옵션 속성들

  • label
    • label 명
  • read-only
    • read-only로 마운트
  • lock
    • 초기화 시 unlock 불가능하게 하는 옵션

 

Broadcom

Case 1) 

nand@f0442800 {
        compatible = "brcm,brcmnand-v7.0", "brcm,brcmnand";
        reg = <0xF0442800 0x600>,
              <0xF0443000 0x100>;
        reg-names = "nand", "flash-dma";
        interrupt-parent = <&hif_intr2_intc>;
        interrupts = <24>, <4>;

        #address-cells = <1>;
        #size-cells = <0>;

        nandcs@1 {
                compatible = "brcm,nandcs";
                reg = <1>; // Chip select 1
                nand-on-flash-bbt;
                nand-ecc-strength = <12>;
                nand-ecc-step-size = <512>;

                // Partitions
                #address-cells = <1>;  // <2>, for 64-bit offset
                #size-cells = <1>;     // <2>, for 64-bit length
                flash0.rootfs@0 {
                        reg = <0 0x10000000>;
                };
                flash0@0 {
                        reg = <0 0>; // MTDPART_SIZ_FULL
                };
                flash0.kernel@10000000 {
                        reg = <0x10000000 0x400000>;
                };
        };
};
  • BBT가 지원되며, 512바이트 당 12bit의 ECC(BCH-12)

 

Case 2)
nand@10000200 {
        compatible = "brcm,nand-bcm63168", "brcm,nand-bcm6368",
                "brcm,brcmnand-v4.0", "brcm,brcmnand";
        reg = <0x10000200 0x180>,
              <0x10000600 0x200>,
              <0x100000b0 0x10>;
        reg-names = "nand", "nand-cache", "nand-int-base";
        interrupt-parent = <&periph_intc>;
        interrupts = <50>;
        clocks = <&periph_clk 20>;
        clock-names = "nand";

        #address-cells = <1>;
        #size-cells = <0>;

        nand0: nandcs@0 {
                compatible = "brcm,nandcs";
                reg = <0>;
                nand-on-flash-bbt;
                nand-ecc-strength = <1>;
                nand-ecc-step-size = <512>;
        };
};
  • brcm,nand-oob-sector-size
    • OOB 사이즈
      • 16, 32, 64, 128, 256, …
  • BBT가 지원되며, 512바이트 당 1bit의 ECC

 

 

파티션

부팅 시 표현되는 mtd 정보

다음 두 가지의 디바이스가 탑재된 사례이다.

  • Micron NAND 플래시: MT29F16G08ABABAWP 
  • Micron NOR 플래시: m25p80
nand: device found, Manufacturer ID: 0x2c, Chip ID: 0x48
nand: Micron MT29F16G08ABABAWP
nand: 2048 MiB, SLC, erase size: 512 KiB, page size: 4096, OOB size: 224
iproc_nand 66460000.nand: detected 2048MiB total, 512KiB blocks, 4KiB pages, 27B OOB, 8-bit, BCH-8
Scanning device for bad blocks
random: nonblocking pool is initialized
6 ofpart partitions found on MTD device brcmnand.0
Creating 6 MTD partitions on "brcmnand.0":
0x000000000000-0x000000700000 : "nboot"
0x000000700000-0x000000800000 : "nenv"
0x000000800000-0x000001000000 : "bootconf"
0x000001000000-0x000011000000 : "os1"
0x000011000000-0x000021000000 : "os2"
0x000021000000-0x00003f000000 : "ndata"

bcmspi_setup:628 cru_control 0x6010000
m25p80 spi1.0: n25q256a (32768 Kbytes)
4 ofpart partitions found on MTD device spi1.0
Creating 4 MTD partitions on "spi1.0":
0x000000000000-0x0000001d0000 : "bootloader"
0x0000001d0000-0x0000001f0000 : "env"
0x000000200000-0x000002000000 : "reserved"
0x000001000000-0x000002000000 : "data"

 

proc 인터페이스

다음과 같이 구성되어 있다.

  • NAND
    • mtd0 ~ mtd5
  • NOR
    • mtd6 ~ mtd9
# cat /proc/mtd
dev:    size   erasesize  name
mtd0: 00700000 00080000 "nboot"
mtd1: 00100000 00080000 "nenv"
mtd2: 00800000 00080000 "bootconf"
mtd3: 10000000 00080000 "os1"
mtd4: 10000000 00080000 "os2"
mtd5: 1e000000 00080000 "ndata"
mtd6: 001d0000 00001000 "bootloader"
mtd7: 00020000 00001000 "env"
mtd8: 01e00000 00001000 "reserved"
mtd9: 01000000 00001000 "data"

 

device 파일

아래와 같이 디바이스 파일들이 생성된다.

# ls /dev/mtd?
mtd0  mtd1  mtd2  mtd3  mtd4  mtd5  mtd6  mtd7  mtd8  mtd9

# ls /dev/mtd?ro
mtd0ro  mtd1ro  mtd2ro  mtd3ro  mtd4ro  mtd5ro  mtd6ro  mtd7ro  mtd8ro  mtd9ro

# ls /dev/mtdblock*
mtdblock0   mtdblock1   mtdblock10  mtdblock2   mtdblock3   mtdblock4   mtdblock5   mtdblock6   mtdblock7   mtdblock8   mtdblock9

 

mtd-utils

파티션 정보 확인
# mtdinfo
Count of MTD devices:           10
Present MTD devices:            mtd0, mtd1, mtd2, mtd3, mtd4, mtd5, mtd6, mtd7, mtd8, mtd9
Sysfs interface supported:      yes
  • -a 옵션을 사용하는 경우 전체 파티션에 대한 세부 정보를 볼 수 있다.

 

특정 파티션에 대한 세부 정보를 보려면 다음과 같이한다.

# mtdinfo /dev/mtd0
mtd0
Name:                           nboot
Type:                           nand
Eraseblock size:                262144 bytes, 256.0 KiB
Amount of eraseblocks:          28 (7340032 bytes, 7.0 MiB)
Minimum input/output unit size: 4096 bytes
Sub-page size:                  4096 bytes
OOB size:                       216 bytes
Character device major/minor:   90:0
Bad blocks are allowed:         true
Device is writable:             false

 

nanddump
# nanddump
Usage: nanddump [OPTIONS] MTD-device
Dumps the contents of a nand mtd partition.

           --help               Display this help and exit
           --version            Output version information and exit
           --bb=METHOD          Choose bad block handling method (see below).
-a         --forcebinary        Force printing of binary data to tty
-c         --canonicalprint     Print canonical Hex+ASCII dump
-f file    --file=file          Dump to file
-l length  --length=length      Length
-n         --noecc              Read without error correction
           --omitoob            Omit OOB data (default)
-o         --oob                Dump OOB data
-p         --prettyprint        Print nice (hexdump)
-q         --quiet              Don't display progress and status messages
-s addr    --startaddress=addr  Start address
-b         --checkbadblock      Check bad blocks from the start of device

--bb=METHOD, where METHOD can be `padbad', `dumpbad', or `skipbad':
    padbad:  dump flash data, substituting 0xFF for any bad blocks
    dumpbad: dump flash data, including any bad blocks
    skipbad: dump good data, completely skipping any bad blocks (default)
 

nanddump 유틸리티를 사용하여 0번 파티션을 파일로 Backup

  • -o 옵션을 사용하는 경우 oob까지 같이 dump
# nanddump /dev/mtd0 -f /tmp/bios.bak
ECC failed: 0
ECC corrected: 0
Number of bad blocks: 0
Number of bbt blocks: 0
Block size 262144, page size 4096, OOB size 216
Dumping data starting at 0x00000000 and ending at 0x00700000...

 

또는 다음과 같이 dd 명령으로도 백업을 받을 수 있다. 단 nanddump와 달리 배드 블럭이 있는 경우 포함되어 dump된다.

# dd if=/dev/mtd0ro of=bios.bak
16384+0 records in
16384+0 records out
8388608 bytes (8.4 MB) copied, 10.0269 s, 837 kB/s

 

Verify

0번 파티션과 피일간 비교

sha1sum /dev/mtd0ro bios.bak
    fdbb011920572ca6c991377c4b418a0502668b73  /dev/mtd0ro
    fdbb011920572ca6c991377c4b418a0502668b73  bios.bak

 

Erase

0번 파티션 erase

    # flash_erase /dev/mtd0 0 0
    Erasing 4 Kibyte @ 7ff000 -- 100 % complete

 

Write

0번 파티션에 파일을 기록

$ sudo nandwrite /dev/mtd0 nand.img
Writing data to block 0 at offset 0x0
Writing data to block 1 at offset 0x4000
Writing data to block 2 at offset 0x8000
Writing data to block 3 at offset 0xc000
...

 

또는 다음과 같이 dd 명령으로도 백업을 받을 수 있다. 단 nandwrite와 달리 배드 블럭을 무시하고 기록된다.

# dd if=MNW2MAX1.X64.0092.R01.1605221712.bin of=/dev/mtd0

 

/sys/class/mtd 사용

다음과 같이 mtd 파티션 중 nand 파티션 하나를 골라 출력을 하였다.

# ls /sys/class/mtd/mtd1
bad_blocks         dev                ecc_strength       numeraseregions    subpagesize        writesize
bbt_blocks         device             erasesize          offset             subsystem
bitflip_threshold  ecc_failures       flags              oobsize            type
corrected_bits     ecc_step_size      name               size               uevent

한 번의 Erase 에 사용되는 바이트 수 (= 1 블럭)

# cat erasesize
262144

 

한 번의 Write에 사용되는 바이트 수 (= 1 페이지)

# cat writesize
4096

 

이 파티션 내의 배드 블럭 수

# cat bad_blocks
0

 

이 파티션 내에 존재하는 bbt 블럭 수

# cat bbt_blocks
0

 

이 디바이스에서 읽기 동작 시 ECC 에러 발생 수 확인

# cat ecc_failures
0

 

이 디바이스에서 ECC correction한 비트 수

# cat corrected_bits
0

 

ECC 스텝이 사용할 바이트 수

# cat ecc_step_size
512

 

한 번의 ECC 스텝당 coreection 가능한 비트 수

# cat ecc_strength
8

 

한 번의 ECC 스텝에서 사용할 OOB 사이즈

# cat oobsize
216

 

그 외 offset 및 size에 시작 위치와 사이즈가 담긴다.

 

NAND 드라이버

Broadcom iproc-nand driver

drivers/mtd/nand/brcmnand/iproc_nand.c

static const struct of_device_id iproc_nand_of_match[] = {
        { .compatible = "brcm,nand-iproc" },
        {},
};
MODULE_DEVICE_TABLE(of, iproc_nand_of_match);

static struct platform_driver iproc_nand_driver = {
        .probe                  = iproc_nand_probe,
        .remove                 = brcmnand_remove,
        .driver = {
                .name           = "iproc_nand",
                .pm             = &brcmnand_pm_ops,
                .of_match_table = iproc_nand_of_match,
        }
};
module_platform_driver(iproc_nand_driver);

이 플랫폼 드라이버에서는 Device Tree의 “brcm,nand-iproc” compatible 명으로 등록된 nand 드라이버의 probe 함수인 iproc_nand_probe()을 호출한다.

 

iproc_nand_probe()

drivers/mtd/nand/brcmnand/iproc_nand.c

static int iproc_nand_probe(struct platform_device *pdev)
{
        struct device *dev = &pdev->dev;
        struct iproc_nand_soc *priv;
        struct brcmnand_soc *soc;
        struct resource *res;

        priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
        if (!priv)
                return -ENOMEM;
        soc = &priv->soc;

        spin_lock_init(&priv->idm_lock);

        res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "iproc-idm");
        priv->idm_base = devm_ioremap_resource(dev, res);
        if (IS_ERR(priv->idm_base))
                return PTR_ERR(priv->idm_base);

        res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "iproc-ext");
        priv->ext_base = devm_ioremap_resource(dev, res);
        if (IS_ERR(priv->ext_base))
                return PTR_ERR(priv->ext_base);

        soc->ctlrdy_ack = iproc_nand_intc_ack;
        soc->ctlrdy_set_enabled = iproc_nand_intc_set;
        soc->prepare_data_bus = iproc_nand_apb_access;

        return brcmnand_probe(pdev, soc);
}

iproc 낸드 드라이버를 probe한다.

  • 코드 라인 8~10에서 iproc 낸드 드라이버 정보를 담을 구조체를 할당받는다.
  • 코드 라인 15~18에서 플랫폼 리소스에 “iproc-idm” 리소스가 있는 경우 idm 레지스터들 물리 주소 범위를 가상 주소에 매핑한다.
    • 예) reg = <0x66460000 0x600>, <0x67015408 0x600>, <0x66460f00 0x20>;
    • reg-names = “nand”, “iproc-idm”, “iproc-ext”;
  • 코드 라인 20~23에서 플랫폼 리소스에 “iproc-ext” 리소스가 있는 경우 ext 레지스터들 물리 주소 범위를 가상 주소에 매핑한다.
  • 코드 라인 25~29에 낸드 인터럽트 처리 및 ARM APB 버스 접근관련 후크 함수를 준비하고 broadcom의 nand probe 함수를 호출한다.

 

아래 broadcom nand 드라이버 다음 버전들을 지원한다.

static const struct of_device_id brcmnand_of_match[] = {
        { .compatible = "brcm,brcmnand-v4.0" },
        { .compatible = "brcm,brcmnand-v5.0" },
        { .compatible = "brcm,brcmnand-v6.0" },
        { .compatible = "brcm,brcmnand-v6.1" },
        { .compatible = "brcm,brcmnand-v6.2" },
        { .compatible = "brcm,brcmnand-v7.0" },
        { .compatible = "brcm,brcmnand-v7.1" },
        { .compatible = "brcm,brcmnand-v7.2" },
        {},
};
MODULE_DEVICE_TABLE(of, brcmnand_of_match);

 

brcmnand_probe()

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

int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc)
{
        struct device *dev = &pdev->dev;
        struct device_node *dn = dev->of_node, *child;
        struct brcmnand_controller *ctrl;
        struct resource *res;
        int ret;

        /* We only support device-tree instantiation */
        if (!dn)
                return -ENODEV;

        if (!of_match_node(brcmnand_of_match, dn))
                return -ENODEV;

        ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
        if (!ctrl)
                return -ENOMEM;

        dev_set_drvdata(dev, ctrl);
        ctrl->dev = dev;

        init_completion(&ctrl->done);
        init_completion(&ctrl->dma_done);
        nand_hw_control_init(&ctrl->controller);
        INIT_LIST_HEAD(&ctrl->host_list);

        /* NAND register range */
        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        ctrl->nand_base = devm_ioremap_resource(dev, res);
        if (IS_ERR(ctrl->nand_base))
                return PTR_ERR(ctrl->nand_base);

        /* Enable clock before using NAND registers */
        ctrl->clk = devm_clk_get(dev, "nand");
        if (!IS_ERR(ctrl->clk)) {
                ret = clk_prepare_enable(ctrl->clk);
                if (ret)
                        return ret;
        } else {
                ret = PTR_ERR(ctrl->clk);
                if (ret == -EPROBE_DEFER)
                        return ret;

                ctrl->clk = NULL;
        }

        /* Initialize NAND revision */
        ret = brcmnand_revision_init(ctrl);
        if (ret)
                goto err;

        /*
         * Most chips have this cache at a fixed offset within 'nand' block.
         * Some must specify this region separately.
         */
        res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "nand-cache");
        if (res) {
                ctrl->nand_fc = devm_ioremap_resource(dev, res);
                if (IS_ERR(ctrl->nand_fc)) {
                        ret = PTR_ERR(ctrl->nand_fc);
                        goto err;
                }
        } else {
                ctrl->nand_fc = ctrl->nand_base +
                                ctrl->reg_offsets[BRCMNAND_FC_BASE];
        }

broadcom 낸드 드라이버를 probe한다.

  • 코드 라인 10~11에서 디바이스 트리 노드가 없는 경우 지원하지 않는다.
  • 코드 라인 13~14에서 매치되는 nand 디바이스 버전을 체크한다.
  • 코드 라인 16~18에서 broadcom 낸드 컨트롤러 구조체를 할당한다.
  • 코드 라인 29~32에서 NAND 레지스터 정보가 담긴 플랫폼 리소스를 얻어와서 가상 주소에 매핑한다.
  • 코드 라인 35~46에서 디바이스 트리에 “nand” 클럭 정보가 있는 경우 클럭을 준비하고 없으면 null 인채로 진행한다.
  • 코드 라인 49~51에서 broadcom 낸드 컨트롤러 버전에 맞는 정보를 알아온다.
    • reg_offsets
      • 레지스터 offset
    • reg_spacing
      • 칩셀렉트 stride
    • cs_offsets
      • 칩 셀렉트 offset
    • max_page_size
      • 컨트롤러가 처리할 수 있는 최대 페이지 사이즈
    • max_block_size
      • 컨트롤러가 처리할 수 있는 최대 블럭 사이즈
    • cs_offsets & cs0_offsets
      • 칩 셀렉트 레지스터 offset
    • max_oob
      • 컨트롤러가 처리할 수 있는 최대 OOB 사이즈
    • features
      • BRCMNAND_HAS_1K_SECTORS
        • ECC의 1K 섹터 지원 여부
        • v5.0 이상(v6.1은 제외)
      • BRCMNAND_HAS_PREFETCH
        • prefetch 지원 여부
        • v6.0 이상(v6.1은 제외)
      • BRCMNAND_HAS_CACHE_MODE
        • cache 지원 여부
        • v7.0 이상
      • BRCMNAND_HAS_WP
        • write protect 지원 여부
        • v7.0 이상 또는 “brcm,nand-has-wp” 속성이 있는 경우
  • 코드 라인 57~67에서 디바이스 트리에 “nand-cache”에 대한 레지스터 범위가 있는 경우 이를 가상 주소에 매핑한다.

 

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

.       /* FLASH_DMA */
        res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "flash-dma");
        if (res) {
                ctrl->flash_dma_base = devm_ioremap_resource(dev, res);
                if (IS_ERR(ctrl->flash_dma_base)) {
                        ret = PTR_ERR(ctrl->flash_dma_base);
                        goto err;
                }

                flash_dma_writel(ctrl, FLASH_DMA_MODE, 1); /* linked-list */
                flash_dma_writel(ctrl, FLASH_DMA_ERROR_STATUS, 0);

                /* Allocate descriptor(s) */
                ctrl->dma_desc = dmam_alloc_coherent(dev,
                                                     sizeof(*ctrl->dma_desc),
                                                     &ctrl->dma_pa, GFP_KERNEL);
                if (!ctrl->dma_desc) {
                        ret = -ENOMEM;
                        goto err;
                }

                ctrl->dma_irq = platform_get_irq(pdev, 1);
                if ((int)ctrl->dma_irq < 0) {
                        dev_err(dev, "missing FLASH_DMA IRQ\n");
                        ret = -ENODEV;
                        goto err;
                }

                ret = devm_request_irq(dev, ctrl->dma_irq,
                                brcmnand_dma_irq, 0, DRV_NAME,
                                ctrl);
                if (ret < 0) {
                        dev_err(dev, "can't allocate IRQ %d: error %d\n",
                                        ctrl->dma_irq, ret);
                        goto err;
                }

                dev_info(dev, "enabling FLASH_DMA\n");
        }

        /* Disable automatic device ID config, direct addressing */
        brcmnand_rmw_reg(ctrl, BRCMNAND_CS_SELECT,
                         CS_SELECT_AUTO_DEVICE_ID_CFG | 0xff, 0, 0);
        /* Disable XOR addressing */
        brcmnand_rmw_reg(ctrl, BRCMNAND_CS_XOR, 0xff, 0, 0);

        if (ctrl->features & BRCMNAND_HAS_WP) {
                /* Permanently disable write protection */
                if (wp_on == 2)
                        brcmnand_set_wp(ctrl, false);
        } else {
                wp_on = 0;
        }

        /* IRQ */
        ctrl->irq = platform_get_irq(pdev, 0);
        if ((int)ctrl->irq < 0) {
                dev_err(dev, "no IRQ defined\n");
                ret = -ENODEV;
                goto err;
        }
  • 코드 라인 2~39에서 디바이스 트리에서 “flash-dma” 속성이 있는 경우 dma를 준비한다. 이 때 dma에 사용할 인터럽트와 핸들러를 준비한다.
    • 디바이스 트리에서 인터럽트는 두 번째 항목에 있는 것을 사용한다.
  • 코드 라인 42~43에서 기본 설정으로 낸드 칩을 선택하지 않고 disable 상태로 둔다.
  • 코드 라인 45에서 XOR 어드레싱을 disable한다.
  • 코드 라인 47~53에서 write protect 기능이 있고 모듈 파라메터 “wp_on”의 값이 2인 경우 write protection 처리한다.
  • 코드 라인 56~61에서 NAND 컨트롤러에서 사용할 인터럽트를 준비한다.

 

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

        /*
         * Some SoCs integrate this controller (e.g., its interrupt bits) in
         * interesting ways
         */
        if (soc) {
                ctrl->soc = soc;

                ret = devm_request_irq(dev, ctrl->irq, brcmnand_irq, 0,
                                       DRV_NAME, ctrl);

                /* Enable interrupt */
                ctrl->soc->ctlrdy_ack(ctrl->soc);
                ctrl->soc->ctlrdy_set_enabled(ctrl->soc, true);
        } else {
                /* Use standard interrupt infrastructure */
                ret = devm_request_irq(dev, ctrl->irq, brcmnand_ctlrdy_irq, 0,
                                       DRV_NAME, ctrl);
        }
        if (ret < 0) {
                dev_err(dev, "can't allocate IRQ %d: error %d\n",
                        ctrl->irq, ret);
                goto err;
        }

        for_each_available_child_of_node(dn, child) {
                if (of_device_is_compatible(child, "brcm,nandcs")) {
                        struct brcmnand_host *host;

                        host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
                        if (!host) {
                                of_node_put(child);
                                ret = -ENOMEM;
                                goto err;
                        }
                        host->pdev = pdev;
                        host->ctrl = ctrl;

                        ret = brcmnand_init_cs(host, child);
                        if (ret) {
                                devm_kfree(dev, host);
                                continue; /* Try all chip-selects */
                        }

                        list_add_tail(&host->node, &ctrl->host_list);
                }
        }

        /* No chip-selects could initialize properly */
        if (list_empty(&ctrl->host_list)) {
                ret = -ENODEV;
                goto err;
        }

        return 0;

err:
        clk_disable_unprepare(ctrl->clk);
        return ret;

}
EXPORT_SYMBOL_GPL(brcmnand_probe);
  • 코드 라인 5~23에서 broadcom nand driver가 독립된 표준 모드로 동작하거나 iproc과 같이 인터럽트를 enable하기 위해 추가로 후크 함수들을 준비하는 soc 모드 등 두 가지 모드가 있다.
    • 표준 모드
      • brcmnand_ctlrdy_irq() 인터럽트 핸들러를 사용하도록 준비한다.
    • soc 모드
      • brcmnand_irq() 인터럽트 핸들러를 사용하도록 준비한다.
      • 인터럽트를 enable하기 위해 해당 soc에서 준비한 후크 함수들을 호출한다.
  • 코드 라인 25~46에서 nand 노드의 하위 노드에 “brcm,nandcs” compatible 명이 있는 경우 해당 칩셀렉트 번호에 해당하는 NAND 호스트 컨트롤러의 operation과 ecc에 대한 operation 후크 함수들을 준비하고 nand 컨트롤러를 설정한 후 mtd 디바이스로 등록한다.
  • 코드 라인 49~54에서 등록된 NAND 호스트 컨트롤러가 없는 경우 함수를 빠져나간다.

 

brcmnand_init_cs()

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

static int brcmnand_init_cs(struct brcmnand_host *host, struct device_node *dn)
{
        struct brcmnand_controller *ctrl = host->ctrl;
        struct platform_device *pdev = host->pdev;
        struct mtd_info *mtd;
        struct nand_chip *chip;
        int ret;
        u16 cfg_offs;

        ret = of_property_read_u32(dn, "reg", &host->cs);
        if (ret) {
                dev_err(&pdev->dev, "can't get chip-select\n");
                return -ENXIO;
        }

        mtd = nand_to_mtd(&host->chip);
        chip = &host->chip;

        nand_set_flash_node(chip, dn);
        nand_set_controller_data(chip, host);
        mtd->name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "brcmnand.%d",
                                   host->cs);
        mtd->owner = THIS_MODULE;
        mtd->dev.parent = &pdev->dev;

        chip->IO_ADDR_R = (void __iomem *)0xdeadbeef;
        chip->IO_ADDR_W = (void __iomem *)0xdeadbeef;

        chip->cmd_ctrl = brcmnand_cmd_ctrl;
        chip->cmdfunc = brcmnand_cmdfunc;
        chip->waitfunc = brcmnand_waitfunc;
        chip->read_byte = brcmnand_read_byte;
        chip->read_buf = brcmnand_read_buf;
        chip->write_buf = brcmnand_write_buf;

        chip->ecc.mode = NAND_ECC_HW;
        chip->ecc.read_page = brcmnand_read_page;
        chip->ecc.write_page = brcmnand_write_page;
        chip->ecc.read_page_raw = brcmnand_read_page_raw;
        chip->ecc.write_page_raw = brcmnand_write_page_raw;
        chip->ecc.write_oob_raw = brcmnand_write_oob_raw;
        chip->ecc.read_oob_raw = brcmnand_read_oob_raw;
        chip->ecc.read_oob = brcmnand_read_oob;
        chip->ecc.write_oob = brcmnand_write_oob;

        chip->controller = &ctrl->controller;
  • 코드 라인 10~14에서 nand 노드에서 reg 속성값을 읽어온다.
  • 코드 라인 16~46에서 nand chip 드라이버에서 사용할 멤버들을 초기화하고, 호출될 후크 함수들을 준비한다.

 

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

        /*
         * The bootloader might have configured 16bit mode but
         * NAND READID command only works in 8bit mode. We force
         * 8bit mode here to ensure that NAND READID commands works.
         */
        cfg_offs = brcmnand_cs_offset(ctrl, host->cs, BRCMNAND_CS_CFG);
        nand_writereg(ctrl, cfg_offs,
                      nand_readreg(ctrl, cfg_offs) & ~CFG_BUS_WIDTH);

        ret = nand_scan_ident(mtd, 1, NULL);
        if (ret)
                return ret;

        chip->options |= NAND_NO_SUBPAGE_WRITE;
        /*
         * Avoid (for instance) kmap()'d buffers from JFFS2, which we can't DMA
         * to/from, and have nand_base pass us a bounce buffer instead, as
         * needed.
         */
        chip->options |= NAND_USE_BOUNCE_BUFFER;

        if (chip->bbt_options & NAND_BBT_USE_FLASH)
                chip->bbt_options |= NAND_BBT_NO_OOB;

        if (brcmnand_setup_dev(host))
                return -ENXIO;

        chip->ecc.size = host->hwcfg.sector_size_1k ? 1024 : 512;
        /* only use our internal HW threshold */
        mtd->bitflip_threshold = 1;

        ret = brcmstb_choose_ecc_layout(host);
        if (ret)
                return ret;

        ret = nand_scan_tail(mtd);
        if (ret)
                return ret;

        return mtd_device_register(mtd, NULL, 0);
}
  • 코드 라인 6~8에서 부트로더에서 16bit 모드로 설정되어 있을 수도 있다. NAND READID 명령은 8bit 모드에서 동작해야 하므로 8비트 모드로 변경한다.
  • 코드 라인 10~12에서 nand 디바이스를 스캔하는 1st phase이다.
  • 코드 라인 14~26에서 broadcom nand 호스트 컨트롤러를 설정한다.
  • 코드 라인 28~34에서 ecc가 저장되는 oob layout을 결정한다.
  • 코드 라인 36~38에서 nand 디바이스를 스캔하는 2nd phase이다.
  • 코드 라인 40에서 mtd 디바이스로 등록한다.

 

다음 그림은 broadcom사의 nand_chip 구조체에 설정된 후크함수들을 보여준다.

 

참고