1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
|
/*
* This file is part of the flashrom project.
*
* Copyright (C) 2000 Silicon Integrated System Corporation
* Copyright (C) 2006 Giampiero Giancipoli <gianci@email.it>
* Copyright (C) 2006 coresystems GmbH <info@coresystems.de>
* Copyright (C) 2007-2012 Carl-Daniel Hailfinger
* Copyright (C) 2009 Sean Nelson <audiohacked@gmail.com>
* Copyright (C) 2014 Stefan Tauner
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*/
#include "flash.h"
#include "chipdrivers.h"
struct unlockblock {
unsigned int size;
unsigned int count;
};
typedef int (*unlockblock_func)(const struct flashctx *flash, chipaddr offset);
static int regspace2_walk_unlockblocks(const struct flashctx *flash, const struct unlockblock *block, unlockblock_func func)
{
chipaddr off = flash->virtual_registers + 2;
while (block->count != 0) {
unsigned int j;
for (j = 0; j < block->count; j++) {
if (func(flash, off))
return -1;
off += block->size;
}
block++;
}
return 0;
}
#define REG2_RWLOCK ((1 << 2) | (1 << 0))
#define REG2_LOCKDOWN (1 << 1)
#define REG2_MASK (REG2_RWLOCK | REG2_LOCKDOWN)
static int printlock_regspace2_block(const struct flashctx *flash, chipaddr lockreg)
{
uint8_t state = chip_readb(flash, lockreg);
msg_cdbg("Lock status of block at 0x%0*" PRIxPTR " is ", PRIxPTR_WIDTH, lockreg);
switch (state & REG2_MASK) {
case 0:
msg_cdbg("Full Access.\n");
break;
case 1:
msg_cdbg("Write Lock (Default State).\n");
break;
case 2:
msg_cdbg("Locked Open (Full Access, Locked Down).\n");
break;
case 3:
msg_cdbg("Write Lock, Locked Down.\n");
break;
case 4:
msg_cdbg("Read Lock.\n");
break;
case 5:
msg_cdbg("Read/Write Lock.\n");
break;
case 6:
msg_cdbg("Read Lock, Locked Down.\n");
break;
case 7:
msg_cdbg("Read/Write Lock, Locked Down.\n");
break;
}
return 0;
}
static int printlock_regspace2_uniform(struct flashctx *flash, unsigned long block_size)
{
const unsigned int elems = flash->chip->total_size * 1024 / block_size;
struct unlockblock blocks[2] = {{.size = block_size, .count = elems}};
return regspace2_walk_unlockblocks(flash, blocks, &printlock_regspace2_block);
}
int printlock_regspace2_uniform_64k(struct flashctx *flash)
{
return printlock_regspace2_uniform(flash, 64 * 1024);
}
int printlock_regspace2_block_eraser_0(struct flashctx *flash)
{
// FIXME: this depends on the eraseblocks not to be filled up completely (i.e. to be null-terminated).
const struct unlockblock *unlockblocks =
(const struct unlockblock *)flash->chip->block_erasers[0].eraseblocks;
return regspace2_walk_unlockblocks(flash, unlockblocks, &printlock_regspace2_block);
}
int printlock_regspace2_block_eraser_1(struct flashctx *flash)
{
// FIXME: this depends on the eraseblocks not to be filled up completely (i.e. to be null-terminated).
const struct unlockblock *unlockblocks =
(const struct unlockblock *)flash->chip->block_erasers[1].eraseblocks;
return regspace2_walk_unlockblocks(flash, unlockblocks, &printlock_regspace2_block);
}
/* Try to change the lock register at address lockreg from cur to new.
*
* - Try to unlock the lock bit if requested and it is currently set (although this is probably futile).
* - Try to change the read/write bits if requested.
* - Try to set the lockdown bit if requested.
* Return an error immediately if any of this fails. */
static int changelock_regspace2_block(const struct flashctx *flash, chipaddr lockreg, uint8_t cur, uint8_t new)
{
/* Only allow changes to known read/write/lockdown bits */
if (((cur ^ new) & ~REG2_MASK) != 0) {
msg_cerr("Invalid lock change from 0x%02x to 0x%02x requested at 0x%0*" PRIxPTR "!\n"
"Please report a bug at flashrom@flashrom.org\n",
cur, new, PRIxPTR_WIDTH, lockreg);
return -1;
}
/* Exit early if no change (of read/write/lockdown bits) was requested. */
if (((cur ^ new) & REG2_MASK) == 0) {
msg_cdbg2("Lock bits at 0x%0*" PRIxPTR " not changed.\n", PRIxPTR_WIDTH, lockreg);
return 0;
}
/* Normally the lockdown bit can not be cleared. Try nevertheless if requested. */
if ((cur & REG2_LOCKDOWN) && !(new & REG2_LOCKDOWN)) {
chip_writeb(flash, cur & ~REG2_LOCKDOWN, lockreg);
cur = chip_readb(flash, lockreg);
if ((cur & REG2_LOCKDOWN) == REG2_LOCKDOWN) {
msg_cwarn("Lockdown can't be removed at 0x%0*" PRIxPTR "! New value: 0x%02x.\n",
PRIxPTR_WIDTH, lockreg, cur);
return -1;
}
}
/* Change read and/or write bit */
if ((cur ^ new) & REG2_RWLOCK) {
/* Do not lockdown yet. */
uint8_t wanted = (cur & ~REG2_RWLOCK) | (new & REG2_RWLOCK);
chip_writeb(flash, wanted, lockreg);
cur = chip_readb(flash, lockreg);
if (cur != wanted) {
msg_cerr("Changing lock bits failed at 0x%0*" PRIxPTR "! New value: 0x%02x.\n",
PRIxPTR_WIDTH, lockreg, cur);
return -1;
}
msg_cdbg("Changed lock bits at 0x%0*" PRIxPTR " to 0x%02x.\n",
PRIxPTR_WIDTH, lockreg, cur);
}
/* Eventually, enable lockdown if requested. */
if (!(cur & REG2_LOCKDOWN) && (new & REG2_LOCKDOWN)) {
chip_writeb(flash, new, lockreg);
cur = chip_readb(flash, lockreg);
if (cur != new) {
msg_cerr("Enabling lockdown FAILED at 0x%0*" PRIxPTR "! New value: 0x%02x.\n",
PRIxPTR_WIDTH, lockreg, cur);
return -1;
}
msg_cdbg("Enabled lockdown at 0x%0*" PRIxPTR ".\n", PRIxPTR_WIDTH, lockreg);
}
return 0;
}
static int unlock_regspace2_block_generic(const struct flashctx *flash, chipaddr lockreg)
{
uint8_t old = chip_readb(flash, lockreg);
/* We don't care for the lockdown bit as long as the RW locks are 0 after we're done */
return changelock_regspace2_block(flash, lockreg, old, old & ~REG2_RWLOCK);
}
static int unlock_regspace2_uniform(struct flashctx *flash, unsigned long block_size)
{
const unsigned int elems = flash->chip->total_size * 1024 / block_size;
struct unlockblock blocks[2] = {{.size = block_size, .count = elems}};
return regspace2_walk_unlockblocks(flash, blocks, &unlock_regspace2_block_generic);
}
static int unlock_regspace2_uniform_64k(struct flashctx *flash)
{
return unlock_regspace2_uniform(flash, 64 * 1024);
}
static int unlock_regspace2_uniform_32k(struct flashctx *flash)
{
return unlock_regspace2_uniform(flash, 32 * 1024);
}
static int unlock_regspace2_block_eraser_0(struct flashctx *flash)
{
// FIXME: this depends on the eraseblocks not to be filled up completely (i.e. to be null-terminated).
const struct unlockblock *unlockblocks =
(const struct unlockblock *)flash->chip->block_erasers[0].eraseblocks;
return regspace2_walk_unlockblocks(flash, unlockblocks, &unlock_regspace2_block_generic);
}
static int unlock_regspace2_block_eraser_1(struct flashctx *flash)
{
// FIXME: this depends on the eraseblocks not to be filled up completely (i.e. to be null-terminated).
const struct unlockblock *unlockblocks =
(const struct unlockblock *)flash->chip->block_erasers[1].eraseblocks;
return regspace2_walk_unlockblocks(flash, unlockblocks, &unlock_regspace2_block_generic);
}
blockprotect_func_t *lookup_jedec_blockprotect_func_ptr(const struct flashchip *const chip)
{
switch (chip->unlock) {
case UNLOCK_REGSPACE2_BLOCK_ERASER_0: return unlock_regspace2_block_eraser_0;
case UNLOCK_REGSPACE2_BLOCK_ERASER_1: return unlock_regspace2_block_eraser_1;
case UNLOCK_REGSPACE2_UNIFORM_32K: return unlock_regspace2_uniform_32k;
case UNLOCK_REGSPACE2_UNIFORM_64K: return unlock_regspace2_uniform_64k;
default: return NULL; /* fallthough */
};
}
|