All of lore.kernel.org
 help / color / mirror / Atom feed
From: Richard Weinberger <richard@nod.at>
To: linux-mtd@lists.infradead.org
Cc: david.oberhollenzer@sigma-star.at, Richard Weinberger <richard@nod.at>
Subject: [PATCH 8/8] mtd-utils: Add nand page test utility
Date: Tue, 26 Apr 2016 00:13:29 +0200	[thread overview]
Message-ID: <1461622409-14970-9-git-send-email-richard@nod.at> (raw)
In-Reply-To: <1461622409-14970-1-git-send-email-richard@nod.at>

From: David Oberhollenzer <david.oberhollenzer@sigma-star.at>

Basically a user space port of the mtd page test kernel module.
In addition to the module parameters, the utility supports using
only a sub-range of the flash erase blocks with a configurable stride.

Signed-off-by: David Oberhollenzer <david.oberhollenzer@sigma-star.at>
Signed-off-by: Richard Weinberger <richard@nod.at>
---
 .gitignore                |   1 +
 Makefile                  |   2 +-
 nand-utils/nandpagetest.c | 579 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 581 insertions(+), 1 deletion(-)
 create mode 100644 nand-utils/nandpagetest.c

diff --git a/.gitignore b/.gitignore
index 8e240d5..ff25458 100644
--- a/.gitignore
+++ b/.gitignore
@@ -45,6 +45,7 @@
 /nand-utils/nftl_format
 /nand-utils/nftldump
 /nand-utils/nandbiterrs
+/nand-utils/nandpagetest
 /misc-utils/recv_image
 /nor-utils/rfddump
 /nor-utils/rfdformat
diff --git a/Makefile b/Makefile
index add8864..76491ce 100644
--- a/Makefile
+++ b/Makefile
@@ -30,7 +30,7 @@ UBIFS_BINS = \
 JFFSX_BINS = \
 	mkfs.jffs2 sumtool jffs2reader jffs2dump
 NAND_BINS = \
-	nanddump nandwrite nandtest nftldump nftl_format nandbiterrs
+	nanddump nandwrite nandtest nftldump nftl_format nandbiterrs nandpagetest
 NOR_BINS = \
 	rfddump rfdformat
 
