안녕하세요? 문c 블로그의 문영일입니다.
며칠 후 2019년 3월에 접어들면 커널 v5.x-rc가 모두 merge 되어 v5.0이 탄생합니다.
현재 2019년 이후의 글들은 커널 v5.x 코드를 기준으로 올리고 있습니다.
앞으로도 ARM 및 ARM64 위주로 분석을 할 예정이고, 간간이 x86과 비교도 하고 있습니다.
제 글이 조금이나마 리눅스 커널 스터디에 도움되길 바랍니다.
문영일 드림.
안녕하세요? 문c 블로그의 문영일입니다.
며칠 후 2019년 3월에 접어들면 커널 v5.x-rc가 모두 merge 되어 v5.0이 탄생합니다.
현재 2019년 이후의 글들은 커널 v5.x 코드를 기준으로 올리고 있습니다.
앞으로도 ARM 및 ARM64 위주로 분석을 할 예정이고, 간간이 x86과 비교도 하고 있습니다.
제 글이 조금이나마 리눅스 커널 스터디에 도움되길 바랍니다.
문영일 드림.
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로 할당한다.
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); }
부트 페이지셋 테이블을 할당 받고 초기화한다.
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]); }
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 이상 값으로 설정한다.
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; }
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); }
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); }
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)); }
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 범위로 한정한다.
<kernel v4.14>
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"); }
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를 알아오도록 수행한다.
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; }
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를 위해 요청한 사이즈 만큼 메모리 할당을 한다.
<kernel v4.14>
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); }
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 전역 변수에 지정된다.
유저별 namespace 제한 값을 설정하는 기능은 커널 v4.9에 추가되었다.
<kernel v4.14>
커널 v4.16부터 NAND subsystem이 다시 정돈(refactoring)되고 있다. 어느 정도 자리 잡고나면 추후 커널 5.x에서 다시 한 번 개선된 기능을 소개할 예정이다.
nand 디바이스의 id 값을 읽어 제조사 정보와 페이지 사이즈(pagesize), 칩 사이즈(chipsize), 블록 사이즈(erasesize), oobsize, ECC step당 에러 교정 비트 수(strength), ECC 스텝 바이트 수(step) 등을 파악하기 위해 다음과 같이 동작한다.
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 디바이스를 스캔한다.
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);
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 관련 속성 값을 알아와서 지정한다.
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; } }
다음 그림은 버퍼 메모리를 생성하는 두 가지 방법을 보여준다.
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; }
다음 그림은 제조사 드라이버가 특별히 지정하지 않는 경우 모드에 따라 지정되는 디폴트 함수를 보여준다.
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; }
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를 스캔한다.
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;
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;
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; }
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", µn_nand_manuf_ops}, {NAND_MFR_AMD, "AMD/Spansion", &amd_nand_manuf_ops}, {NAND_MFR_MACRONIX, "Macronix", ¯onix_nand_manuf_ops}, {NAND_MFR_EON, "Eon"}, {NAND_MFR_SANDISK, "SanDisk"}, {NAND_MFR_INTEL, "Intel"}, {NAND_MFR_ATO, "ATO"}, {NAND_MFR_WINBOND, "Winbond"}, };
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 파라메터들을 읽어 칩 정보를 갱신한다.
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; }
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 파라메터들을 읽어 칩 정보를 갱신한다.
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() 함수를 통해 칩 정보를 결정한다.
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, 버스 폭등을 알아낸다.
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 및 셀당 비스 수를 알아온다.
다음 그림은 application이 마운트된 storage 디바이스에서 데이터를 읽거나 쓸 경우 DMA 동작과 연동하여 보여주고있다.
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에 담아온다.
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;
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; } }
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; }
operation 후크 함수 들 중 나머지 read/write 함수들의 동작은 거의 위의 read 함수와 유사하므로 코드 해석은 하지 않는다.