+ Brian and the MTD ML Hi George,
On 11 Jun 2016 18:30:04 -0400 "George Spelvin" <li...@sciencehorizons.net> wrote: > I was just browsing LKML history and wanted to understand this > concept, but while reading I think I spotted an error. > > > +static void nand_pairing_dist3_get_info(struct mtd_info *mtd, int page, > + struct mtd_pairing_info *info) > +{ > + int lastpage = (mtd->erasesize / mtd->writesize) - 1; > + int dist = 3; > + > + if (page == lastpage) > + dist = 2; > + > + if (!page || (page & 1)) { > + info->group = 0; > + info->pair = (page + 1) / 2; > + } else { > + info->group = 1; > + info->pair = (page + 1 - dist) / 2; > + } > +} > > As I understand this, the general rule is that odd pages i are paired with > even pages i+3, and group = !(i & 1). > > If there are N pages total (numbered 0..N-1), this leaves four exceptions > to deal with: > > -3 would be apired with 0 > -1 would be paired with 2 > N-3 would be paired with N > N-1 would be paired with N+2 > > This is solved by pairing 0 with 2, and N-1 with N-3. > > In terms of group addresses, there are only two exceptions: > * 0 is pair 0, group 0 (not pair -1, group 1) > * N-1 is pair N/2/-1, group 1 (not pair N/2, group 0) > > You have the first exception correct, but not the second; the condition > detects it, but the change to "dist" doesn't matter since the first half > of the second if() will be taken. Indeed. Nice catch! > > > I think the easiest way to get this right is the following: > > page = page + (page != 0) + (page == lastpage); > > info->group = page & 1; > if (page & 1) > page -= dist; > info->pair = page >> 1; > > Rather than make up too many rules, this just maps 0 -> -1 and N-1 -> N, > then applies the general rule. > > It also applies an offset of +1, to avoid negative numbers and the > problems of signed divides. It seems to cover all cases. > > > Also, a very minor note: divides are expensive. > A cheaper way to evaluate the "page == lastpage" condition is > > if ((page + 1) * mtd->writesize == mtd->erasesize) > > (or you could add an mtd->write_per_erase field). Okay. Actually I'd like to avoid adding new 'conversion' fields to the mtd_info struct. Not sure we are really improving perfs when doing that, since what takes long is the I/O ops between the flash and the controller not the conversion operations. > > > Applying all of this, the corrected code looks like the following: > > (Note the addition of "const" and "__pure" annotations, which should > get copied to the "struct mtd_pairing_scheme" declaration.) I didn't know about the __pure attribute, thanks for mentioning it. I agree on the const specifier. > > Signed-off-by: George Spelvin <li...@sciencehorizons.net> > > /* > * In distance-3 pairing, odd pages i are group 0, and are paired > * with even pages i+3. The exceptions are the first page (page 0) > * and last page (page N-1) in an erase group. These pair as if > * they were pages -1 and N, respectively. > */ > static void nand_pairing_dist3_get_info(const struct mtd_info *mtd, int page, > struct mtd_pairing_info *info) > { > page += (page != 0) + ((page + 1) * mtd->writesize == mtd->erasesize); Can we have a boolean to make it clearer? bool lastpage = ((page + 1) * mtd->writesize) == mtd->erasesize; Also, the page update is quite obscure for people that did not have the explanation you gave above. Can we make it /* * The first and last pages are not surrounded by other pages, * and are thus less sensitive to read/write disturbance. * That's why NAND vendors decided to use a different distance * for these 2 specific case, which complicates a bit the * pairing scheme logic. * * To simplify the code, and keep the same distance for * everyone, we'll increment all pages by 1 except the first * one (page 0). * The last page receives an extra +1 for the same reason. */ page += (page != 0) + lastpage; > /* * The datasheets state that odd pages should be part of group * 0 and even ones part of group 1 (except for the last and * first pages) but this has changed when we added + 1 to the * page variable. */ > info->group = page & 1; /* * We're trying to extract the pair id, which is always equal * to first_page_of_a_pair / 2. Subtract the distance to the * page if it's not part of group 0. */ > if (page & 1) > page -= 3; > info->pair = page >> 1; > } > > static int __pure nand_pairing_dist3_get_wunit(const struct mtd_info *mtd, > const struct mtd_pairing_info *info) > { I'll add the same kind of comment here. > int page = 2 * info->pair + 3 * info->group; > > page -= (page != 0) + (page * mtd->writesize > mtd->erasesize); > return page; > } > > const struct mtd_pairing_scheme dist3_pairing_scheme = { > .ngroups = 2, > .get_info = nand_pairing_dist3_get_info, > .get_wunit = nand_pairing_dist3_get_wunit, > }; > EXPORT_SYMBOL_GPL(dist3_pairing_scheme); > > /* > * Distance-6 pairing works like distance-3 pairing, except that pages > * are taken two at a time. The lsbit of the page number is chopped off > * and later re-added as the lsbit of the pair number. > */ > static void nand_pairing_dist6_get_info(const struct mtd_info *mtd, int page, > struct mtd_pairing_info *info) > { > bool lsbit = page & 1; > > page >>= 1; > page += (page != 0) + ((page+1) * 2*mtd->writesize == mtd->erasesize); > > info->group = page & 1; > if (page & 1) > page -= 3; > info->pair = page | lsbit; > } > > static int __pure nand_pairing_dist6_get_wunit(const struct mtd_info *mtd, > const struct mtd_pairing_info *info) > { > int page = (info->pair & ~1) + 3 * info->group; > > page -= (page != 0) + (page * 2 * mtd->writesize > mtd->erasesize); > return 2 * page + (info->pair & 1); > } > > const struct mtd_pairing_scheme dist6_pairing_scheme = { > .ngroups = 2, > .get_info = nand_pairing_dist6_get_info, > .get_wunit = nand_pairing_dist6_get_wunit, > }; > EXPORT_SYMBOL_GPL(dist6_pairing_scheme); > > Thanks for your valuable review/suggestions. Just out of curiosity, why are you interested in the pairing scheme concept? Are you working with NANDs? Best Regards, Boris -- Boris Brezillon, Free Electrons Embedded Linux and Kernel engineering http://free-electrons.com