summaryrefslogtreecommitdiffstats
path: root/printlock.c
blob: f6eea7f469c5ca0872aabe8c8c4bbb50f3316b83 (plain)
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 */
	};
}