diff --git a/nand-utils/nandpagetest.c b/nand-utils/nandpagetest.c
new file mode 100644
index 0000000..11e2eef
--- /dev/null
+++ b/nand-utils/nandpagetest.c
@@ -0,0 +1,579 @@
+/*
+ * Copyright (C) 2006-2008 Nokia Corporation
+ * Copyright (C) 2016 sigma star gmbh
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; see the file COPYING. If not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Test page read and write on MTD device.
+ *
+ * Author: David Oberhollenzer <david.oberhollenzer@sigma-star.at>
+ *
+ * Based on linux pagetest.c
+ * Author: Adrian Hunter <ext-adrian.hunter@nokia.com>
+ */
+#define PROGRAM_NAME "nandpagetest"
+
+#include <mtd/mtd-user.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <libmtd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <time.h>
+
+#include "common.h"
+
+#define KEEP_CONTENTS 0x01
+#define SEED_SET 0x02
+
+static struct mtd_dev_info mtd;
+static const char *mtddev;
+static libmtd_t mtd_desc;
+
+static unsigned char *bbt=NULL, *writebuf=NULL, *backup=NULL;
+static unsigned char *twopages=NULL, *boundary=NULL;
+static int peb = -1, seed = -1, skip = -1, ebcnt = -1, flags = 0;
+static int fd, bufsize, pgsize, pgcnt;
+static unsigned int rnd_state;
+
+static void usage(int status)
+{
+	fputs(
+	"Usage: "PROGRAM_NAME" [OPTIONS] <device>\n\n"
+	"Options:\n"
+	"  -h, --help         Display this help output\n"
+	"  -p, --peb <num>    Index of the first erase block to use\n"
+	"  -c, --count <num>  Number of erase blocks to user (default all)\n"
+	"  -s, --skip <num>   Number of erase blocks to skip\n"
+	"  -S, --seed <num>   Seed for pseudor random number generator\n"
+	"  -k, --keep         Restore existing contents after test\n",
+	status==EXIT_SUCCESS ? stdout : stderr);
+	exit(status);
+}
+
+static long read_num(int idx, int argidx, int argc, char **argv)
+{
+	char *end;
+	long num;
+
+	if (argidx >= argc) {
+		fprintf(stderr, "%s: missing argument\n", argv[idx]);
+		exit(EXIT_FAILURE);
+	}
+
+	num = strtol(argv[argidx], &end, 0);
+
+	if (!end || *end!='\0') {
+		fprintf(stderr, "%s: expected integer argument\n", argv[idx]);
+		exit(EXIT_FAILURE);
+	}
+	return num;
+}
+
+static void process_options(int argc, char **argv)
+{
+	int i;
+
+	for (i=1; i<argc; ++i) {
+		if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h")) {
+			usage(EXIT_SUCCESS);
+		} else if (!strcmp(argv[i], "--peb") || !strcmp(argv[i], "-b")) {
+			if (peb >= 0)
+				goto failmulti;
+			peb = read_num(i, i+1, argc, argv);
+			if (peb < 0)
+				goto failarg;
+			++i;
+		} else if (!strcmp(argv[i], "--count") || !strcmp(argv[i], "-c")) {
+			if (ebcnt >= 0)
+				goto failmulti;
+			ebcnt = read_num(i, i+1, argc, argv);
+			if (ebcnt < 0)
+				goto failarg;
+			++i;
+		} else if (!strcmp(argv[i], "--skip") || !strcmp(argv[i], "-s")) {
+			if (skip >= 0)
+				goto failmulti;
+			skip = read_num(i, i+1, argc, argv);
+			if (skip < 0)
+				goto failarg;
+			++i;
+		} else if (!strcmp(argv[i], "--seed") || !strcmp(argv[i], "-S")) {
+			if (flags & SEED_SET)
+				goto failmulti;
+			seed = read_num(i, i+1, argc, argv);
+			flags |= SEED_SET;
+			++i;
+		} else if (!strcmp(argv[i], "--keep") || !strcmp(argv[i], "-k")) {
+			if (flags & KEEP_CONTENTS)
+				goto failmulti;
+			flags |= KEEP_CONTENTS;
+		} else {
+			if (mtddev)
+				usage(EXIT_FAILURE);
+			mtddev = argv[i];
+		}
+	}
+
+	if (!mtddev)
+		errmsg_die("No device specified!");
+
+	if (peb < 0)
+		peb = 0;
+	if (!(flags & SEED_SET))
+		seed = time(NULL);
+	if (skip < 0)
+		skip = 0;
+	return;
+failmulti:
+	errmsg_die("'%s' specified more than once!", argv[i]);
+failarg:
+	errmsg_die("Invalid argument for '%s'!", argv[i]);
+}
+
+static int write_eraseblock(int ebnum)
+{
+	int i;
+
+	for (i = 0; i < mtd.eb_size; ++i)
+		writebuf[i] = rand_r(&rnd_state);
+
+	return mtd_write(mtd_desc, &mtd, fd, ebnum, 0,
+				writebuf, mtd.eb_size, NULL, 0, 0);
+}
+
+static void get_first_and_last_block(int *first, int *last)
+{
+	int i;
+
+	*first = peb;
+	for (i = 0; i < ebcnt && bbt[i]; ++i)
+		*first += skip + 1;
+
+	*last = peb + (ebcnt - 1) * (skip + 1);
+	for (i = 0; i < ebcnt && bbt[ebcnt - i - 1]; ++i)
+		*last -= skip + 1;
+}
+
+/* Do a read to set the internal dataRAMs to different data */
+static int flush_data_rams(int eb0, int ebn)
+{
+	int err;
+	err = mtd_read(&mtd, fd, eb0, 0, twopages, bufsize);
+	if (err)
+		return err;
+	err = mtd_read(&mtd, fd, ebn, mtd.eb_size-bufsize,
+					twopages, bufsize);
+	if (err)
+		return err;
+	memset(twopages, 0, bufsize);
+	return 0;
+}
+
+static int verify_eraseblock(int ebnum)
+{
+	int err = 0, i, ret = 0, eb0, ebn, rd, diff;
+	loff_t  offset = 0, addr, addrn;
+	unsigned int j, old_state;
+
+	for (i = 0; i < mtd.eb_size; ++i)
+		writebuf[i] = rand_r(&rnd_state);
+
+	get_first_and_last_block(&eb0, &ebn);
+
+	for (j = 0; j < pgcnt - 1; ++j, offset += pgsize) {
+		err = flush_data_rams(eb0, ebn);
+		if (err)
+			return err;
+		err = mtd_read(&mtd, fd, ebnum, offset, twopages, bufsize);
+		if (err)
+			break;
+		if (memcmp(twopages, writebuf + (j * pgsize), bufsize)) {
+			fprintf(stderr, "error: verify failed at block %d, page %ld\n",
+					ebnum, ((long)offset) / pgsize );
+			ret = -1;
+		}
+	}
+	/* Check boundary between eraseblocks */
+	addr = (loff_t)ebnum*mtd.eb_size + offset;
+	addrn = (loff_t)ebn*mtd.eb_size + mtd.eb_size - 2*pgsize;
+
+	if (addr <= addrn && !mtd_is_bad(&mtd, fd, ebnum+1)) {
+		old_state = rnd_state;
+		err = flush_data_rams(eb0, ebn);
+		if (err)
+			return err;
+
+		if (lseek(fd, addr, SEEK_SET) != addr) {
+			fprintf(stderr, "cannot seek mtd%d to offset %"PRIdoff_t,
+	 				mtd.mtd_num, addr);
+			return -1;
+		}
+
+		for (rd = 0; rd < bufsize; rd += diff) {
+			diff = read(fd, twopages + rd, bufsize - rd);
+			if (diff < 0) {
+				fprintf(stderr, "cannot read %d bytes from mtd%d "
+								"(eraseblock %d, offset %d)",
+								bufsize-rd, mtd.mtd_num, ebnum,
+								(int)offset+rd);
+				return -1;
+		  	}
+		}
+
+		memcpy(boundary, writebuf + mtd.eb_size - pgsize, pgsize);
+
+		for (j = 0; j < pgsize; ++j)
+			(boundary + pgsize)[j] = rand_r(&rnd_state);
+
+		if (memcmp(twopages, boundary, bufsize)) {
+			fprintf(stderr, "error: verify failed at block %d, page %ld\n",
+					ebnum, ((long)offset) / pgsize );
+			ret = -1;
+		}
+		rnd_state = old_state;
+	}
+	return ret;
+}
+
+static int crosstest(void)
+{
+	unsigned char *pp1, *pp2, *pp3, *pp4;
+	int eb0, ebn, err = 0, offset;
+
+	puts("crosstest");
+	pp1 = xzalloc(pgsize * 4);
+	if (!pp1)
+		return -ENOMEM;
+	pp2 = pp1 + pgsize;
+	pp3 = pp2 + pgsize;
+	pp4 = pp3 + pgsize;
+
+	get_first_and_last_block(&eb0, &ebn);
+
+	/* Read 2nd-to-last page to pp1 */
+	err = mtd_read(&mtd, fd, ebn, mtd.eb_size - 2*pgsize, pp1, pgsize);
+	if (err)
+		goto out;
+
+	/* Read 3rd-to-last page to pp1 */
+	err = mtd_read(&mtd, fd, ebn, mtd.eb_size - 3*pgsize, pp1, pgsize);
+	if (err)
+		goto out;
+
+	/* Read first page to pp2 */
+	printf("reading page at block %d, page %d\n", eb0, 0);
+	err = mtd_read(&mtd, fd, eb0, 0, pp2, pgsize);
+	if (err)
+		goto out;
+
+	/* Read last page to pp3 */
+	offset = mtd.eb_size - pgsize;
+	printf("reading page at block %d, page %d\n", ebn, offset/pgsize);
+	err = mtd_read(&mtd, fd, ebn, offset, pp3, pgsize);
+	if (err)
+		goto out;
+
+	/* Read first page again to pp4 */
+	printf("reading page at block %d, page %d\n", eb0, 0);
+	err = mtd_read(&mtd, fd, eb0, 0, pp4, pgsize);
+	if (err)
+		goto out;
+
+	/* pp2 and pp4 should be the same */
+	printf("verifying pages read at block %d match\n", eb0);
+	if (memcmp(pp2, pp4, pgsize)) {
+		fputs("verify failed!\n", stderr);
+		err = -1;
+	} else {
+		puts("crosstest ok");
+	}
+out:
+	free(pp1);
+	return err;
+}
+
+static int erasecrosstest(void)
+{
+	unsigned char *readbuf = twopages;
+	int err = 0, i, eb0, ebn;
+
+	puts("erasecrosstest");
+
+	get_first_and_last_block(&eb0, &ebn);
+
+	printf("erasing block %d\n", eb0);
+	err = mtd_erase(mtd_desc, &mtd, fd, eb0);
+	if (err)
+		return err;
+
+	printf("writing 1st page of block %d\n", eb0);
+	for (i = 0; i < pgsize; ++i)
+		writebuf[i] = rand_r(&rnd_state);
+	strcpy((char*)writebuf, "There is no data like this!");
+	err = mtd_write(mtd_desc, &mtd, fd, eb0, 0, writebuf, pgsize, NULL, 0, 0);
+	if (err)
+		return err;
+
+	printf("reading 1st page of block %d\n", eb0);
+	memset(readbuf, 0, pgsize);
+	err = mtd_read(&mtd, fd, eb0, 0, readbuf, pgsize);
+	if (err)
+		return err;
+
+	printf("verifying 1st page of block %d\n", eb0);
+	if (memcmp(writebuf, readbuf, pgsize)) {
+		fputs("verify failed!\n", stderr);
+		return -1;
+	}
+
+	printf("erasing block %d\n", eb0);
+	err = mtd_erase(mtd_desc, &mtd, fd, eb0);
+	if (err)
+		return err;
+
+	printf("writing 1st page of block %d\n", eb0);
+	for (i = 0; i < pgsize; ++i)
+		writebuf[i] = rand_r(&rnd_state);
+	strcpy((char*)writebuf, "There is no data like this!");
+	err = mtd_write(mtd_desc, &mtd, fd, eb0, 0, writebuf, pgsize, NULL, 0, 0);
+	if (err)
+		return err;
+
+	printf("erasing block %d\n", ebn);
+	err = mtd_erase(mtd_desc, &mtd, fd, ebn);
+	if (err)
+		return err;
+
+	printf("reading 1st page of block %d\n", eb0);
+	memset(readbuf, 0, pgsize);
+	err = mtd_read(&mtd, fd, eb0, 0, readbuf, pgsize);
+	if (err)
+		return err;
+
+	printf("verifying 1st page of block %d\n", eb0);
+	if (memcmp(writebuf, readbuf, pgsize)) {
+		fputs("verify failed!\n", stderr);
+		return -1;
+	}
+
+	puts("erasecrosstest ok");
+	return 0;
+}
+
+static int erasetest(void)
+{
+	int err = 0, i, ebnum, ebn;
+
+	puts("erasetest");
+	get_first_and_last_block(&ebnum, &ebn);
+
+	printf("erasing block %d\n", ebnum);
+	err = mtd_erase(mtd_desc, &mtd, fd, ebnum);
+	if (err)
+		return err;
+
+	printf("writing 1st page of block %d\n", ebnum);
+	for (i = 0; i < pgsize; ++i)
+		writebuf[i] = rand_r(&rnd_state);
+	err = mtd_write(mtd_desc, &mtd, fd, ebnum, 0,
+					writebuf, pgsize, NULL, 0, 0);
+	if (err)
+		return err;
+
+	printf("erasing block %d\n", ebnum);
+	err = mtd_erase(mtd_desc, &mtd, fd, ebnum);
+	if (err)
+		return err;
+
+	printf("reading 1st page of block %d\n", ebnum);
+	err = mtd_read(&mtd, fd, ebnum, 0, twopages, pgsize);
+	if (err)
+		return err;
+
+	printf("verifying 1st page of block %d is all 0xff\n", ebnum);
+	for (i = 0; i < pgsize; ++i) {
+		if (twopages[i] != 0xff) {
+			fprintf(stderr, "verifying all 0xff failed at %d\n", i);
+			return -1;
+		}
+	}
+
+	puts("erasetest ok");
+	return 0;
+}
+
+int main(int argc, char **argv)
+{
+	int i, eb, err = 0, status = EXIT_FAILURE;
+	unsigned char *backupptr;
+
+	process_options(argc, argv);
+
+	mtd_desc = libmtd_open();
+	if (!mtd_desc)
+		return errmsg("can't initialize libmtd");
+
+	if (mtd_get_dev_info(mtd_desc, mtddev, &mtd) < 0)
+		return errmsg("mtd_get_dev_info failed");
+
+	if (mtd.type!=MTD_MLCNANDFLASH && mtd.type!=MTD_NANDFLASH)
+		return errmsg("%s is not a NAND flash!", mtddev);
+
+	pgsize = mtd.min_io_size;
+	pgcnt = mtd.eb_size / pgsize;
+	bufsize = pgsize * 2;
+
+	if (ebcnt < 0)
+		ebcnt = (mtd.eb_cnt - peb) / (skip + 1);
+
+	if (peb >= mtd.eb_cnt)
+		return errmsg("physical erase block %d is out of range!", peb);
+
+	eb = peb + (ebcnt - 1)*(skip + 1);
+
+	if (eb >= mtd.eb_cnt)
+		return errmsg("last physical erase block %d is out of range!", eb);
+
+	writebuf = xmalloc(mtd.eb_size);
+	twopages = xmalloc(bufsize);
+	boundary = xmalloc(bufsize);
+	bbt = xzalloc(ebcnt);
+
+	if ((fd = open(mtddev, O_RDWR)) == -1) {
+		perror(mtddev);
+		goto out_cleanup;
+	}
+
+	/* find bad blocks */
+	for (i = 0; i < ebcnt; ++i) {
+		eb = peb + i*(skip+1);
+		bbt[i] = mtd_is_bad(&mtd, fd, eb);
+
+		if (bbt[i])
+			printf("ignoring bad erase block %d\n", eb);
+	}
+
+	/* create block backup */
+	if (flags & KEEP_CONTENTS) {
+		eb = 0;
+		for (i = 0; i < ebcnt; ++i) {
+			if (!bbt[i])
+				++eb;
+		}
+		backup = malloc(mtd.eb_size * eb);
+		if (!backup) {
+			fprintf(stderr, "not enough memory to keep block contents!\n");
+			goto out_cleanup;
+		}
+		printf("reading %d blocks int memory\n", eb);
+		backupptr = backup;
+		for (i = 0; i < ebcnt; ++i) {
+			if (bbt[i])
+				continue;
+			eb = peb + i*(skip+1);
+			err = mtd_read(&mtd, fd, eb, 0, backupptr, mtd.eb_size);
+			if (err) {
+				fprintf(stderr, "error reading block %d!\n", eb);
+				goto out_cleanup;
+			}
+			backupptr += mtd.eb_size;
+		}
+	}
+
+	/* Erase all eraseblocks */
+	puts("erasing all blocks");
+	for (i = 0; i < ebcnt; ++i) {
+		if (bbt[i])
+			continue;
+		eb = peb + i*(skip+1);
+		if (mtd_erase(mtd_desc, &mtd, fd, eb)) {
+			fprintf(stderr, "error erasing block %d\n", eb);
+			goto out;
+		}
+	}
+	printf("erased %u eraseblocks\n", ebcnt);
+
+	/* Write all eraseblocks */
+	rnd_state = seed;
+	puts("writing all blocks");
+	for (i = 0; i < ebcnt; ++i) {
+		if (bbt[i])
+			continue;
+		eb = peb + i*(skip+1);
+		err = write_eraseblock(eb);
+		if (err)
+			goto out;
+		if (i % 256 == 0)
+			printf("written up to eraseblock %u\n", i);
+	}
+	printf("written %u eraseblocks\n", i);
+
+	/* Check all eraseblocks */
+	rnd_state = seed;
+	puts("verifying all eraseblocks");
+	for (i = 0; i < ebcnt; ++i) {
+		eb = peb + i*(skip+1);
+		if (bbt[i])
+			continue;
+		err = verify_eraseblock(eb);
+		if (err)
+			goto out;
+		if (i % 256 == 0)
+			printf("verified up to eraseblock %u\n", i);
+	}
+	printf("verified %u eraseblocks\n", i);
+
+	if (crosstest())
+		goto out;
+
+	if (erasecrosstest())
+		goto out;
+
+	if (erasetest())
+		goto out;
+
+	status = EXIT_SUCCESS;
+out:
+	/* restore block backup */
+	if (flags & KEEP_CONTENTS) {
+		puts("restoring original contents");
+		backupptr = backup;
+		for (i = 0; i < ebcnt; ++i) {
+			if (bbt[i])
+				continue;
+			eb = peb + i*(skip+1);
+			if (mtd_erase(mtd_desc, &mtd, fd, eb)) {
+				fprintf(stderr, "error erasing block %d!\n", eb);
+				status = EXIT_FAILURE;
+			}
+			err = mtd_write(mtd_desc, &mtd, fd, eb, 0,
+							backupptr, mtd.eb_size, NULL, 0, 0);
+			if (err) {
+				fprintf(stderr, "error restoring block %d!\n", eb);
+				status = EXIT_FAILURE;
+			}
+			backupptr += mtd.eb_size;
+		}
+	}
+out_cleanup:
+	free(bbt);
+	free(boundary);
+	free(twopages);
+	free(writebuf);
+	free(backup);
+	close(fd);
+	return status;
+}
+
-- 
2.7.3

  parent reply	other threads:[~2016-04-25 22:14 UTC|newest]

