178c08247SWeiXiong Liao // SPDX-License-Identifier: GPL-2.0 278c08247SWeiXiong Liao 378c08247SWeiXiong Liao #define dev_fmt(fmt) "mtdoops-pstore: " fmt 478c08247SWeiXiong Liao 578c08247SWeiXiong Liao #include <linux/kernel.h> 678c08247SWeiXiong Liao #include <linux/module.h> 778c08247SWeiXiong Liao #include <linux/pstore_blk.h> 878c08247SWeiXiong Liao #include <linux/mtd/mtd.h> 978c08247SWeiXiong Liao #include <linux/bitops.h> 10*7999096fSHerbert Xu #include <linux/slab.h> 1178c08247SWeiXiong Liao 1278c08247SWeiXiong Liao static struct mtdpstore_context { 1378c08247SWeiXiong Liao int index; 1478c08247SWeiXiong Liao struct pstore_blk_config info; 1578c08247SWeiXiong Liao struct pstore_device_info dev; 1678c08247SWeiXiong Liao struct mtd_info *mtd; 1778c08247SWeiXiong Liao unsigned long *rmmap; /* removed bit map */ 1878c08247SWeiXiong Liao unsigned long *usedmap; /* used bit map */ 1978c08247SWeiXiong Liao /* 2078c08247SWeiXiong Liao * used for panic write 2178c08247SWeiXiong Liao * As there are no block_isbad for panic case, we should keep this 2278c08247SWeiXiong Liao * status before panic to ensure panic_write not failed. 2378c08247SWeiXiong Liao */ 2478c08247SWeiXiong Liao unsigned long *badmap; /* bad block bit map */ 2578c08247SWeiXiong Liao } oops_cxt; 2678c08247SWeiXiong Liao 2778c08247SWeiXiong Liao static int mtdpstore_block_isbad(struct mtdpstore_context *cxt, loff_t off) 2878c08247SWeiXiong Liao { 2978c08247SWeiXiong Liao int ret; 3078c08247SWeiXiong Liao struct mtd_info *mtd = cxt->mtd; 3178c08247SWeiXiong Liao u64 blknum; 3278c08247SWeiXiong Liao 3378c08247SWeiXiong Liao off = ALIGN_DOWN(off, mtd->erasesize); 3478c08247SWeiXiong Liao blknum = div_u64(off, mtd->erasesize); 3578c08247SWeiXiong Liao 3678c08247SWeiXiong Liao if (test_bit(blknum, cxt->badmap)) 3778c08247SWeiXiong Liao return true; 3878c08247SWeiXiong Liao ret = mtd_block_isbad(mtd, off); 3978c08247SWeiXiong Liao if (ret < 0) { 4078c08247SWeiXiong Liao dev_err(&mtd->dev, "mtd_block_isbad failed, aborting\n"); 4178c08247SWeiXiong Liao return ret; 4278c08247SWeiXiong Liao } else if (ret > 0) { 4378c08247SWeiXiong Liao set_bit(blknum, cxt->badmap); 4478c08247SWeiXiong Liao return true; 4578c08247SWeiXiong Liao } 4678c08247SWeiXiong Liao return false; 4778c08247SWeiXiong Liao } 4878c08247SWeiXiong Liao 4978c08247SWeiXiong Liao static inline int mtdpstore_panic_block_isbad(struct mtdpstore_context *cxt, 5078c08247SWeiXiong Liao loff_t off) 5178c08247SWeiXiong Liao { 5278c08247SWeiXiong Liao struct mtd_info *mtd = cxt->mtd; 5378c08247SWeiXiong Liao u64 blknum; 5478c08247SWeiXiong Liao 5578c08247SWeiXiong Liao off = ALIGN_DOWN(off, mtd->erasesize); 5678c08247SWeiXiong Liao blknum = div_u64(off, mtd->erasesize); 5778c08247SWeiXiong Liao return test_bit(blknum, cxt->badmap); 5878c08247SWeiXiong Liao } 5978c08247SWeiXiong Liao 6078c08247SWeiXiong Liao static inline void mtdpstore_mark_used(struct mtdpstore_context *cxt, 6178c08247SWeiXiong Liao loff_t off) 6278c08247SWeiXiong Liao { 6378c08247SWeiXiong Liao struct mtd_info *mtd = cxt->mtd; 6478c08247SWeiXiong Liao u64 zonenum = div_u64(off, cxt->info.kmsg_size); 6578c08247SWeiXiong Liao 6678c08247SWeiXiong Liao dev_dbg(&mtd->dev, "mark zone %llu used\n", zonenum); 6778c08247SWeiXiong Liao set_bit(zonenum, cxt->usedmap); 6878c08247SWeiXiong Liao } 6978c08247SWeiXiong Liao 7078c08247SWeiXiong Liao static inline void mtdpstore_mark_unused(struct mtdpstore_context *cxt, 7178c08247SWeiXiong Liao loff_t off) 7278c08247SWeiXiong Liao { 7378c08247SWeiXiong Liao struct mtd_info *mtd = cxt->mtd; 7478c08247SWeiXiong Liao u64 zonenum = div_u64(off, cxt->info.kmsg_size); 7578c08247SWeiXiong Liao 7678c08247SWeiXiong Liao dev_dbg(&mtd->dev, "mark zone %llu unused\n", zonenum); 7778c08247SWeiXiong Liao clear_bit(zonenum, cxt->usedmap); 7878c08247SWeiXiong Liao } 7978c08247SWeiXiong Liao 8078c08247SWeiXiong Liao static inline void mtdpstore_block_mark_unused(struct mtdpstore_context *cxt, 8178c08247SWeiXiong Liao loff_t off) 8278c08247SWeiXiong Liao { 8378c08247SWeiXiong Liao struct mtd_info *mtd = cxt->mtd; 8478c08247SWeiXiong Liao u32 zonecnt = mtd->erasesize / cxt->info.kmsg_size; 8578c08247SWeiXiong Liao u64 zonenum; 8678c08247SWeiXiong Liao 8778c08247SWeiXiong Liao off = ALIGN_DOWN(off, mtd->erasesize); 8878c08247SWeiXiong Liao zonenum = div_u64(off, cxt->info.kmsg_size); 8978c08247SWeiXiong Liao while (zonecnt > 0) { 9078c08247SWeiXiong Liao dev_dbg(&mtd->dev, "mark zone %llu unused\n", zonenum); 9178c08247SWeiXiong Liao clear_bit(zonenum, cxt->usedmap); 9278c08247SWeiXiong Liao zonenum++; 9378c08247SWeiXiong Liao zonecnt--; 9478c08247SWeiXiong Liao } 9578c08247SWeiXiong Liao } 9678c08247SWeiXiong Liao 9778c08247SWeiXiong Liao static inline int mtdpstore_is_used(struct mtdpstore_context *cxt, loff_t off) 9878c08247SWeiXiong Liao { 9978c08247SWeiXiong Liao u64 zonenum = div_u64(off, cxt->info.kmsg_size); 10078c08247SWeiXiong Liao u64 blknum = div_u64(off, cxt->mtd->erasesize); 10178c08247SWeiXiong Liao 10278c08247SWeiXiong Liao if (test_bit(blknum, cxt->badmap)) 10378c08247SWeiXiong Liao return true; 10478c08247SWeiXiong Liao return test_bit(zonenum, cxt->usedmap); 10578c08247SWeiXiong Liao } 10678c08247SWeiXiong Liao 10778c08247SWeiXiong Liao static int mtdpstore_block_is_used(struct mtdpstore_context *cxt, 10878c08247SWeiXiong Liao loff_t off) 10978c08247SWeiXiong Liao { 11078c08247SWeiXiong Liao struct mtd_info *mtd = cxt->mtd; 11178c08247SWeiXiong Liao u32 zonecnt = mtd->erasesize / cxt->info.kmsg_size; 11278c08247SWeiXiong Liao u64 zonenum; 11378c08247SWeiXiong Liao 11478c08247SWeiXiong Liao off = ALIGN_DOWN(off, mtd->erasesize); 11578c08247SWeiXiong Liao zonenum = div_u64(off, cxt->info.kmsg_size); 11678c08247SWeiXiong Liao while (zonecnt > 0) { 11778c08247SWeiXiong Liao if (test_bit(zonenum, cxt->usedmap)) 11878c08247SWeiXiong Liao return true; 11978c08247SWeiXiong Liao zonenum++; 12078c08247SWeiXiong Liao zonecnt--; 12178c08247SWeiXiong Liao } 12278c08247SWeiXiong Liao return false; 12378c08247SWeiXiong Liao } 12478c08247SWeiXiong Liao 12578c08247SWeiXiong Liao static int mtdpstore_is_empty(struct mtdpstore_context *cxt, char *buf, 12678c08247SWeiXiong Liao size_t size) 12778c08247SWeiXiong Liao { 12878c08247SWeiXiong Liao struct mtd_info *mtd = cxt->mtd; 12978c08247SWeiXiong Liao size_t sz; 13078c08247SWeiXiong Liao int i; 13178c08247SWeiXiong Liao 13278c08247SWeiXiong Liao sz = min_t(uint32_t, size, mtd->writesize / 4); 13378c08247SWeiXiong Liao for (i = 0; i < sz; i++) { 13478c08247SWeiXiong Liao if (buf[i] != (char)0xFF) 13578c08247SWeiXiong Liao return false; 13678c08247SWeiXiong Liao } 13778c08247SWeiXiong Liao return true; 13878c08247SWeiXiong Liao } 13978c08247SWeiXiong Liao 14078c08247SWeiXiong Liao static void mtdpstore_mark_removed(struct mtdpstore_context *cxt, loff_t off) 14178c08247SWeiXiong Liao { 14278c08247SWeiXiong Liao struct mtd_info *mtd = cxt->mtd; 14378c08247SWeiXiong Liao u64 zonenum = div_u64(off, cxt->info.kmsg_size); 14478c08247SWeiXiong Liao 14578c08247SWeiXiong Liao dev_dbg(&mtd->dev, "mark zone %llu removed\n", zonenum); 14678c08247SWeiXiong Liao set_bit(zonenum, cxt->rmmap); 14778c08247SWeiXiong Liao } 14878c08247SWeiXiong Liao 14978c08247SWeiXiong Liao static void mtdpstore_block_clear_removed(struct mtdpstore_context *cxt, 15078c08247SWeiXiong Liao loff_t off) 15178c08247SWeiXiong Liao { 15278c08247SWeiXiong Liao struct mtd_info *mtd = cxt->mtd; 15378c08247SWeiXiong Liao u32 zonecnt = mtd->erasesize / cxt->info.kmsg_size; 15478c08247SWeiXiong Liao u64 zonenum; 15578c08247SWeiXiong Liao 15678c08247SWeiXiong Liao off = ALIGN_DOWN(off, mtd->erasesize); 15778c08247SWeiXiong Liao zonenum = div_u64(off, cxt->info.kmsg_size); 15878c08247SWeiXiong Liao while (zonecnt > 0) { 15978c08247SWeiXiong Liao clear_bit(zonenum, cxt->rmmap); 16078c08247SWeiXiong Liao zonenum++; 16178c08247SWeiXiong Liao zonecnt--; 16278c08247SWeiXiong Liao } 16378c08247SWeiXiong Liao } 16478c08247SWeiXiong Liao 16578c08247SWeiXiong Liao static int mtdpstore_block_is_removed(struct mtdpstore_context *cxt, 16678c08247SWeiXiong Liao loff_t off) 16778c08247SWeiXiong Liao { 16878c08247SWeiXiong Liao struct mtd_info *mtd = cxt->mtd; 16978c08247SWeiXiong Liao u32 zonecnt = mtd->erasesize / cxt->info.kmsg_size; 17078c08247SWeiXiong Liao u64 zonenum; 17178c08247SWeiXiong Liao 17278c08247SWeiXiong Liao off = ALIGN_DOWN(off, mtd->erasesize); 17378c08247SWeiXiong Liao zonenum = div_u64(off, cxt->info.kmsg_size); 17478c08247SWeiXiong Liao while (zonecnt > 0) { 17578c08247SWeiXiong Liao if (test_bit(zonenum, cxt->rmmap)) 17678c08247SWeiXiong Liao return true; 17778c08247SWeiXiong Liao zonenum++; 17878c08247SWeiXiong Liao zonecnt--; 17978c08247SWeiXiong Liao } 18078c08247SWeiXiong Liao return false; 18178c08247SWeiXiong Liao } 18278c08247SWeiXiong Liao 18378c08247SWeiXiong Liao static int mtdpstore_erase_do(struct mtdpstore_context *cxt, loff_t off) 18478c08247SWeiXiong Liao { 18578c08247SWeiXiong Liao struct mtd_info *mtd = cxt->mtd; 18678c08247SWeiXiong Liao struct erase_info erase; 18778c08247SWeiXiong Liao int ret; 18878c08247SWeiXiong Liao 18978c08247SWeiXiong Liao off = ALIGN_DOWN(off, cxt->mtd->erasesize); 19078c08247SWeiXiong Liao dev_dbg(&mtd->dev, "try to erase off 0x%llx\n", off); 19178c08247SWeiXiong Liao erase.len = cxt->mtd->erasesize; 19278c08247SWeiXiong Liao erase.addr = off; 19378c08247SWeiXiong Liao ret = mtd_erase(cxt->mtd, &erase); 19478c08247SWeiXiong Liao if (!ret) 19578c08247SWeiXiong Liao mtdpstore_block_clear_removed(cxt, off); 19678c08247SWeiXiong Liao else 19778c08247SWeiXiong Liao dev_err(&mtd->dev, "erase of region [0x%llx, 0x%llx] on \"%s\" failed\n", 19878c08247SWeiXiong Liao (unsigned long long)erase.addr, 19978c08247SWeiXiong Liao (unsigned long long)erase.len, cxt->info.device); 20078c08247SWeiXiong Liao return ret; 20178c08247SWeiXiong Liao } 20278c08247SWeiXiong Liao 20378c08247SWeiXiong Liao /* 20478c08247SWeiXiong Liao * called while removing file 20578c08247SWeiXiong Liao * 20678c08247SWeiXiong Liao * Avoiding over erasing, do erase block only when the whole block is unused. 20778c08247SWeiXiong Liao * If the block contains valid log, do erase lazily on flush_removed() when 20878c08247SWeiXiong Liao * unregister. 20978c08247SWeiXiong Liao */ 21078c08247SWeiXiong Liao static ssize_t mtdpstore_erase(size_t size, loff_t off) 21178c08247SWeiXiong Liao { 21278c08247SWeiXiong Liao struct mtdpstore_context *cxt = &oops_cxt; 21378c08247SWeiXiong Liao 21478c08247SWeiXiong Liao if (mtdpstore_block_isbad(cxt, off)) 21578c08247SWeiXiong Liao return -EIO; 21678c08247SWeiXiong Liao 21778c08247SWeiXiong Liao mtdpstore_mark_unused(cxt, off); 21878c08247SWeiXiong Liao 21978c08247SWeiXiong Liao /* If the block still has valid data, mtdpstore do erase lazily */ 22078c08247SWeiXiong Liao if (likely(mtdpstore_block_is_used(cxt, off))) { 22178c08247SWeiXiong Liao mtdpstore_mark_removed(cxt, off); 22278c08247SWeiXiong Liao return 0; 22378c08247SWeiXiong Liao } 22478c08247SWeiXiong Liao 22578c08247SWeiXiong Liao /* all zones are unused, erase it */ 22678c08247SWeiXiong Liao return mtdpstore_erase_do(cxt, off); 22778c08247SWeiXiong Liao } 22878c08247SWeiXiong Liao 22978c08247SWeiXiong Liao /* 23078c08247SWeiXiong Liao * What is security for mtdpstore? 23178c08247SWeiXiong Liao * As there is no erase for panic case, we should ensure at least one zone 23278c08247SWeiXiong Liao * is writable. Otherwise, panic write will fail. 23378c08247SWeiXiong Liao * If zone is used, write operation will return -ENOMSG, which means that 23478c08247SWeiXiong Liao * pstore/blk will try one by one until gets an empty zone. So, it is not 23578c08247SWeiXiong Liao * needed to ensure the next zone is empty, but at least one. 23678c08247SWeiXiong Liao */ 23778c08247SWeiXiong Liao static int mtdpstore_security(struct mtdpstore_context *cxt, loff_t off) 23878c08247SWeiXiong Liao { 23978c08247SWeiXiong Liao int ret = 0, i; 24078c08247SWeiXiong Liao struct mtd_info *mtd = cxt->mtd; 24178c08247SWeiXiong Liao u32 zonenum = (u32)div_u64(off, cxt->info.kmsg_size); 24278c08247SWeiXiong Liao u32 zonecnt = (u32)div_u64(cxt->mtd->size, cxt->info.kmsg_size); 24378c08247SWeiXiong Liao u32 blkcnt = (u32)div_u64(cxt->mtd->size, cxt->mtd->erasesize); 24478c08247SWeiXiong Liao u32 erasesize = cxt->mtd->erasesize; 24578c08247SWeiXiong Liao 24678c08247SWeiXiong Liao for (i = 0; i < zonecnt; i++) { 24778c08247SWeiXiong Liao u32 num = (zonenum + i) % zonecnt; 24878c08247SWeiXiong Liao 24978c08247SWeiXiong Liao /* found empty zone */ 25078c08247SWeiXiong Liao if (!test_bit(num, cxt->usedmap)) 25178c08247SWeiXiong Liao return 0; 25278c08247SWeiXiong Liao } 25378c08247SWeiXiong Liao 25478c08247SWeiXiong Liao /* If there is no any empty zone, we have no way but to do erase */ 25578c08247SWeiXiong Liao while (blkcnt--) { 25678c08247SWeiXiong Liao div64_u64_rem(off + erasesize, cxt->mtd->size, (u64 *)&off); 25778c08247SWeiXiong Liao 25878c08247SWeiXiong Liao if (mtdpstore_block_isbad(cxt, off)) 25978c08247SWeiXiong Liao continue; 26078c08247SWeiXiong Liao 26178c08247SWeiXiong Liao ret = mtdpstore_erase_do(cxt, off); 26278c08247SWeiXiong Liao if (!ret) { 26378c08247SWeiXiong Liao mtdpstore_block_mark_unused(cxt, off); 26478c08247SWeiXiong Liao break; 26578c08247SWeiXiong Liao } 26678c08247SWeiXiong Liao } 26778c08247SWeiXiong Liao 26878c08247SWeiXiong Liao if (ret) 26978c08247SWeiXiong Liao dev_err(&mtd->dev, "all blocks bad!\n"); 27078c08247SWeiXiong Liao dev_dbg(&mtd->dev, "end security\n"); 27178c08247SWeiXiong Liao return ret; 27278c08247SWeiXiong Liao } 27378c08247SWeiXiong Liao 27478c08247SWeiXiong Liao static ssize_t mtdpstore_write(const char *buf, size_t size, loff_t off) 27578c08247SWeiXiong Liao { 27678c08247SWeiXiong Liao struct mtdpstore_context *cxt = &oops_cxt; 27778c08247SWeiXiong Liao struct mtd_info *mtd = cxt->mtd; 27878c08247SWeiXiong Liao size_t retlen; 27978c08247SWeiXiong Liao int ret; 28078c08247SWeiXiong Liao 28178c08247SWeiXiong Liao if (mtdpstore_block_isbad(cxt, off)) 28278c08247SWeiXiong Liao return -ENOMSG; 28378c08247SWeiXiong Liao 28478c08247SWeiXiong Liao /* zone is used, please try next one */ 28578c08247SWeiXiong Liao if (mtdpstore_is_used(cxt, off)) 28678c08247SWeiXiong Liao return -ENOMSG; 28778c08247SWeiXiong Liao 28878c08247SWeiXiong Liao dev_dbg(&mtd->dev, "try to write off 0x%llx size %zu\n", off, size); 28978c08247SWeiXiong Liao ret = mtd_write(cxt->mtd, off, size, &retlen, (u_char *)buf); 29078c08247SWeiXiong Liao if (ret < 0 || retlen != size) { 29178c08247SWeiXiong Liao dev_err(&mtd->dev, "write failure at %lld (%zu of %zu written), err %d\n", 29278c08247SWeiXiong Liao off, retlen, size, ret); 29378c08247SWeiXiong Liao return -EIO; 29478c08247SWeiXiong Liao } 29578c08247SWeiXiong Liao mtdpstore_mark_used(cxt, off); 29678c08247SWeiXiong Liao 29778c08247SWeiXiong Liao mtdpstore_security(cxt, off); 29878c08247SWeiXiong Liao return retlen; 29978c08247SWeiXiong Liao } 30078c08247SWeiXiong Liao 30178c08247SWeiXiong Liao static inline bool mtdpstore_is_io_error(int ret) 30278c08247SWeiXiong Liao { 30378c08247SWeiXiong Liao return ret < 0 && !mtd_is_bitflip(ret) && !mtd_is_eccerr(ret); 30478c08247SWeiXiong Liao } 30578c08247SWeiXiong Liao 30678c08247SWeiXiong Liao /* 30778c08247SWeiXiong Liao * All zones will be read as pstore/blk will read zone one by one when do 30878c08247SWeiXiong Liao * recover. 30978c08247SWeiXiong Liao */ 31078c08247SWeiXiong Liao static ssize_t mtdpstore_read(char *buf, size_t size, loff_t off) 31178c08247SWeiXiong Liao { 31278c08247SWeiXiong Liao struct mtdpstore_context *cxt = &oops_cxt; 31378c08247SWeiXiong Liao struct mtd_info *mtd = cxt->mtd; 31478c08247SWeiXiong Liao size_t retlen, done; 31578c08247SWeiXiong Liao int ret; 31678c08247SWeiXiong Liao 31778c08247SWeiXiong Liao if (mtdpstore_block_isbad(cxt, off)) 31878c08247SWeiXiong Liao return -ENOMSG; 31978c08247SWeiXiong Liao 32078c08247SWeiXiong Liao dev_dbg(&mtd->dev, "try to read off 0x%llx size %zu\n", off, size); 32178c08247SWeiXiong Liao for (done = 0, retlen = 0; done < size; done += retlen) { 32278c08247SWeiXiong Liao retlen = 0; 32378c08247SWeiXiong Liao 32478c08247SWeiXiong Liao ret = mtd_read(cxt->mtd, off + done, size - done, &retlen, 32578c08247SWeiXiong Liao (u_char *)buf + done); 32678c08247SWeiXiong Liao if (mtdpstore_is_io_error(ret)) { 32778c08247SWeiXiong Liao dev_err(&mtd->dev, "read failure at %lld (%zu of %zu read), err %d\n", 32878c08247SWeiXiong Liao off + done, retlen, size - done, ret); 32978c08247SWeiXiong Liao /* the zone may be broken, try next one */ 33078c08247SWeiXiong Liao return -ENOMSG; 33178c08247SWeiXiong Liao } 33278c08247SWeiXiong Liao 33378c08247SWeiXiong Liao /* 33478c08247SWeiXiong Liao * ECC error. The impact on log data is so small. Maybe we can 33578c08247SWeiXiong Liao * still read it and try to understand. So mtdpstore just hands 33678c08247SWeiXiong Liao * over what it gets and user can judge whether the data is 33778c08247SWeiXiong Liao * valid or not. 33878c08247SWeiXiong Liao */ 33978c08247SWeiXiong Liao if (mtd_is_eccerr(ret)) { 34078c08247SWeiXiong Liao dev_err(&mtd->dev, "ecc error at %lld (%zu of %zu read), err %d\n", 34178c08247SWeiXiong Liao off + done, retlen, size - done, ret); 34278c08247SWeiXiong Liao /* driver may not set retlen when ecc error */ 34378c08247SWeiXiong Liao retlen = retlen == 0 ? size - done : retlen; 34478c08247SWeiXiong Liao } 34578c08247SWeiXiong Liao } 34678c08247SWeiXiong Liao 34778c08247SWeiXiong Liao if (mtdpstore_is_empty(cxt, buf, size)) 34878c08247SWeiXiong Liao mtdpstore_mark_unused(cxt, off); 34978c08247SWeiXiong Liao else 35078c08247SWeiXiong Liao mtdpstore_mark_used(cxt, off); 35178c08247SWeiXiong Liao 35278c08247SWeiXiong Liao mtdpstore_security(cxt, off); 35378c08247SWeiXiong Liao return retlen; 35478c08247SWeiXiong Liao } 35578c08247SWeiXiong Liao 35678c08247SWeiXiong Liao static ssize_t mtdpstore_panic_write(const char *buf, size_t size, loff_t off) 35778c08247SWeiXiong Liao { 35878c08247SWeiXiong Liao struct mtdpstore_context *cxt = &oops_cxt; 35978c08247SWeiXiong Liao struct mtd_info *mtd = cxt->mtd; 36078c08247SWeiXiong Liao size_t retlen; 36178c08247SWeiXiong Liao int ret; 36278c08247SWeiXiong Liao 36378c08247SWeiXiong Liao if (mtdpstore_panic_block_isbad(cxt, off)) 36478c08247SWeiXiong Liao return -ENOMSG; 36578c08247SWeiXiong Liao 36678c08247SWeiXiong Liao /* zone is used, please try next one */ 36778c08247SWeiXiong Liao if (mtdpstore_is_used(cxt, off)) 36878c08247SWeiXiong Liao return -ENOMSG; 36978c08247SWeiXiong Liao 37078c08247SWeiXiong Liao ret = mtd_panic_write(cxt->mtd, off, size, &retlen, (u_char *)buf); 37178c08247SWeiXiong Liao if (ret < 0 || size != retlen) { 37278c08247SWeiXiong Liao dev_err(&mtd->dev, "panic write failure at %lld (%zu of %zu read), err %d\n", 37378c08247SWeiXiong Liao off, retlen, size, ret); 37478c08247SWeiXiong Liao return -EIO; 37578c08247SWeiXiong Liao } 37678c08247SWeiXiong Liao mtdpstore_mark_used(cxt, off); 37778c08247SWeiXiong Liao 37878c08247SWeiXiong Liao return retlen; 37978c08247SWeiXiong Liao } 38078c08247SWeiXiong Liao 38178c08247SWeiXiong Liao static void mtdpstore_notify_add(struct mtd_info *mtd) 38278c08247SWeiXiong Liao { 38378c08247SWeiXiong Liao int ret; 38478c08247SWeiXiong Liao struct mtdpstore_context *cxt = &oops_cxt; 38578c08247SWeiXiong Liao struct pstore_blk_config *info = &cxt->info; 38678c08247SWeiXiong Liao unsigned long longcnt; 38778c08247SWeiXiong Liao 38878c08247SWeiXiong Liao if (!strcmp(mtd->name, info->device)) 38978c08247SWeiXiong Liao cxt->index = mtd->index; 39078c08247SWeiXiong Liao 39178c08247SWeiXiong Liao if (mtd->index != cxt->index || cxt->index < 0) 39278c08247SWeiXiong Liao return; 39378c08247SWeiXiong Liao 39478c08247SWeiXiong Liao dev_dbg(&mtd->dev, "found matching MTD device %s\n", mtd->name); 39578c08247SWeiXiong Liao 39678c08247SWeiXiong Liao if (mtd->size < info->kmsg_size * 2) { 39778c08247SWeiXiong Liao dev_err(&mtd->dev, "MTD partition %d not big enough\n", 39878c08247SWeiXiong Liao mtd->index); 39978c08247SWeiXiong Liao return; 40078c08247SWeiXiong Liao } 40178c08247SWeiXiong Liao /* 40278c08247SWeiXiong Liao * kmsg_size must be aligned to 4096 Bytes, which is limited by 40378c08247SWeiXiong Liao * psblk. The default value of kmsg_size is 64KB. If kmsg_size 40478c08247SWeiXiong Liao * is larger than erasesize, some errors will occur since mtdpsotre 40578c08247SWeiXiong Liao * is designed on it. 40678c08247SWeiXiong Liao */ 40778c08247SWeiXiong Liao if (mtd->erasesize < info->kmsg_size) { 40878c08247SWeiXiong Liao dev_err(&mtd->dev, "eraseblock size of MTD partition %d too small\n", 40978c08247SWeiXiong Liao mtd->index); 41078c08247SWeiXiong Liao return; 41178c08247SWeiXiong Liao } 41278c08247SWeiXiong Liao if (unlikely(info->kmsg_size % mtd->writesize)) { 41378c08247SWeiXiong Liao dev_err(&mtd->dev, "record size %lu KB must align to write size %d KB\n", 41478c08247SWeiXiong Liao info->kmsg_size / 1024, 41578c08247SWeiXiong Liao mtd->writesize / 1024); 41678c08247SWeiXiong Liao return; 41778c08247SWeiXiong Liao } 41878c08247SWeiXiong Liao 41978c08247SWeiXiong Liao longcnt = BITS_TO_LONGS(div_u64(mtd->size, info->kmsg_size)); 42078c08247SWeiXiong Liao cxt->rmmap = kcalloc(longcnt, sizeof(long), GFP_KERNEL); 42178c08247SWeiXiong Liao cxt->usedmap = kcalloc(longcnt, sizeof(long), GFP_KERNEL); 42278c08247SWeiXiong Liao 42378c08247SWeiXiong Liao longcnt = BITS_TO_LONGS(div_u64(mtd->size, mtd->erasesize)); 42478c08247SWeiXiong Liao cxt->badmap = kcalloc(longcnt, sizeof(long), GFP_KERNEL); 42578c08247SWeiXiong Liao 42678c08247SWeiXiong Liao cxt->dev.total_size = mtd->size; 42778c08247SWeiXiong Liao /* just support dmesg right now */ 42878c08247SWeiXiong Liao cxt->dev.flags = PSTORE_FLAGS_DMESG; 42978c08247SWeiXiong Liao cxt->dev.read = mtdpstore_read; 43078c08247SWeiXiong Liao cxt->dev.write = mtdpstore_write; 43178c08247SWeiXiong Liao cxt->dev.erase = mtdpstore_erase; 43278c08247SWeiXiong Liao cxt->dev.panic_write = mtdpstore_panic_write; 43378c08247SWeiXiong Liao 43478c08247SWeiXiong Liao ret = register_pstore_device(&cxt->dev); 43578c08247SWeiXiong Liao if (ret) { 43678c08247SWeiXiong Liao dev_err(&mtd->dev, "mtd%d register to psblk failed\n", 43778c08247SWeiXiong Liao mtd->index); 43878c08247SWeiXiong Liao return; 43978c08247SWeiXiong Liao } 44078c08247SWeiXiong Liao cxt->mtd = mtd; 44178c08247SWeiXiong Liao dev_info(&mtd->dev, "Attached to MTD device %d\n", mtd->index); 44278c08247SWeiXiong Liao } 44378c08247SWeiXiong Liao 44478c08247SWeiXiong Liao static int mtdpstore_flush_removed_do(struct mtdpstore_context *cxt, 44578c08247SWeiXiong Liao loff_t off, size_t size) 44678c08247SWeiXiong Liao { 44778c08247SWeiXiong Liao struct mtd_info *mtd = cxt->mtd; 44878c08247SWeiXiong Liao u_char *buf; 44978c08247SWeiXiong Liao int ret; 45078c08247SWeiXiong Liao size_t retlen; 45178c08247SWeiXiong Liao struct erase_info erase; 45278c08247SWeiXiong Liao 45378c08247SWeiXiong Liao buf = kmalloc(mtd->erasesize, GFP_KERNEL); 45478c08247SWeiXiong Liao if (!buf) 45578c08247SWeiXiong Liao return -ENOMEM; 45678c08247SWeiXiong Liao 45778c08247SWeiXiong Liao /* 1st. read to cache */ 45878c08247SWeiXiong Liao ret = mtd_read(mtd, off, mtd->erasesize, &retlen, buf); 45978c08247SWeiXiong Liao if (mtdpstore_is_io_error(ret)) 46078c08247SWeiXiong Liao goto free; 46178c08247SWeiXiong Liao 46278c08247SWeiXiong Liao /* 2nd. erase block */ 46378c08247SWeiXiong Liao erase.len = mtd->erasesize; 46478c08247SWeiXiong Liao erase.addr = off; 46578c08247SWeiXiong Liao ret = mtd_erase(mtd, &erase); 46678c08247SWeiXiong Liao if (ret) 46778c08247SWeiXiong Liao goto free; 46878c08247SWeiXiong Liao 46978c08247SWeiXiong Liao /* 3rd. write back */ 47078c08247SWeiXiong Liao while (size) { 47178c08247SWeiXiong Liao unsigned int zonesize = cxt->info.kmsg_size; 47278c08247SWeiXiong Liao 47378c08247SWeiXiong Liao /* there is valid data on block, write back */ 47478c08247SWeiXiong Liao if (mtdpstore_is_used(cxt, off)) { 47578c08247SWeiXiong Liao ret = mtd_write(mtd, off, zonesize, &retlen, buf); 47678c08247SWeiXiong Liao if (ret) 47778c08247SWeiXiong Liao dev_err(&mtd->dev, "write failure at %lld (%zu of %u written), err %d\n", 47878c08247SWeiXiong Liao off, retlen, zonesize, ret); 47978c08247SWeiXiong Liao } 48078c08247SWeiXiong Liao 48178c08247SWeiXiong Liao off += zonesize; 48278c08247SWeiXiong Liao size -= min_t(unsigned int, zonesize, size); 48378c08247SWeiXiong Liao } 48478c08247SWeiXiong Liao 48578c08247SWeiXiong Liao free: 48678c08247SWeiXiong Liao kfree(buf); 48778c08247SWeiXiong Liao return ret; 48878c08247SWeiXiong Liao } 48978c08247SWeiXiong Liao 49078c08247SWeiXiong Liao /* 49178c08247SWeiXiong Liao * What does mtdpstore_flush_removed() do? 49278c08247SWeiXiong Liao * When user remove any log file on pstore filesystem, mtdpstore should do 49378c08247SWeiXiong Liao * something to ensure log file removed. If the whole block is no longer used, 49478c08247SWeiXiong Liao * it's nice to erase the block. However if the block still contains valid log, 49578c08247SWeiXiong Liao * what mtdpstore can do is to erase and write the valid log back. 49678c08247SWeiXiong Liao */ 49778c08247SWeiXiong Liao static int mtdpstore_flush_removed(struct mtdpstore_context *cxt) 49878c08247SWeiXiong Liao { 49978c08247SWeiXiong Liao struct mtd_info *mtd = cxt->mtd; 50078c08247SWeiXiong Liao int ret; 50178c08247SWeiXiong Liao loff_t off; 50278c08247SWeiXiong Liao u32 blkcnt = (u32)div_u64(mtd->size, mtd->erasesize); 50378c08247SWeiXiong Liao 50478c08247SWeiXiong Liao for (off = 0; blkcnt > 0; blkcnt--, off += mtd->erasesize) { 50578c08247SWeiXiong Liao ret = mtdpstore_block_isbad(cxt, off); 50678c08247SWeiXiong Liao if (ret) 50778c08247SWeiXiong Liao continue; 50878c08247SWeiXiong Liao 50978c08247SWeiXiong Liao ret = mtdpstore_block_is_removed(cxt, off); 51078c08247SWeiXiong Liao if (!ret) 51178c08247SWeiXiong Liao continue; 51278c08247SWeiXiong Liao 51378c08247SWeiXiong Liao ret = mtdpstore_flush_removed_do(cxt, off, mtd->erasesize); 51478c08247SWeiXiong Liao if (ret) 51578c08247SWeiXiong Liao return ret; 51678c08247SWeiXiong Liao } 51778c08247SWeiXiong Liao return 0; 51878c08247SWeiXiong Liao } 51978c08247SWeiXiong Liao 52078c08247SWeiXiong Liao static void mtdpstore_notify_remove(struct mtd_info *mtd) 52178c08247SWeiXiong Liao { 52278c08247SWeiXiong Liao struct mtdpstore_context *cxt = &oops_cxt; 52378c08247SWeiXiong Liao 52478c08247SWeiXiong Liao if (mtd->index != cxt->index || cxt->index < 0) 52578c08247SWeiXiong Liao return; 52678c08247SWeiXiong Liao 52778c08247SWeiXiong Liao mtdpstore_flush_removed(cxt); 52878c08247SWeiXiong Liao 52978c08247SWeiXiong Liao unregister_pstore_device(&cxt->dev); 53078c08247SWeiXiong Liao kfree(cxt->badmap); 53178c08247SWeiXiong Liao kfree(cxt->usedmap); 53278c08247SWeiXiong Liao kfree(cxt->rmmap); 53378c08247SWeiXiong Liao cxt->mtd = NULL; 53478c08247SWeiXiong Liao cxt->index = -1; 53578c08247SWeiXiong Liao } 53678c08247SWeiXiong Liao 53778c08247SWeiXiong Liao static struct mtd_notifier mtdpstore_notifier = { 53878c08247SWeiXiong Liao .add = mtdpstore_notify_add, 53978c08247SWeiXiong Liao .remove = mtdpstore_notify_remove, 54078c08247SWeiXiong Liao }; 54178c08247SWeiXiong Liao 54278c08247SWeiXiong Liao static int __init mtdpstore_init(void) 54378c08247SWeiXiong Liao { 54478c08247SWeiXiong Liao int ret; 54578c08247SWeiXiong Liao struct mtdpstore_context *cxt = &oops_cxt; 54678c08247SWeiXiong Liao struct pstore_blk_config *info = &cxt->info; 54778c08247SWeiXiong Liao 54878c08247SWeiXiong Liao ret = pstore_blk_get_config(info); 54978c08247SWeiXiong Liao if (unlikely(ret)) 55078c08247SWeiXiong Liao return ret; 55178c08247SWeiXiong Liao 55278c08247SWeiXiong Liao if (strlen(info->device) == 0) { 55378c08247SWeiXiong Liao pr_err("mtd device must be supplied (device name is empty)\n"); 55478c08247SWeiXiong Liao return -EINVAL; 55578c08247SWeiXiong Liao } 55678c08247SWeiXiong Liao if (!info->kmsg_size) { 55778c08247SWeiXiong Liao pr_err("no backend enabled (kmsg_size is 0)\n"); 55878c08247SWeiXiong Liao return -EINVAL; 55978c08247SWeiXiong Liao } 56078c08247SWeiXiong Liao 56178c08247SWeiXiong Liao /* Setup the MTD device to use */ 56278c08247SWeiXiong Liao ret = kstrtoint((char *)info->device, 0, &cxt->index); 56378c08247SWeiXiong Liao if (ret) 56478c08247SWeiXiong Liao cxt->index = -1; 56578c08247SWeiXiong Liao 56678c08247SWeiXiong Liao register_mtd_user(&mtdpstore_notifier); 56778c08247SWeiXiong Liao return 0; 56878c08247SWeiXiong Liao } 56978c08247SWeiXiong Liao module_init(mtdpstore_init); 57078c08247SWeiXiong Liao 57178c08247SWeiXiong Liao static void __exit mtdpstore_exit(void) 57278c08247SWeiXiong Liao { 57378c08247SWeiXiong Liao unregister_mtd_user(&mtdpstore_notifier); 57478c08247SWeiXiong Liao } 57578c08247SWeiXiong Liao module_exit(mtdpstore_exit); 57678c08247SWeiXiong Liao 57778c08247SWeiXiong Liao MODULE_LICENSE("GPL"); 57878c08247SWeiXiong Liao MODULE_AUTHOR("WeiXiong Liao <liaoweixiong@allwinnertech.com>"); 57978c08247SWeiXiong Liao MODULE_DESCRIPTION("MTD backend for pstore/blk"); 580