[Linux-kernel] Advanced Sector Protectionの機能がついてるNORフラッシュのロック・アンロック対応

しょっぱなからマニアックな誰得投稿です。

LinuxでAdvanced Sector Protectionの機能がついてるNORフラッシュのロック・アンロック対応してないっぽいので最近のU-bootを見ながら書きました。

NORフラッシュにはいろいろロック形式はありますが、とりあえずSPBだけ対応してます。
これを当てるとmtd-utilsでNORのセクタロック・アンロックが出来るようになります。
つまり、U-boot上でロックされたセクタをアンロックしたり、その逆がLinux上のコマンドで出来るようになります。

もしかしたら最近のカーネルではすでに対応しているかもしれません。

（この記事は私が書いた https://gist.github.com/chromabox/5370360 の転載です。glistだと埋もれて自分が困るので、、、）

```c:cfi_ppb_lock.h
#ifndef PPB_LOCK_H
#define PPB_LOCK_H

#define AMD_CMD_SET_PPB_ENTRY           0xC0
#define AMD_CMD_SET_PPB_EXIT_BC1        0x90
#define AMD_CMD_SET_PPB_EXIT_BC2        0x00
#define AMD_CMD_PPB_UNLOCK_BC1          0x80
#define AMD_CMD_PPB_UNLOCK_BC2          0x30
#define AMD_CMD_PPB_LOCK_BC1            0xA0
#define AMD_CMD_PPB_LOCK_BC2            0x00

enum ppb_lock_state {
	PPB_LOCK = 0,
	PPB_UNLOCK = 1,
};

struct ppb_lock_thunk {
	enum ppb_lock_state val;
};

#define PPB_LOCK_ONEBLOCK_LOCK   ((struct ppb_lock_thunk){ PPB_LOCK })
#define PPB_LOCK_ONEBLOCK_UNLOCK ((struct ppb_lock_thunk){ PPB_UNLOCK })

static int __xipram chip_ready(struct map_info *map, unsigned long addr);

static int do_cfi_ppb_lock_oneblock(struct map_info *map, struct flchip *chip,
	unsigned long adr, int len, void *thunk)
{
	struct cfi_private *cfi = map->fldrv_priv;
	struct ppb_lock_thunk *th = (struct ppb_lock_thunk *)thunk;
	
	unsigned long timeo;
	int ret = 0;
	uint16_t lock_flag;
	
	DEBUG(MTD_DEBUG_LEVEL1,
		"do_cfi_ppb_lock_oneblock: start %08lX adr %08lX \n",chip->start,adr);
	adr += chip->start;

	mutex_lock(&chip->mutex);
	ret = get_chip(map, chip, adr, FL_LOCKING);
	if (ret) {
		mutex_unlock(&chip->mutex);
		return ret;
	}
	chip->oldstate = chip->state;
	chip->state = FL_LOCKING;
	
	DEBUG(MTD_DEBUG_LEVEL1,
		"do_cfi_ppb_lock_oneblock: adr %08lX\n",adr);
	
	// enter PPB mode
	cfi_send_gen_cmd(0xAA,					cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL);
	cfi_send_gen_cmd(0x55,					cfi->addr_unlock2, chip->start, map, cfi, cfi->device_type, NULL);	
	cfi_send_gen_cmd(AMD_CMD_SET_PPB_ENTRY,	cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL);
	
	lock_flag = cfi_read_query16(map,adr);
	DEBUG(MTD_DEBUG_LEVEL1,
		"do_cfi_ppb_lock_oneblock: lockflg=%d,%s\n",lock_flag,(lock_flag == PPB_UNLOCK) ? "Unlocked":"Locked");

	// Lock or Unlock
	if(th->val == PPB_LOCK){
		map_write(map, CMD(AMD_CMD_PPB_LOCK_BC1), adr);
		map_write(map, CMD(AMD_CMD_PPB_LOCK_BC2), adr);
	}else{
		// unlock is no sector spec
		cfi_send_gen_cmd(AMD_CMD_PPB_UNLOCK_BC1,0, chip->start, map, cfi, cfi->device_type, NULL);
		cfi_send_gen_cmd(AMD_CMD_PPB_UNLOCK_BC2,0, chip->start, map, cfi, cfi->device_type, NULL);
	}

	// wait...
	timeo = jiffies + (HZ*20);
	
	for(;;){
		if(chip_ready(map,adr)) break;
		
		if (time_after(jiffies, timeo)) {
			printk(KERN_WARNING "MTD %s(): software timeout\n",
				__func__ );
			break;
		}
		mutex_unlock(&chip->mutex);
		cfi_udelay(1000000/HZ);
		mutex_lock(&chip->mutex);
	}
	// exit PPB mode
	cfi_send_gen_cmd(AMD_CMD_SET_PPB_EXIT_BC1,0, chip->start, map, cfi, cfi->device_type, NULL);
	cfi_send_gen_cmd(AMD_CMD_SET_PPB_EXIT_BC2,0, chip->start, map, cfi, cfi->device_type, NULL);	
	
	chip->state = chip->oldstate;
	put_chip(map, chip, adr);
	mutex_unlock(&chip->mutex);
	return ret;
}


static int cfi_ppb_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
{
	int ret;

	ret = cfi_varsize_frob(mtd, do_cfi_ppb_lock_oneblock, ofs, len,
		(void *)&PPB_LOCK_ONEBLOCK_LOCK);

	return ret;
}


static int cfi_ppb_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
{
	int ret;

	ret = cfi_varsize_frob(mtd, do_cfi_ppb_lock_oneblock, ofs, len,
		(void *)&PPB_LOCK_ONEBLOCK_UNLOCK);

	return ret;
}


/* PBB fixup*/
static void cfi_fixup_ppb_unlock(struct cfi_pri_amdstd *extp,struct mtd_info *mtd)
{
	/* read sector protect/unprotect scheme is 0x08 to old scheme */
	if(extp->BlkProtUnprot != 8) return;
	
	printk(KERN_INFO "Advanced Sector Protection (PPB) enabled\n");
	printk(KERN_INFO "Use PPB lock/unlock method \n");
	mtd->lock   = cfi_ppb_lock;
	mtd->unlock = cfi_ppb_unlock;
}

#endif /* PPB_LOCK_H */
```

```diff:cfi_cmdset_0002.patch
diff --git a/drivers/mtd/chips/cfi_cmdset_0002.c b/drivers/mtd/chips/cfi_cmdset_0002
old mode 100644
new mode 100755
index d81079e..7ba6a65
--- a/drivers/mtd/chips/cfi_cmdset_0002.c
+++ b/drivers/mtd/chips/cfi_cmdset_0002.c
@@ -68,6 +68,7 @@ static struct mtd_info *cfi_amdstd_setup (struct mtd_info *);
 static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr, i
 static void put_chip(struct map_info *map, struct flchip *chip, unsigned long adr);
 #include "fwh_lock.h"
+#include "cfi_ppb_lock.h"
 
 static int cfi_atmel_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len);
 static int cfi_atmel_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len);
@@ -461,6 +462,9 @@ struct mtd_info *cfi_cmdset_0002(struct map_info *map, int prima
                        /* Set the default CFI lock/unlock addresses */
                        cfi->addr_unlock1 = 0x555;
                        cfi->addr_unlock2 = 0x2aa;
+                                               
+                       /* read sector protect/unprotect scheme is 0x08 to old schem
+                       cfi_fixup_ppb_unlock(extp,mtd);
                }
                cfi_fixup(mtd, cfi_nopri_fixup_table);
```