Thread overview: 29+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2016-04-25 22:13 [RFC] Porting kernel MTD tests to user space Richard Weinberger
2016-04-25 22:13 ` [PATCH 1/8] mtd-utils: Fix return status in mtd_torture test function Richard Weinberger
2016-04-26  7:57   ` Boris Brezillon
2016-07-13 17:30   ` Brian Norris
2016-07-13 21:59     ` Richard Weinberger
2016-07-13 22:06       ` Brian Norris
2016-04-25 22:13 ` [PATCH 2/8] mtd-utils: Add multi-block erase function Richard Weinberger
2016-04-26  8:04   ` Boris Brezillon
2016-04-27  9:21     ` David Oberhollenzer
2016-04-27  9:27       ` Boris Brezillon
2016-04-25 22:13 ` [PATCH 3/8] mtd-utils: Add flash torture test utility Richard Weinberger
2016-04-26  8:13   ` Boris Brezillon
2016-04-26 14:34   ` Ezequiel Garcia
2016-04-27  9:28     ` David Oberhollenzer
2016-04-25 22:13 ` [PATCH 4/8] mtd-utils: Add flash stress test Utility Richard Weinberger
2016-04-26  8:18   ` Boris Brezillon
2016-04-26  9:22     ` Richard Weinberger
2016-04-26  9:47       ` Boris Brezillon
2016-04-27 16:38         ` Brian Norris
2016-04-25 22:13 ` [PATCH 5/8] mtd-utils: Add flash speed " Richard Weinberger
2016-04-25 22:13 ` [PATCH 6/8] mtd-utils: Add nand flash bit errors test Richard Weinberger
2016-04-25 22:13 ` [PATCH 7/8] mtd-utils: Add flash read test utility Richard Weinberger
2016-04-25 22:13 ` Richard Weinberger [this message]
2016-04-26  3:13 ` [RFC] Porting kernel MTD tests to user space Ezequiel Garcia
2016-04-26  7:00   ` Richard Weinberger
2016-04-27 16:32     ` Brian Norris
2016-04-26  5:17 ` Artem Bityutskiy
2016-04-26  6:58   ` Richard Weinberger
2016-04-26  7:45 ` Boris Brezillon

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1461622409-14970-9-git-send-email-richard@nod.at \
    --to=richard@nod.at \
    --cc=david.oberhollenzer@sigma-star.at \
    --cc=linux-mtd@lists.infradead.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.