From: Kyungmin Park <kmpark@infradead.org>
To: u-boot@lists.denx.de
Subject: [U-Boot] [PATCH] Fix OneNAND read_oob/write_oob functions compatability
Date: Sat, 16 Aug 2008 14:32:16 +0900 [thread overview]
Message-ID: <20080816053216.GA5081@july> (raw)
Also sync with kernel OneNAND codes
Thank you,
Kyungmin Park
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
---
diff --git a/common/cmd_onenand.c b/common/cmd_onenand.c
index 5e2062b..8d87b78 100644
--- a/common/cmd_onenand.c
+++ b/common/cmd_onenand.c
@@ -85,15 +85,25 @@ int do_onenand(cmd_tbl_t * cmdtp, int flag, int argc, char *argv[])
ulong addr = simple_strtoul(argv[2], NULL, 16);
ulong ofs = simple_strtoul(argv[3], NULL, 16);
size_t len = simple_strtoul(argv[4], NULL, 16);
- size_t retlen = 0;
int oob = strncmp(argv[1], "read.oob", 8) ? 0 : 1;
+ struct mtd_oob_ops ops;
+
+ ops.mode = MTD_OOB_PLACE;
+
+ if (oob) {
+ ops.len = 0;
+ ops.datbuf = NULL;
+ ops.ooblen = len;
+ ops.oobbuf = (u_char *) addr;
+ } else {
+ ops.len = len;
+ ops.datbuf = (u_char *) addr;
+ ops.ooblen = 0;
+ ops.oobbuf = NULL;
+ }
+ ops.retlen = ops.oobretlen = 0;
- if (oob)
- onenand_read_oob(&onenand_mtd, ofs, len,
- &retlen, (u_char *) addr);
- else
- onenand_read(&onenand_mtd, ofs, len, &retlen,
- (u_char *) addr);
+ onenand_mtd.read_oob(&onenand_mtd, ofs, &ops);
printf("Done\n");
return 0;
@@ -117,9 +127,12 @@ int do_onenand(cmd_tbl_t * cmdtp, int flag, int argc, char *argv[])
ulong block = simple_strtoul(argv[3], NULL, 10);
ulong page = simple_strtoul(argv[4], NULL, 10);
size_t len = simple_strtol(argv[5], NULL, 10);
- size_t retlen = 0;
ulong ofs;
int oob = strncmp(argv[1], "block.oob", 9) ? 0 : 1;
+ struct mtd_oob_ops ops;
+
+ ops.mode = MTD_OOB_PLACE;
+
ofs = block << onenand_chip.erase_shift;
if (page)
@@ -127,17 +140,21 @@ int do_onenand(cmd_tbl_t * cmdtp, int flag, int argc, char *argv[])
if (!len) {
if (oob)
- len = 64;
+ ops.ooblen = 64;
else
- len = 512;
+ ops.len = 512;
+ }
+
+ if (oob) {
+ ops.datbuf = NULL;
+ ops.oobbuf = (u_char *) addr;
+ } else {
+ ops.datbuf = (u_char *) addr;
+ ops.oobbuf = NULL;
}
+ ops.retlen = ops.oobretlen = 0;
- if (oob)
- onenand_read_oob(&onenand_mtd, ofs, len,
- &retlen, (u_char *) addr);
- else
- onenand_read(&onenand_mtd, ofs, len, &retlen,
- (u_char *) addr);
+ onenand_read_oob(&onenand_mtd, ofs, &ops);
return 0;
}
diff --git a/drivers/mtd/onenand/onenand_base.c b/drivers/mtd/onenand/onenand_base.c
index 9ce68e1..d14586d 100644
--- a/drivers/mtd/onenand/onenand_base.c
+++ b/drivers/mtd/onenand/onenand_base.c
@@ -526,83 +526,269 @@ static void onenand_release_device(struct mtd_info *mtd)
}
/**
- * onenand_read_ecc - [MTD Interface] Read data with ECC
+ * onenand_transfer_auto_oob - [Internal] oob auto-placement transfer
+ * @param mtd MTD device structure
+ * @param buf destination address
+ * @param column oob offset to read from
+ * @param thislen oob length to read
+ */
+static int onenand_transfer_auto_oob(struct mtd_info *mtd, uint8_t *buf,
+ int column, int thislen)
+{
+ struct onenand_chip *this = mtd->priv;
+ struct nand_oobfree *free;
+ int readcol = column;
+ int readend = column + thislen;
+ int lastgap = 0;
+ unsigned int i;
+ uint8_t *oob_buf = this->oob_buf;
+
+ free = this->ecclayout->oobfree;
+ for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES && free->length; i++, free++) {
+ if (readcol >= lastgap)
+ readcol += free->offset - lastgap;
+ if (readend >= lastgap)
+ readend += free->offset - lastgap;
+ lastgap = free->offset + free->length;
+ }
+ this->read_bufferram(mtd, ONENAND_SPARERAM, oob_buf, 0, mtd->oobsize);
+ free = this->ecclayout->oobfree;
+ for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES && free->length; i++, free++) {
+ int free_end = free->offset + free->length;
+ if (free->offset < readend && free_end > readcol) {
+ int st = max_t(int,free->offset,readcol);
+ int ed = min_t(int,free_end,readend);
+ int n = ed - st;
+ memcpy(buf, oob_buf + st, n);
+ buf += n;
+ } else if (column == 0)
+ break;
+ }
+ return 0;
+}
+
+/**
+ * onenand_read_ops_nolock - [OneNAND Interface] OneNAND read main and/or out-of-band
* @param mtd MTD device structure
* @param from offset to read from
- * @param len number of bytes to read
- * @param retlen pointer to variable to store the number of read bytes
- * @param buf the databuffer to put data
- * @param oob_buf filesystem supplied oob data buffer
- * @param oobsel oob selection structure
+ * @param ops oob operation description structure
*
- * OneNAND read with ECC
+ * OneNAND read main and/or out-of-band data
*/
-static int onenand_read_ecc(struct mtd_info *mtd, loff_t from, size_t len,
- size_t * retlen, u_char * buf,
- u_char * oob_buf, struct nand_oobinfo *oobsel)
+static int onenand_read_ops_nolock(struct mtd_info *mtd, loff_t from,
+ struct mtd_oob_ops *ops)
{
struct onenand_chip *this = mtd->priv;
- int read = 0, column;
- int thislen;
- int ret = 0;
+ struct mtd_ecc_stats stats;
+ size_t len = ops->len;
+ size_t ooblen = ops->ooblen;
+ u_char *buf = ops->datbuf;
+ u_char *oobbuf = ops->oobbuf;
+ int read = 0, column, thislen;
+ int oobread = 0, oobcolumn, thisooblen, oobsize;
+ int ret = 0, boundary = 0;
+ int writesize = this->writesize;
+
+ MTDDEBUG(MTD_DEBUG_LEVEL3, "onenand_read_ops_nolock: from = 0x%08x, len = %i\n", (unsigned int) from, (int) len);
+
+ if (ops->mode == MTD_OOB_AUTO)
+ oobsize = this->ecclayout->oobavail;
+ else
+ oobsize = mtd->oobsize;
- MTDDEBUG (MTD_DEBUG_LEVEL3, "onenand_read_ecc: "
- "from = 0x%08x, len = %i\n",
- (unsigned int)from, (int)len);
+ oobcolumn = from & (mtd->oobsize - 1);
/* Do not allow reads past end of device */
if ((from + len) > mtd->size) {
- MTDDEBUG (MTD_DEBUG_LEVEL0, "onenand_read_ecc: "
- "Attempt read beyond end of device\n");
- *retlen = 0;
+ printk(KERN_ERR "onenand_read_ops_nolock: Attempt read beyond end of device\n");
+ ops->retlen = 0;
+ ops->oobretlen = 0;
return -EINVAL;
}
- /* Grab the lock and see if the device is available */
- onenand_get_device(mtd, FL_READING);
-
- while (read < len) {
- thislen = min_t(int, mtd->writesize, len - read);
+ stats = mtd->ecc_stats;
- column = from & (mtd->writesize - 1);
- if (column + thislen > mtd->writesize)
- thislen = mtd->writesize - column;
+ /* Read-while-load method */
+ /* Do first load to bufferRAM */
+ if (read < len) {
if (!onenand_check_bufferram(mtd, from)) {
- this->command(mtd, ONENAND_CMD_READ, from,
- mtd->writesize);
+ this->command(mtd, ONENAND_CMD_READ, from, writesize);
ret = this->wait(mtd, FL_READING);
- /* First copy data and check return value for ECC handling */
- onenand_update_bufferram(mtd, from, 1);
+ onenand_update_bufferram(mtd, from, !ret);
+ if (ret == -EBADMSG)
+ ret = 0;
}
+ }
- this->read_bufferram(mtd, ONENAND_DATARAM, buf, column,
- thislen);
+ thislen = min_t(int, writesize, len - read);
+ column = from & (writesize - 1);
+ if (column + thislen > writesize)
+ thislen = writesize - column;
- read += thislen;
- if (read == len)
- break;
+ while (!ret) {
+ /* If there is more to load then start next load */
+ from += thislen;
+ if (read + thislen < len) {
+ this->command(mtd, ONENAND_CMD_READ, from, writesize);
+ /*
+ * Chip boundary handling in DDP
+ * Now we issued chip 1 read and pointed chip 1
+ * bufferam so we have to point chip 0 bufferam.
+ */
+ if (ONENAND_IS_DDP(this) &&
+ unlikely(from == (this->chipsize >> 1))) {
+ this->write_word(ONENAND_DDP_CHIP0, this->base + ONENAND_REG_START_ADDRESS2);
+ boundary = 1;
+ } else
+ boundary = 0;
+ ONENAND_SET_PREV_BUFFERRAM(this);
+ }
- if (ret) {
- MTDDEBUG (MTD_DEBUG_LEVEL0,
- "onenand_read_ecc: read failed = %d\n", ret);
- break;
+ /* While load is going, read from last bufferRAM */
+ this->read_bufferram(mtd, ONENAND_DATARAM, buf, column, thislen);
+
+ /* Read oob area if needed */
+ if (oobbuf) {
+ thisooblen = oobsize - oobcolumn;
+ thisooblen = min_t(int, thisooblen, ooblen - oobread);
+
+ if (ops->mode == MTD_OOB_AUTO)
+ onenand_transfer_auto_oob(mtd, oobbuf, oobcolumn, thisooblen);
+ else
+ this->read_bufferram(mtd, ONENAND_SPARERAM, oobbuf, oobcolumn, thisooblen);
+ oobread += thisooblen;
+ oobbuf += thisooblen;
+ oobcolumn = 0;
}
- from += thislen;
+ /* See if we are done */
+ read += thislen;
+ if (read == len)
+ break;
+ /* Set up for next read from bufferRAM */
+ if (unlikely(boundary))
+ this->write_word(ONENAND_DDP_CHIP1, this->base + ONENAND_REG_START_ADDRESS2);
+ ONENAND_SET_NEXT_BUFFERRAM(this);
buf += thislen;
- }
+ thislen = min_t(int, writesize, len - read);
+ column = 0;
- /* Deselect and wake up anyone waiting on the device */
- onenand_release_device(mtd);
+ /* Now wait for load */
+ ret = this->wait(mtd, FL_READING);
+ onenand_update_bufferram(mtd, from, !ret);
+ if (ret == -EBADMSG)
+ ret = 0;
+ }
/*
* Return success, if no ECC failures, else -EBADMSG
* fs driver will take care of that, because
* retlen == desired len and result == -EBADMSG
*/
- *retlen = read;
- return ret;
+ ops->retlen = read;
+ ops->oobretlen = oobread;
+
+ if (ret)
+ return ret;
+
+ if (mtd->ecc_stats.failed - stats.failed)
+ return -EBADMSG;
+
+ return mtd->ecc_stats.corrected - stats.corrected ? -EUCLEAN : 0;
+}
+
+/**
+ * onenand_read_oob_nolock - [MTD Interface] OneNAND read out-of-band
+ * @param mtd MTD device structure
+ * @param from offset to read from
+ * @param ops oob operation description structure
+ *
+ * OneNAND read out-of-band data from the spare area
+ */
+static int onenand_read_oob_nolock(struct mtd_info *mtd, loff_t from,
+ struct mtd_oob_ops *ops)
+{
+ struct onenand_chip *this = mtd->priv;
+ struct mtd_ecc_stats stats;
+ int read = 0, thislen, column, oobsize;
+ size_t len = ops->ooblen;
+ mtd_oob_mode_t mode = ops->mode;
+ u_char *buf = ops->oobbuf;
+ int ret = 0;
+
+ from += ops->ooboffs;
+
+ MTDDEBUG(MTD_DEBUG_LEVEL3, "onenand_read_oob_nolock: from = 0x%08x, len = %i\n", (unsigned int) from, (int) len);
+
+ /* Initialize return length value */
+ ops->oobretlen = 0;
+
+ if (mode == MTD_OOB_AUTO)
+ oobsize = this->ecclayout->oobavail;
+ else
+ oobsize = mtd->oobsize;
+
+ column = from & (mtd->oobsize - 1);
+
+ if (unlikely(column >= oobsize)) {
+ printk(KERN_ERR "onenand_read_oob_nolock: Attempted to start read outside oob\n");
+ return -EINVAL;
+ }
+
+ /* Do not allow reads past end of device */
+ if (unlikely(from >= mtd->size ||
+ column + len > ((mtd->size >> this->page_shift) -
+ (from >> this->page_shift)) * oobsize)) {
+ printk(KERN_ERR "onenand_read_oob_nolock: Attempted to read beyond end of device\n");
+ return -EINVAL;
+ }
+
+ stats = mtd->ecc_stats;
+
+ while (read < len) {
+ thislen = oobsize - column;
+ thislen = min_t(int, thislen, len);
+
+ this->command(mtd, ONENAND_CMD_READOOB, from, mtd->oobsize);
+
+ onenand_update_bufferram(mtd, from, 0);
+
+ ret = this->wait(mtd, FL_READING);
+ if (ret && ret != -EBADMSG) {
+ printk(KERN_ERR "onenand_read_oob_nolock: read failed = 0x%x\n", ret);
+ break;
+ }
+
+ if (mode == MTD_OOB_AUTO)
+ onenand_transfer_auto_oob(mtd, buf, column, thislen);
+ else
+ this->read_bufferram(mtd, ONENAND_SPARERAM, buf, column, thislen);
+
+ read += thislen;
+
+ if (read == len)
+ break;
+
+ buf += thislen;
+
+ /* Read more? */
+ if (read < len) {
+ /* Page size */
+ from += mtd->writesize;
+ column = 0;
+ }
+ }
+
+ ops->oobretlen = read;
+
+ if (ret)
+ return ret;
+
+ if (mtd->ecc_stats.failed - stats.failed)
+ return -EBADMSG;
+
+ return 0;
}
/**
@@ -618,38 +804,124 @@ static int onenand_read_ecc(struct mtd_info *mtd, loff_t from, size_t len,
int onenand_read(struct mtd_info *mtd, loff_t from, size_t len,
size_t * retlen, u_char * buf)
{
- return onenand_read_ecc(mtd, from, len, retlen, buf, NULL, NULL);
+ struct mtd_oob_ops ops = {
+ .len = len,
+ .ooblen = 0,
+ .datbuf = buf,
+ .oobbuf = NULL,
+ };
+ int ret;
+
+ onenand_get_device(mtd, FL_READING);
+ ret = onenand_read_ops_nolock(mtd, from, &ops);
+ onenand_release_device(mtd);
+
+ *retlen = ops.retlen;
+ return ret;
}
/**
* onenand_read_oob - [MTD Interface] OneNAND read out-of-band
* @param mtd MTD device structure
* @param from offset to read from
- * @param len number of bytes to read
- * @param retlen pointer to variable to store the number of read bytes
- * @param buf the databuffer to put data
+ * @param ops oob operations description structure
*
- * OneNAND read out-of-band data from the spare area
+ * OneNAND main and/or out-of-band
+ */
+int onenand_read_oob(struct mtd_info *mtd, loff_t from,
+ struct mtd_oob_ops *ops)
+{
+ int ret;
+
+ switch (ops->mode) {
+ case MTD_OOB_PLACE:
+ case MTD_OOB_AUTO:
+ break;
+ case MTD_OOB_RAW:
+ /* Not implemented yet */
+ default:
+ return -EINVAL;
+ }
+
+ onenand_get_device(mtd, FL_READING);
+ if (ops->datbuf)
+ ret = onenand_read_ops_nolock(mtd, from, ops);
+ else
+ ret = onenand_read_oob_nolock(mtd, from, ops);
+ onenand_release_device(mtd);
+
+ return ret;
+}
+
+/**
+ * onenand_bbt_wait - [DEFAULT] wait until the command is done
+ * @param mtd MTD device structure
+ * @param state state to select the max. timeout value
+ *
+ * Wait for command done.
+ */
+static int onenand_bbt_wait(struct mtd_info *mtd, int state)
+{
+ struct onenand_chip *this = mtd->priv;
+ unsigned int flags = ONENAND_INT_MASTER;
+ unsigned int interrupt;
+ unsigned int ctrl;
+
+ while (1) {
+ interrupt = this->read_word(this->base + ONENAND_REG_INTERRUPT);
+ if (interrupt & flags)
+ break;
+ }
+
+ /* To get correct interrupt status in timeout case */
+ interrupt = this->read_word(this->base + ONENAND_REG_INTERRUPT);
+ ctrl = this->read_word(this->base + ONENAND_REG_CTRL_STATUS);
+
+ /* Initial bad block case: 0x2400 or 0x0400 */
+ if (ctrl & ONENAND_CTRL_ERROR) {
+ printk(KERN_DEBUG "onenand_bbt_wait: controller error = 0x%04x\n", ctrl);
+ return ONENAND_BBT_READ_ERROR;
+ }
+
+ if (interrupt & ONENAND_INT_READ) {
+ int ecc = this->read_word(this->base + ONENAND_REG_ECC_STATUS);
+ if (ecc & ONENAND_ECC_2BIT_ALL)
+ return ONENAND_BBT_READ_ERROR;
+ } else {
+ printk(KERN_ERR "onenand_bbt_wait: read timeout!"
+ "ctrl=0x%04x intr=0x%04x\n", ctrl, interrupt);
+ return ONENAND_BBT_READ_FATAL_ERROR;
+ }
+
+ return 0;
+}
+
+/**
+ * onenand_bbt_read_oob - [MTD Interface] OneNAND read out-of-band for bbt scan
+ * @param mtd MTD device structure
+ * @param from offset to read from
+ * @param ops oob operation description structure
+ *
+ * OneNAND read out-of-band data from the spare area for bbt scan
*/
-int onenand_read_oob(struct mtd_info *mtd, loff_t from, size_t len,
- size_t * retlen, u_char * buf)
+int onenand_bbt_read_oob(struct mtd_info *mtd, loff_t from,
+ struct mtd_oob_ops *ops)
{
struct onenand_chip *this = mtd->priv;
int read = 0, thislen, column;
int ret = 0;
+ size_t len = ops->ooblen;
+ u_char *buf = ops->oobbuf;
- MTDDEBUG (MTD_DEBUG_LEVEL3, "onenand_read_oob: "
- "from = 0x%08x, len = %i\n",
- (unsigned int)from, (int)len);
+ MTDDEBUG(MTD_DEBUG_LEVEL3, "onenand_bbt_read_oob: from = 0x%08x, len = %zi\n", (unsigned int) from, len);
- /* Initialize return length value */
- *retlen = 0;
+ /* Initialize return value */
+ ops->oobretlen = 0;
/* Do not allow reads past end of device */
if (unlikely((from + len) > mtd->size)) {
- MTDDEBUG (MTD_DEBUG_LEVEL0, "onenand_read_oob: "
- "Attempt read beyond end of device\n");
- return -EINVAL;
+ printk(KERN_ERR "onenand_bbt_read_oob: Attempt read beyond end of device\n");
+ return ONENAND_BBT_READ_FATAL_ERROR;
}
/* Grab the lock and see if the device is available */
@@ -658,6 +930,7 @@ int onenand_read_oob(struct mtd_info *mtd, loff_t from, size_t len,
column = from & (mtd->oobsize - 1);
while (read < len) {
+
thislen = mtd->oobsize - column;
thislen = min_t(int, thislen, len);
@@ -665,27 +938,21 @@ int onenand_read_oob(struct mtd_info *mtd, loff_t from, size_t len,
onenand_update_bufferram(mtd, from, 0);
- ret = this->wait(mtd, FL_READING);
- /* First copy data and check return value for ECC handling */
-
- this->read_bufferram(mtd, ONENAND_SPARERAM, buf, column,
- thislen);
+ ret = onenand_bbt_wait(mtd, FL_READING);
+ if (ret)
+ break;
+ this->read_bufferram(mtd, ONENAND_SPARERAM, buf, column, thislen);
read += thislen;
if (read == len)
break;
- if (ret) {
- MTDDEBUG (MTD_DEBUG_LEVEL0,
- "onenand_read_oob: read failed = %d\n", ret);
- break;
- }
-
buf += thislen;
+
/* Read more? */
if (read < len) {
- /* Page size */
- from += mtd->writesize;
+ /* Update Page size */
+ from += this->writesize;
column = 0;
}
}
@@ -693,224 +960,421 @@ int onenand_read_oob(struct mtd_info *mtd, loff_t from, size_t len,
/* Deselect and wake up anyone waiting on the device */
onenand_release_device(mtd);
- *retlen = read;
+ ops->oobretlen = read;
return ret;
}
+
#ifdef CONFIG_MTD_ONENAND_VERIFY_WRITE
/**
- * onenand_verify_page - [GENERIC] verify the chip contents after a write
- * @param mtd MTD device structure
- * @param buf the databuffer to verify
- *
- * Check DataRAM area directly
+ * onenand_verify_oob - [GENERIC] verify the oob contents after a write
+ * @param mtd MTD device structure
+ * @param buf the databuffer to verify
+ * @param to offset to read from
*/
-static int onenand_verify_page(struct mtd_info *mtd, u_char * buf,
- loff_t addr)
+static int onenand_verify_oob(struct mtd_info *mtd, const u_char *buf, loff_t to)
{
struct onenand_chip *this = mtd->priv;
- void __iomem *dataram0, *dataram1;
+ u_char *oob_buf = this->oob_buf;
+ int status, i;
+
+ this->command(mtd, ONENAND_CMD_READOOB, to, mtd->oobsize);
+ onenand_update_bufferram(mtd, to, 0);
+ status = this->wait(mtd, FL_READING);
+ if (status)
+ return status;
+
+ this->read_bufferram(mtd, ONENAND_SPARERAM, oob_buf, 0, mtd->oobsize);
+ for (i = 0; i < mtd->oobsize; i++)
+ if (buf[i] != 0xFF && buf[i] != oob_buf[i])
+ return -EBADMSG;
+
+ return 0;
+}
+
+/**
+ * onenand_verify - [GENERIC] verify the chip contents after a write
+ * @param mtd MTD device structure
+ * @param buf the databuffer to verify
+ * @param addr offset to read from
+ * @param len number of bytes to read and compare
+ */
+static int onenand_verify(struct mtd_info *mtd, const u_char *buf, loff_t addr, size_t len)
+{
+ struct onenand_chip *this = mtd->priv;
+ void __iomem *dataram;
int ret = 0;
+ int thislen, column;
- this->command(mtd, ONENAND_CMD_READ, addr, mtd->writesize);
+ while (len != 0) {
+ thislen = min_t(int, this->writesize, len);
+ column = addr & (this->writesize - 1);
+ if (column + thislen > this->writesize)
+ thislen = this->writesize - column;
- ret = this->wait(mtd, FL_READING);
- if (ret)
- return ret;
+ this->command(mtd, ONENAND_CMD_READ, addr, this->writesize);
- onenand_update_bufferram(mtd, addr, 1);
+ onenand_update_bufferram(mtd, addr, 0);
- /* Check, if the two dataram areas are same */
- dataram0 = this->base + ONENAND_DATARAM;
- dataram1 = dataram0 + mtd->writesize;
+ ret = this->wait(mtd, FL_READING);
+ if (ret)
+ return ret;
- if (memcmp(dataram0, dataram1, mtd->writesize))
- return -EBADMSG;
+ onenand_update_bufferram(mtd, addr, 1);
+
+ dataram = this->base + ONENAND_DATARAM;
+ dataram += onenand_bufferram_offset(mtd, ONENAND_DATARAM);
+
+ if (memcmp(buf, dataram + column, thislen))
+ return -EBADMSG;
+
+ len -= thislen;
+ buf += thislen;
+ addr += thislen;
+ }
return 0;
}
#else
-#define onenand_verify_page(...) (0)
+#define onenand_verify(...) (0)
+#define onenand_verify_oob(...) (0)
#endif
#define NOTALIGNED(x) ((x & (mtd->writesize - 1)) != 0)
/**
- * onenand_write_ecc - [MTD Interface] OneNAND write with ECC
- * @param mtd MTD device structure
- * @param to offset to write to
- * @param len number of bytes to write
- * @param retlen pointer to variable to store the number of written bytes
- * @param buf the data to write
- * @param eccbuf filesystem supplied oob data buffer
- * @param oobsel oob selection structure
+ * onenand_fill_auto_oob - [Internal] oob auto-placement transfer
+ * @param mtd MTD device structure
+ * @param oob_buf oob buffer
+ * @param buf source address
+ * @param column oob offset to write to
+ * @param thislen oob length to write
+ */
+static int onenand_fill_auto_oob(struct mtd_info *mtd, u_char *oob_buf,
+ const u_char *buf, int column, int thislen)
+{
+ struct onenand_chip *this = mtd->priv;
+ struct nand_oobfree *free;
+ int writecol = column;
+ int writeend = column + thislen;
+ int lastgap = 0;
+ unsigned int i;
+
+ free = this->ecclayout->oobfree;
+ for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES && free->length; i++, free++) {
+ if (writecol >= lastgap)
+ writecol += free->offset - lastgap;
+ if (writeend >= lastgap)
+ writeend += free->offset - lastgap;
+ lastgap = free->offset + free->length;
+ }
+ free = this->ecclayout->oobfree;
+ for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES && free->length; i++, free++) {
+ int free_end = free->offset + free->length;
+ if (free->offset < writeend && free_end > writecol) {
+ int st = max_t(int,free->offset,writecol);
+ int ed = min_t(int,free_end,writeend);
+ int n = ed - st;
+ memcpy(oob_buf + st, buf, n);
+ buf += n;
+ } else if (column == 0)
+ break;
+ }
+ return 0;
+}
+
+/**
+ * onenand_write_ops_nolock - [OneNAND Interface] write main and/or out-of-band
+ * @param mtd MTD device structure
+ * @param to offset to write to
+ * @param ops oob operation description structure
*
- * OneNAND write with ECC
+ * Write main and/or oob with ECC
*/
-static int onenand_write_ecc(struct mtd_info *mtd, loff_t to, size_t len,
- size_t * retlen, const u_char * buf,
- u_char * eccbuf, struct nand_oobinfo *oobsel)
+static int onenand_write_ops_nolock(struct mtd_info *mtd, loff_t to,
+ struct mtd_oob_ops *ops)
{
struct onenand_chip *this = mtd->priv;
- int written = 0;
+ int written = 0, column, thislen, subpage;
+ int oobwritten = 0, oobcolumn, thisooblen, oobsize;
+ size_t len = ops->len;
+ size_t ooblen = ops->ooblen;
+ const u_char *buf = ops->datbuf;
+ const u_char *oob = ops->oobbuf;
+ u_char *oobbuf;
int ret = 0;
- MTDDEBUG (MTD_DEBUG_LEVEL3, "onenand_write_ecc: "
- "to = 0x%08x, len = %i\n",
- (unsigned int)to, (int)len);
+ MTDDEBUG(MTD_DEBUG_LEVEL3, "onenand_write_ops_nolock: to = 0x%08x, len = %i\n", (unsigned int) to, (int) len);
/* Initialize retlen, in case of early exit */
- *retlen = 0;
+ ops->retlen = 0;
+ ops->oobretlen = 0;
/* Do not allow writes past end of device */
if (unlikely((to + len) > mtd->size)) {
- MTDDEBUG (MTD_DEBUG_LEVEL0, "onenand_write_ecc: "
- "Attempt write to past end of device\n");
+ printk(KERN_ERR "onenand_write_ops_nolock: Attempt write to past end of device\n");
return -EINVAL;
}
/* Reject writes, which are not page aligned */
- if (unlikely(NOTALIGNED(to)) || unlikely(NOTALIGNED(len))) {
- MTDDEBUG (MTD_DEBUG_LEVEL0, "onenand_write_ecc: "
- "Attempt to write not page aligned data\n");
+ if (unlikely(NOTALIGNED(to) || NOTALIGNED(len))) {
+ printk(KERN_ERR "onenand_write_ops_nolock: Attempt to write not page aligned data\n");
return -EINVAL;
}
- /* Grab the lock and see if the device is available */
- onenand_get_device(mtd, FL_WRITING);
+ if (ops->mode == MTD_OOB_AUTO)
+ oobsize = this->ecclayout->oobavail;
+ else
+ oobsize = mtd->oobsize;
+
+ oobcolumn = to & (mtd->oobsize - 1);
+
+ column = to & (mtd->writesize - 1);
/* Loop until all data write */
while (written < len) {
- int thislen = min_t(int, mtd->writesize, len - written);
+ u_char *wbuf = (u_char *) buf;
- this->command(mtd, ONENAND_CMD_BUFFERRAM, to, mtd->writesize);
+ thislen = min_t(int, mtd->writesize - column, len - written);
+ thisooblen = min_t(int, oobsize - oobcolumn, ooblen - oobwritten);
- this->write_bufferram(mtd, ONENAND_DATARAM, buf, 0, thislen);
- this->write_bufferram(mtd, ONENAND_SPARERAM, ffchars, 0,
- mtd->oobsize);
+ this->command(mtd, ONENAND_CMD_BUFFERRAM, to, thislen);
- this->command(mtd, ONENAND_CMD_PROG, to, mtd->writesize);
+ /* Partial page write */
+ subpage = thislen < mtd->writesize;
+ if (subpage) {
+ memset(this->page_buf, 0xff, mtd->writesize);
+ memcpy(this->page_buf + column, buf, thislen);
+ wbuf = this->page_buf;
+ }
+
+ this->write_bufferram(mtd, ONENAND_DATARAM, wbuf, 0, mtd->writesize);
+
+ if (oob) {
+ oobbuf = this->oob_buf;
- onenand_update_bufferram(mtd, to, 1);
+ /* We send data to spare ram with oobsize
+ * * to prevent byte access */
+ memset(oobbuf, 0xff, mtd->oobsize);
+ if (ops->mode == MTD_OOB_AUTO)
+ onenand_fill_auto_oob(mtd, oobbuf, oob, oobcolumn, thisooblen);
+ else
+ memcpy(oobbuf + oobcolumn, oob, thisooblen);
+
+ oobwritten += thisooblen;
+ oob += thisooblen;
+ oobcolumn = 0;
+ } else
+ oobbuf = (u_char *) ffchars;
+
+ this->write_bufferram(mtd, ONENAND_SPARERAM, oobbuf, 0, mtd->oobsize);
+
+ this->command(mtd, ONENAND_CMD_PROG, to, mtd->writesize);
ret = this->wait(mtd, FL_WRITING);
+
+ /* In partial page write we don't update bufferram */
+ onenand_update_bufferram(mtd, to, !ret && !subpage);
+ if (ONENAND_IS_2PLANE(this)) {
+ ONENAND_SET_BUFFERRAM1(this);
+ onenand_update_bufferram(mtd, to + this->writesize, !ret && !subpage);
+ }
+
if (ret) {
- MTDDEBUG (MTD_DEBUG_LEVEL0,
- "onenand_write_ecc: write filaed %d\n", ret);
+ printk(KERN_ERR "onenand_write_ops_nolock: write filaed %d\n", ret);
break;
}
- written += thislen;
-
/* Only check verify write turn on */
- ret = onenand_verify_page(mtd, (u_char *) buf, to);
+ ret = onenand_verify(mtd, buf, to, thislen);
if (ret) {
- MTDDEBUG (MTD_DEBUG_LEVEL0,
- "onenand_write_ecc: verify failed %d\n", ret);
+ printk(KERN_ERR "onenand_write_ops_nolock: verify failed %d\n", ret);
break;
}
+ written += thislen;
+
if (written == len)
break;
+ column = 0;
to += thislen;
buf += thislen;
}
- /* Deselect and wake up anyone waiting on the device */
- onenand_release_device(mtd);
-
- *retlen = written;
+ ops->retlen = written;
return ret;
}
/**
- * onenand_write - [MTD Interface] compability function for onenand_write_ecc
- * @param mtd MTD device structure
- * @param to offset to write to
- * @param len number of bytes to write
- * @param retlen pointer to variable to store the number of written bytes
- * @param buf the data to write
- *
- * This function simply calls onenand_write_ecc
- * with oob buffer and oobsel = NULL
- */
-int onenand_write(struct mtd_info *mtd, loff_t to, size_t len,
- size_t * retlen, const u_char * buf)
-{
- return onenand_write_ecc(mtd, to, len, retlen, buf, NULL, NULL);
-}
-
-/**
- * onenand_write_oob - [MTD Interface] OneNAND write out-of-band
- * @param mtd MTD device structure
- * @param to offset to write to
- * @param len number of bytes to write
- * @param retlen pointer to variable to store the number of written bytes
- * @param buf the data to write
+ * onenand_write_oob_nolock - [Internal] OneNAND write out-of-band
+ * @param mtd MTD device structure
+ * @param to offset to write to
+ * @param len number of bytes to write
+ * @param retlen pointer to variable to store the number of written bytes
+ * @param buf the data to write
+ * @param mode operation mode
*
* OneNAND write out-of-band
*/
-int onenand_write_oob(struct mtd_info *mtd, loff_t to, size_t len,
- size_t * retlen, const u_char * buf)
+static int onenand_write_oob_nolock(struct mtd_info *mtd, loff_t to,
+ struct mtd_oob_ops *ops)
{
struct onenand_chip *this = mtd->priv;
- int column, status;
+ int column, ret = 0, oobsize;
int written = 0;
+ u_char *oobbuf;
+ size_t len = ops->ooblen;
+ const u_char *buf = ops->oobbuf;
+ mtd_oob_mode_t mode = ops->mode;
- MTDDEBUG (MTD_DEBUG_LEVEL3, "onenand_write_oob: "
- "to = 0x%08x, len = %i\n",
- (unsigned int)to, (int)len);
+ to += ops->ooboffs;
+
+ MTDDEBUG(MTD_DEBUG_LEVEL3, "onenand_write_oob_nolock: to = 0x%08x, len = %i\n", (unsigned int) to, (int) len);
/* Initialize retlen, in case of early exit */
- *retlen = 0;
+ ops->oobretlen = 0;
- /* Do not allow writes past end of device */
- if (unlikely((to + len) > mtd->size)) {
- MTDDEBUG (MTD_DEBUG_LEVEL0, "onenand_write_oob: "
- "Attempt write to past end of device\n");
+ if (mode == MTD_OOB_AUTO)
+ oobsize = this->ecclayout->oobavail;
+ else
+ oobsize = mtd->oobsize;
+
+ column = to & (mtd->oobsize - 1);
+
+ if (unlikely(column >= oobsize)) {
+ printk(KERN_ERR "onenand_write_oob_nolock: Attempted to start write outside oob\n");
return -EINVAL;
}
- /* Grab the lock and see if the device is available */
- onenand_get_device(mtd, FL_WRITING);
+ /* For compatibility with NAND: Do not allow write past end of page */
+ if (unlikely(column + len > oobsize)) {
+ printk(KERN_ERR "onenand_write_oob_nolock: "
+ "Attempt to write past end of page\n");
+ return -EINVAL;
+ }
+
+ /* Do not allow reads past end of device */
+ if (unlikely(to >= mtd->size ||
+ column + len > ((mtd->size >> this->page_shift) -
+ (to >> this->page_shift)) * oobsize)) {
+ printk(KERN_ERR "onenand_write_oob_nolock: Attempted to write past end of device\n");
+ return -EINVAL;
+ }
+
+ oobbuf = this->oob_buf;
/* Loop until all data write */
while (written < len) {
- int thislen = min_t(int, mtd->oobsize, len - written);
-
- column = to & (mtd->oobsize - 1);
+ int thislen = min_t(int, oobsize, len - written);
this->command(mtd, ONENAND_CMD_BUFFERRAM, to, mtd->oobsize);
- this->write_bufferram(mtd, ONENAND_SPARERAM, ffchars, 0,
- mtd->oobsize);
- this->write_bufferram(mtd, ONENAND_SPARERAM, buf, column,
- thislen);
+ /* We send data to spare ram with oobsize
+ * to prevent byte access */
+ memset(oobbuf, 0xff, mtd->oobsize);
+ if (mode == MTD_OOB_AUTO)
+ onenand_fill_auto_oob(mtd, oobbuf, buf, column, thislen);
+ else
+ memcpy(oobbuf + column, buf, thislen);
+ this->write_bufferram(mtd, ONENAND_SPARERAM, oobbuf, 0, mtd->oobsize);
this->command(mtd, ONENAND_CMD_PROGOOB, to, mtd->oobsize);
onenand_update_bufferram(mtd, to, 0);
+ if (ONENAND_IS_2PLANE(this)) {
+ ONENAND_SET_BUFFERRAM1(this);
+ onenand_update_bufferram(mtd, to + this->writesize, 0);
+ }
- status = this->wait(mtd, FL_WRITING);
- if (status)
+ ret = this->wait(mtd, FL_WRITING);
+ if (ret) {
+ printk(KERN_ERR "onenand_write_oob_nolock: write failed %d\n", ret);
break;
+ }
+
+ ret = onenand_verify_oob(mtd, oobbuf, to);
+ if (ret) {
+ printk(KERN_ERR "onenand_write_oob_nolock: verify failed %d\n", ret);
+ break;
+ }
written += thislen;
if (written == len)
break;
- to += thislen;
+ to += mtd->writesize;
buf += thislen;
+ column = 0;
}
- /* Deselect and wake up anyone waiting on the device */
+ ops->oobretlen = written;
+
+ return ret;
+}
+
+/**
+ * onenand_write - [MTD Interface] compability function for onenand_write_ecc
+ * @param mtd MTD device structure
+ * @param to offset to write to
+ * @param len number of bytes to write
+ * @param retlen pointer to variable to store the number of written bytes
+ * @param buf the data to write
+ *
+ * Write with ECC
+ */
+int onenand_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t * retlen, const u_char * buf)
+{
+ struct mtd_oob_ops ops = {
+ .len = len,
+ .ooblen = 0,
+ .datbuf = (u_char *) buf,
+ .oobbuf = NULL,
+ };
+ int ret;
+
+ onenand_get_device(mtd, FL_WRITING);
+ ret = onenand_write_ops_nolock(mtd, to, &ops);
onenand_release_device(mtd);
- *retlen = written;
+ *retlen = ops.retlen;
+ return ret;
+}
+
+/**
+ * onenand_write_oob - [MTD Interface] OneNAND write out-of-band
+ * @param mtd MTD device structure
+ * @param to offset to write to
+ * @param ops oob operation description structure
+ *
+ * OneNAND write main and/or out-of-band
+ */
+int onenand_write_oob(struct mtd_info *mtd, loff_t to,
+ struct mtd_oob_ops *ops)
+{
+ int ret;
+
+ switch (ops->mode) {
+ case MTD_OOB_PLACE:
+ case MTD_OOB_AUTO:
+ break;
+ case MTD_OOB_RAW:
+ /* Not implemented yet */
+ default:
+ return -EINVAL;
+ }
+
+ onenand_get_device(mtd, FL_WRITING);
+ if (ops->datbuf)
+ ret = onenand_write_ops_nolock(mtd, to, ops);
+ else
+ ret = onenand_write_oob_nolock(mtd, to, ops);
+ onenand_release_device(mtd);
+
+ return ret;
- return 0;
}
/**
@@ -1277,6 +1741,8 @@ static int onenand_probe(struct mtd_info *mtd)
this->page_shift = ffs(mtd->writesize) - 1;
this->ppb_shift = (this->erase_shift - this->page_shift);
this->page_mask = (mtd->erasesize / mtd->writesize) - 1;
+ /* It's real page size */
+ this->writesize = mtd->writesize;
/* REVIST: Multichip handling */
@@ -1346,6 +1812,28 @@ int onenand_scan(struct mtd_info *mtd, int maxchips)
this->read_bufferram = onenand_sync_read_bufferram;
}
+ /* Allocate buffers, if necessary */
+ if (!this->page_buf) {
+ this->page_buf = kzalloc(mtd->writesize, GFP_KERNEL);
+ if (!this->page_buf) {
+ printk(KERN_ERR "onenand_scan(): Can't allocate page_buf\n");
+ return -ENOMEM;
+ }
+ this->options |= ONENAND_PAGEBUF_ALLOC;
+ }
+ if (!this->oob_buf) {
+ this->oob_buf = kzalloc(mtd->oobsize, GFP_KERNEL);
+ if (!this->oob_buf) {
+ printk(KERN_ERR "onenand_scan: Can't allocate oob_buf\n");
+ if (this->options & ONENAND_PAGEBUF_ALLOC) {
+ this->options &= ~ONENAND_PAGEBUF_ALLOC;
+ kfree(this->page_buf);
+ }
+ return -ENOMEM;
+ }
+ this->options |= ONENAND_OOBBUF_ALLOC;
+ }
+
onenand_unlock(mtd, 0, mtd->size);
return onenand_default_bbt(mtd);
diff --git a/drivers/mtd/onenand/onenand_bbt.c b/drivers/mtd/onenand/onenand_bbt.c
index d13d277..dde11ae 100644
--- a/drivers/mtd/onenand/onenand_bbt.c
+++ b/drivers/mtd/onenand/onenand_bbt.c
@@ -68,6 +68,7 @@ static int create_bbt(struct mtd_info *mtd, uint8_t * buf,
int startblock;
loff_t from;
size_t readlen, ooblen;
+ struct mtd_oob_ops ops;
printk(KERN_INFO "Scanning device for bad blocks\n");
@@ -85,25 +86,26 @@ static int create_bbt(struct mtd_info *mtd, uint8_t * buf,
startblock = 0;
from = 0;
+ ops.mode = MTD_OOB_PLACE;
+ ops.ooblen = readlen;
+ ops.oobbuf = buf;
+ ops.len = ops.ooboffs = ops.retlen = ops.oobretlen = 0;
+
for (i = startblock; i < numblocks;) {
int ret;
for (j = 0; j < len; j++) {
- size_t retlen;
-
/* No need to read pages fully,
* just read required OOB bytes */
- ret = onenand_read_oob(mtd,
+ ret = onenand_bbt_read_oob(mtd,
from + j * mtd->writesize +
- bd->offs, readlen, &retlen,
- &buf[0]);
+ bd->offs, &ops);
- if (ret && ret != -EAGAIN) {
- printk("ret = %d\n", ret);
- return ret;
- }
+ /* If it is a initial bad block, just ignore it */
+ if (ret == ONENAND_BBT_READ_FATAL_ERROR)
+ return -EIO;
- if (check_short_pattern
+ if (ret || check_short_pattern
(&buf[j * scanlen], scanlen, mtd->writesize, bd)) {
bbm->bbt[i >> 3] |= 0x03 << (i & 0x6);
printk(KERN_WARNING
diff --git a/include/linux/mtd/bbm.h b/include/linux/mtd/bbm.h
index bffb25b..abf8f1a 100644
--- a/include/linux/mtd/bbm.h
+++ b/include/linux/mtd/bbm.h
@@ -97,6 +97,13 @@ struct nand_bbt_descr {
*/
#define ONENAND_BADBLOCK_POS 0
+/*
+ * Bad block scanning errors
+ */
+#define ONENAND_BBT_READ_ERROR 1
+#define ONENAND_BBT_READ_ECC_ERROR 2
+#define ONENAND_BBT_READ_FATAL_ERROR 4
+
/**
* struct bbt_info - [GENERIC] Bad Block Table data structure
* @param bbt_erase_shift [INTERN] number of address bits in a bbt entry
diff --git a/include/linux/mtd/onenand.h b/include/linux/mtd/onenand.h
index 8a0fd0d..d73ea14 100644
--- a/include/linux/mtd/onenand.h
+++ b/include/linux/mtd/onenand.h
@@ -75,6 +75,7 @@ struct onenand_chip {
unsigned int page_shift;
unsigned int ppb_shift; /* Pages per block shift */
unsigned int page_mask;
+ unsigned int writesize;
unsigned int bufferram_index;
struct onenand_bufferram bufferram[MAX_BUFFERRAM];
@@ -96,22 +97,38 @@ struct onenand_chip {
spinlock_t chip_lock;
wait_queue_head_t wq;
int state;
+ unsigned char *page_buf;
+ unsigned char *oob_buf;
struct nand_oobinfo *autooob;
+ struct nand_ecclayout *ecclayout;
void *bbm;
void *priv;
};
+/*
+ * Helper macros
+ */
#define ONENAND_CURRENT_BUFFERRAM(this) (this->bufferram_index)
#define ONENAND_NEXT_BUFFERRAM(this) (this->bufferram_index ^ 1)
#define ONENAND_SET_NEXT_BUFFERRAM(this) (this->bufferram_index ^= 1)
+#define ONENAND_SET_PREV_BUFFERRAM(this) (this->bufferram_index ^= 1)
+#define ONENAND_SET_BUFFERRAM0(this) (this->bufferram_index = 0)
+#define ONENAND_SET_BUFFERRAM1(this) (this->bufferram_index = 1)
+
+#define ONENAND_IS_DDP(this) \
+ (this->device_id & ONENAND_DEVICE_IS_DDP)
+
+#define ONENAND_IS_2PLANE(this) (0)
/*
* Options bits
*/
#define ONENAND_CONT_LOCK (0x0001)
+#define ONENAND_PAGEBUF_ALLOC (0x1000)
+#define ONENAND_OOBBUF_ALLOC (0x2000)
/*
* OneNAND Flash Manufacturer ID Codes
@@ -129,4 +146,7 @@ struct onenand_manufacturers {
char *name;
};
+int onenand_bbt_read_oob(struct mtd_info *mtd, loff_t from,
+ struct mtd_oob_ops *ops);
+
#endif /* __LINUX_MTD_ONENAND_H */
diff --git a/include/linux/mtd/onenand_regs.h b/include/linux/mtd/onenand_regs.h
index c8a9f3e..6a8aa28 100644
--- a/include/linux/mtd/onenand_regs.h
+++ b/include/linux/mtd/onenand_regs.h
@@ -83,6 +83,8 @@
* Start Address 1 F100h (R/W)
*/
#define ONENAND_DDP_SHIFT (15)
+#define ONENAND_DDP_CHIP0 (0)
+#define ONENAND_DDP_CHIP1 (1 << ONENAND_DDP_SHIFT)
/*
* Start Address 8 F107h (R/W)
diff --git a/include/onenand_uboot.h b/include/onenand_uboot.h
index 4260ee7..2886a6f 100644
--- a/include/onenand_uboot.h
+++ b/include/onenand_uboot.h
@@ -15,24 +15,18 @@
#define __UBOOT_ONENAND_H
#include <linux/types.h>
-
-struct kvec {
- void *iov_base;
- size_t iov_len;
-};
-
-typedef int spinlock_t;
-typedef int wait_queue_head_t;
+#include <mtd_uboot.h>
struct mtd_info;
struct erase_info;
+extern struct mtd_info onenand_mtd;
+
/* Functions */
extern void onenand_init(void);
extern int onenand_read(struct mtd_info *mtd, loff_t from, size_t len,
size_t * retlen, u_char * buf);
-extern int onenand_read_oob(struct mtd_info *mtd, loff_t from, size_t len,
- size_t * retlen, u_char * buf);
+extern int onenand_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops);
extern int onenand_write(struct mtd_info *mtd, loff_t from, size_t len,
size_t * retlen, const u_char * buf);
extern int onenand_erase(struct mtd_info *mtd, struct erase_info *instr);
next reply other threads:[~2008-08-16 5:32 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2008-08-16 5:32 Kyungmin Park [this message]
2008-08-18 22:08 ` [U-Boot] [PATCH] Fix OneNAND read_oob/write_oob functions compatability Scott Wood
2008-08-18 23:07 ` Kyungmin Park
2008-08-18 23:27 ` Scott Wood
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=20080816053216.GA5081@july \
--to=kmpark@infradead.org \
--cc=u-boot@lists.denx.de \
/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.