| Huang Ying | a08f82d | 2010-05-18 14:35:21 +0800 | [diff] [blame] | 1 | /* | 
 | 2 |  * APEI Error Record Serialization Table support | 
 | 3 |  * | 
 | 4 |  * ERST is a way provided by APEI to save and retrieve hardware error | 
| Lucas De Marchi | 58f87ed | 2010-09-07 12:49:45 -0400 | [diff] [blame] | 5 |  * information to and from a persistent store. | 
| Huang Ying | a08f82d | 2010-05-18 14:35:21 +0800 | [diff] [blame] | 6 |  * | 
 | 7 |  * For more information about ERST, please refer to ACPI Specification | 
 | 8 |  * version 4.0, section 17.4. | 
 | 9 |  * | 
 | 10 |  * Copyright 2010 Intel Corp. | 
 | 11 |  *   Author: Huang Ying <ying.huang@intel.com> | 
 | 12 |  * | 
 | 13 |  * This program is free software; you can redistribute it and/or | 
 | 14 |  * modify it under the terms of the GNU General Public License version | 
 | 15 |  * 2 as published by the Free Software Foundation. | 
 | 16 |  * | 
 | 17 |  * This program is distributed in the hope that it will be useful, | 
 | 18 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 | 19 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
 | 20 |  * GNU General Public License for more details. | 
 | 21 |  * | 
 | 22 |  * You should have received a copy of the GNU General Public License | 
 | 23 |  * along with this program; if not, write to the Free Software | 
 | 24 |  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | 
 | 25 |  */ | 
 | 26 |  | 
 | 27 | #include <linux/kernel.h> | 
 | 28 | #include <linux/module.h> | 
 | 29 | #include <linux/init.h> | 
 | 30 | #include <linux/delay.h> | 
 | 31 | #include <linux/io.h> | 
 | 32 | #include <linux/acpi.h> | 
 | 33 | #include <linux/uaccess.h> | 
 | 34 | #include <linux/cper.h> | 
 | 35 | #include <linux/nmi.h> | 
| Thomas Gleixner | 0a7992c | 2010-08-11 14:17:29 -0700 | [diff] [blame] | 36 | #include <linux/hardirq.h> | 
| Tony Luck | 0bb77c4 | 2011-01-03 14:22:11 -0800 | [diff] [blame] | 37 | #include <linux/pstore.h> | 
| Huang Ying | a08f82d | 2010-05-18 14:35:21 +0800 | [diff] [blame] | 38 | #include <acpi/apei.h> | 
 | 39 |  | 
 | 40 | #include "apei-internal.h" | 
 | 41 |  | 
 | 42 | #define ERST_PFX "ERST: " | 
 | 43 |  | 
 | 44 | /* ERST command status */ | 
 | 45 | #define ERST_STATUS_SUCCESS			0x0 | 
 | 46 | #define ERST_STATUS_NOT_ENOUGH_SPACE		0x1 | 
 | 47 | #define ERST_STATUS_HARDWARE_NOT_AVAILABLE	0x2 | 
 | 48 | #define ERST_STATUS_FAILED			0x3 | 
 | 49 | #define ERST_STATUS_RECORD_STORE_EMPTY		0x4 | 
 | 50 | #define ERST_STATUS_RECORD_NOT_FOUND		0x5 | 
 | 51 |  | 
 | 52 | #define ERST_TAB_ENTRY(tab)						\ | 
 | 53 | 	((struct acpi_whea_header *)((char *)(tab) +			\ | 
 | 54 | 				     sizeof(struct acpi_table_erst))) | 
 | 55 |  | 
 | 56 | #define SPIN_UNIT		100			/* 100ns */ | 
| Stefan Weil | e8a8b25 | 2011-01-02 15:12:42 +0100 | [diff] [blame] | 57 | /* Firmware should respond within 1 milliseconds */ | 
| Huang Ying | a08f82d | 2010-05-18 14:35:21 +0800 | [diff] [blame] | 58 | #define FIRMWARE_TIMEOUT	(1 * NSEC_PER_MSEC) | 
 | 59 | #define FIRMWARE_MAX_STALL	50			/* 50us */ | 
 | 60 |  | 
 | 61 | int erst_disable; | 
 | 62 | EXPORT_SYMBOL_GPL(erst_disable); | 
 | 63 |  | 
 | 64 | static struct acpi_table_erst *erst_tab; | 
 | 65 |  | 
 | 66 | /* ERST Error Log Address Range atrributes */ | 
 | 67 | #define ERST_RANGE_RESERVED	0x0001 | 
 | 68 | #define ERST_RANGE_NVRAM	0x0002 | 
 | 69 | #define ERST_RANGE_SLOW		0x0004 | 
 | 70 |  | 
 | 71 | /* | 
 | 72 |  * ERST Error Log Address Range, used as buffer for reading/writing | 
 | 73 |  * error records. | 
 | 74 |  */ | 
 | 75 | static struct erst_erange { | 
 | 76 | 	u64 base; | 
 | 77 | 	u64 size; | 
 | 78 | 	void __iomem *vaddr; | 
 | 79 | 	u32 attr; | 
 | 80 | } erst_erange; | 
 | 81 |  | 
 | 82 | /* | 
 | 83 |  * Prevent ERST interpreter to run simultaneously, because the | 
 | 84 |  * corresponding firmware implementation may not work properly when | 
 | 85 |  * invoked simultaneously. | 
 | 86 |  * | 
 | 87 |  * It is used to provide exclusive accessing for ERST Error Log | 
 | 88 |  * Address Range too. | 
 | 89 |  */ | 
| Huang Ying | 3b38bb5 | 2010-12-02 10:40:53 +0800 | [diff] [blame] | 90 | static DEFINE_RAW_SPINLOCK(erst_lock); | 
| Huang Ying | a08f82d | 2010-05-18 14:35:21 +0800 | [diff] [blame] | 91 |  | 
 | 92 | static inline int erst_errno(int command_status) | 
 | 93 | { | 
 | 94 | 	switch (command_status) { | 
 | 95 | 	case ERST_STATUS_SUCCESS: | 
 | 96 | 		return 0; | 
 | 97 | 	case ERST_STATUS_HARDWARE_NOT_AVAILABLE: | 
 | 98 | 		return -ENODEV; | 
 | 99 | 	case ERST_STATUS_NOT_ENOUGH_SPACE: | 
 | 100 | 		return -ENOSPC; | 
 | 101 | 	case ERST_STATUS_RECORD_STORE_EMPTY: | 
 | 102 | 	case ERST_STATUS_RECORD_NOT_FOUND: | 
 | 103 | 		return -ENOENT; | 
 | 104 | 	default: | 
 | 105 | 		return -EINVAL; | 
 | 106 | 	} | 
 | 107 | } | 
 | 108 |  | 
 | 109 | static int erst_timedout(u64 *t, u64 spin_unit) | 
 | 110 | { | 
 | 111 | 	if ((s64)*t < spin_unit) { | 
 | 112 | 		pr_warning(FW_WARN ERST_PFX | 
 | 113 | 			   "Firmware does not respond in time\n"); | 
 | 114 | 		return 1; | 
 | 115 | 	} | 
 | 116 | 	*t -= spin_unit; | 
 | 117 | 	ndelay(spin_unit); | 
 | 118 | 	touch_nmi_watchdog(); | 
 | 119 | 	return 0; | 
 | 120 | } | 
 | 121 |  | 
 | 122 | static int erst_exec_load_var1(struct apei_exec_context *ctx, | 
 | 123 | 			       struct acpi_whea_header *entry) | 
 | 124 | { | 
 | 125 | 	return __apei_exec_read_register(entry, &ctx->var1); | 
 | 126 | } | 
 | 127 |  | 
 | 128 | static int erst_exec_load_var2(struct apei_exec_context *ctx, | 
 | 129 | 			       struct acpi_whea_header *entry) | 
 | 130 | { | 
 | 131 | 	return __apei_exec_read_register(entry, &ctx->var2); | 
 | 132 | } | 
 | 133 |  | 
 | 134 | static int erst_exec_store_var1(struct apei_exec_context *ctx, | 
 | 135 | 				struct acpi_whea_header *entry) | 
 | 136 | { | 
 | 137 | 	return __apei_exec_write_register(entry, ctx->var1); | 
 | 138 | } | 
 | 139 |  | 
 | 140 | static int erst_exec_add(struct apei_exec_context *ctx, | 
 | 141 | 			 struct acpi_whea_header *entry) | 
 | 142 | { | 
 | 143 | 	ctx->var1 += ctx->var2; | 
 | 144 | 	return 0; | 
 | 145 | } | 
 | 146 |  | 
 | 147 | static int erst_exec_subtract(struct apei_exec_context *ctx, | 
 | 148 | 			      struct acpi_whea_header *entry) | 
 | 149 | { | 
 | 150 | 	ctx->var1 -= ctx->var2; | 
 | 151 | 	return 0; | 
 | 152 | } | 
 | 153 |  | 
 | 154 | static int erst_exec_add_value(struct apei_exec_context *ctx, | 
 | 155 | 			       struct acpi_whea_header *entry) | 
 | 156 | { | 
 | 157 | 	int rc; | 
 | 158 | 	u64 val; | 
 | 159 |  | 
 | 160 | 	rc = __apei_exec_read_register(entry, &val); | 
 | 161 | 	if (rc) | 
 | 162 | 		return rc; | 
 | 163 | 	val += ctx->value; | 
 | 164 | 	rc = __apei_exec_write_register(entry, val); | 
 | 165 | 	return rc; | 
 | 166 | } | 
 | 167 |  | 
 | 168 | static int erst_exec_subtract_value(struct apei_exec_context *ctx, | 
 | 169 | 				    struct acpi_whea_header *entry) | 
 | 170 | { | 
 | 171 | 	int rc; | 
 | 172 | 	u64 val; | 
 | 173 |  | 
 | 174 | 	rc = __apei_exec_read_register(entry, &val); | 
 | 175 | 	if (rc) | 
 | 176 | 		return rc; | 
 | 177 | 	val -= ctx->value; | 
 | 178 | 	rc = __apei_exec_write_register(entry, val); | 
 | 179 | 	return rc; | 
 | 180 | } | 
 | 181 |  | 
 | 182 | static int erst_exec_stall(struct apei_exec_context *ctx, | 
 | 183 | 			   struct acpi_whea_header *entry) | 
 | 184 | { | 
 | 185 | 	u64 stall_time; | 
 | 186 |  | 
 | 187 | 	if (ctx->value > FIRMWARE_MAX_STALL) { | 
 | 188 | 		if (!in_nmi()) | 
 | 189 | 			pr_warning(FW_WARN ERST_PFX | 
 | 190 | 			"Too long stall time for stall instruction: %llx.\n", | 
 | 191 | 				   ctx->value); | 
 | 192 | 		stall_time = FIRMWARE_MAX_STALL; | 
 | 193 | 	} else | 
 | 194 | 		stall_time = ctx->value; | 
 | 195 | 	udelay(stall_time); | 
 | 196 | 	return 0; | 
 | 197 | } | 
 | 198 |  | 
 | 199 | static int erst_exec_stall_while_true(struct apei_exec_context *ctx, | 
 | 200 | 				      struct acpi_whea_header *entry) | 
 | 201 | { | 
 | 202 | 	int rc; | 
 | 203 | 	u64 val; | 
 | 204 | 	u64 timeout = FIRMWARE_TIMEOUT; | 
 | 205 | 	u64 stall_time; | 
 | 206 |  | 
 | 207 | 	if (ctx->var1 > FIRMWARE_MAX_STALL) { | 
 | 208 | 		if (!in_nmi()) | 
 | 209 | 			pr_warning(FW_WARN ERST_PFX | 
 | 210 | 		"Too long stall time for stall while true instruction: %llx.\n", | 
 | 211 | 				   ctx->var1); | 
 | 212 | 		stall_time = FIRMWARE_MAX_STALL; | 
 | 213 | 	} else | 
 | 214 | 		stall_time = ctx->var1; | 
 | 215 |  | 
 | 216 | 	for (;;) { | 
 | 217 | 		rc = __apei_exec_read_register(entry, &val); | 
 | 218 | 		if (rc) | 
 | 219 | 			return rc; | 
 | 220 | 		if (val != ctx->value) | 
 | 221 | 			break; | 
 | 222 | 		if (erst_timedout(&timeout, stall_time * NSEC_PER_USEC)) | 
 | 223 | 			return -EIO; | 
 | 224 | 	} | 
 | 225 | 	return 0; | 
 | 226 | } | 
 | 227 |  | 
 | 228 | static int erst_exec_skip_next_instruction_if_true( | 
 | 229 | 	struct apei_exec_context *ctx, | 
 | 230 | 	struct acpi_whea_header *entry) | 
 | 231 | { | 
 | 232 | 	int rc; | 
 | 233 | 	u64 val; | 
 | 234 |  | 
 | 235 | 	rc = __apei_exec_read_register(entry, &val); | 
 | 236 | 	if (rc) | 
 | 237 | 		return rc; | 
 | 238 | 	if (val == ctx->value) { | 
 | 239 | 		ctx->ip += 2; | 
 | 240 | 		return APEI_EXEC_SET_IP; | 
 | 241 | 	} | 
 | 242 |  | 
 | 243 | 	return 0; | 
 | 244 | } | 
 | 245 |  | 
 | 246 | static int erst_exec_goto(struct apei_exec_context *ctx, | 
 | 247 | 			  struct acpi_whea_header *entry) | 
 | 248 | { | 
 | 249 | 	ctx->ip = ctx->value; | 
 | 250 | 	return APEI_EXEC_SET_IP; | 
 | 251 | } | 
 | 252 |  | 
 | 253 | static int erst_exec_set_src_address_base(struct apei_exec_context *ctx, | 
 | 254 | 					  struct acpi_whea_header *entry) | 
 | 255 | { | 
 | 256 | 	return __apei_exec_read_register(entry, &ctx->src_base); | 
 | 257 | } | 
 | 258 |  | 
 | 259 | static int erst_exec_set_dst_address_base(struct apei_exec_context *ctx, | 
 | 260 | 					  struct acpi_whea_header *entry) | 
 | 261 | { | 
 | 262 | 	return __apei_exec_read_register(entry, &ctx->dst_base); | 
 | 263 | } | 
 | 264 |  | 
 | 265 | static int erst_exec_move_data(struct apei_exec_context *ctx, | 
 | 266 | 			       struct acpi_whea_header *entry) | 
 | 267 | { | 
 | 268 | 	int rc; | 
 | 269 | 	u64 offset; | 
| Huang Ying | 0bbba38 | 2010-09-29 19:53:55 +0800 | [diff] [blame] | 270 | 	void *src, *dst; | 
 | 271 |  | 
 | 272 | 	/* ioremap does not work in interrupt context */ | 
 | 273 | 	if (in_interrupt()) { | 
 | 274 | 		pr_warning(ERST_PFX | 
 | 275 | 			   "MOVE_DATA can not be used in interrupt context"); | 
 | 276 | 		return -EBUSY; | 
 | 277 | 	} | 
| Huang Ying | a08f82d | 2010-05-18 14:35:21 +0800 | [diff] [blame] | 278 |  | 
 | 279 | 	rc = __apei_exec_read_register(entry, &offset); | 
 | 280 | 	if (rc) | 
 | 281 | 		return rc; | 
| Huang Ying | 0bbba38 | 2010-09-29 19:53:55 +0800 | [diff] [blame] | 282 |  | 
 | 283 | 	src = ioremap(ctx->src_base + offset, ctx->var2); | 
 | 284 | 	if (!src) | 
 | 285 | 		return -ENOMEM; | 
 | 286 | 	dst = ioremap(ctx->dst_base + offset, ctx->var2); | 
 | 287 | 	if (!dst) | 
 | 288 | 		return -ENOMEM; | 
 | 289 |  | 
 | 290 | 	memmove(dst, src, ctx->var2); | 
 | 291 |  | 
 | 292 | 	iounmap(src); | 
 | 293 | 	iounmap(dst); | 
| Huang Ying | a08f82d | 2010-05-18 14:35:21 +0800 | [diff] [blame] | 294 |  | 
 | 295 | 	return 0; | 
 | 296 | } | 
 | 297 |  | 
 | 298 | static struct apei_exec_ins_type erst_ins_type[] = { | 
 | 299 | 	[ACPI_ERST_READ_REGISTER] = { | 
 | 300 | 		.flags = APEI_EXEC_INS_ACCESS_REGISTER, | 
 | 301 | 		.run = apei_exec_read_register, | 
 | 302 | 	}, | 
 | 303 | 	[ACPI_ERST_READ_REGISTER_VALUE] = { | 
 | 304 | 		.flags = APEI_EXEC_INS_ACCESS_REGISTER, | 
 | 305 | 		.run = apei_exec_read_register_value, | 
 | 306 | 	}, | 
 | 307 | 	[ACPI_ERST_WRITE_REGISTER] = { | 
 | 308 | 		.flags = APEI_EXEC_INS_ACCESS_REGISTER, | 
 | 309 | 		.run = apei_exec_write_register, | 
 | 310 | 	}, | 
 | 311 | 	[ACPI_ERST_WRITE_REGISTER_VALUE] = { | 
 | 312 | 		.flags = APEI_EXEC_INS_ACCESS_REGISTER, | 
 | 313 | 		.run = apei_exec_write_register_value, | 
 | 314 | 	}, | 
 | 315 | 	[ACPI_ERST_NOOP] = { | 
 | 316 | 		.flags = 0, | 
 | 317 | 		.run = apei_exec_noop, | 
 | 318 | 	}, | 
 | 319 | 	[ACPI_ERST_LOAD_VAR1] = { | 
 | 320 | 		.flags = APEI_EXEC_INS_ACCESS_REGISTER, | 
 | 321 | 		.run = erst_exec_load_var1, | 
 | 322 | 	}, | 
 | 323 | 	[ACPI_ERST_LOAD_VAR2] = { | 
 | 324 | 		.flags = APEI_EXEC_INS_ACCESS_REGISTER, | 
 | 325 | 		.run = erst_exec_load_var2, | 
 | 326 | 	}, | 
 | 327 | 	[ACPI_ERST_STORE_VAR1] = { | 
 | 328 | 		.flags = APEI_EXEC_INS_ACCESS_REGISTER, | 
 | 329 | 		.run = erst_exec_store_var1, | 
 | 330 | 	}, | 
 | 331 | 	[ACPI_ERST_ADD] = { | 
 | 332 | 		.flags = 0, | 
 | 333 | 		.run = erst_exec_add, | 
 | 334 | 	}, | 
 | 335 | 	[ACPI_ERST_SUBTRACT] = { | 
 | 336 | 		.flags = 0, | 
 | 337 | 		.run = erst_exec_subtract, | 
 | 338 | 	}, | 
 | 339 | 	[ACPI_ERST_ADD_VALUE] = { | 
 | 340 | 		.flags = APEI_EXEC_INS_ACCESS_REGISTER, | 
 | 341 | 		.run = erst_exec_add_value, | 
 | 342 | 	}, | 
 | 343 | 	[ACPI_ERST_SUBTRACT_VALUE] = { | 
 | 344 | 		.flags = APEI_EXEC_INS_ACCESS_REGISTER, | 
 | 345 | 		.run = erst_exec_subtract_value, | 
 | 346 | 	}, | 
 | 347 | 	[ACPI_ERST_STALL] = { | 
 | 348 | 		.flags = 0, | 
 | 349 | 		.run = erst_exec_stall, | 
 | 350 | 	}, | 
 | 351 | 	[ACPI_ERST_STALL_WHILE_TRUE] = { | 
 | 352 | 		.flags = APEI_EXEC_INS_ACCESS_REGISTER, | 
 | 353 | 		.run = erst_exec_stall_while_true, | 
 | 354 | 	}, | 
 | 355 | 	[ACPI_ERST_SKIP_NEXT_IF_TRUE] = { | 
 | 356 | 		.flags = APEI_EXEC_INS_ACCESS_REGISTER, | 
 | 357 | 		.run = erst_exec_skip_next_instruction_if_true, | 
 | 358 | 	}, | 
 | 359 | 	[ACPI_ERST_GOTO] = { | 
 | 360 | 		.flags = 0, | 
 | 361 | 		.run = erst_exec_goto, | 
 | 362 | 	}, | 
 | 363 | 	[ACPI_ERST_SET_SRC_ADDRESS_BASE] = { | 
 | 364 | 		.flags = APEI_EXEC_INS_ACCESS_REGISTER, | 
 | 365 | 		.run = erst_exec_set_src_address_base, | 
 | 366 | 	}, | 
 | 367 | 	[ACPI_ERST_SET_DST_ADDRESS_BASE] = { | 
 | 368 | 		.flags = APEI_EXEC_INS_ACCESS_REGISTER, | 
 | 369 | 		.run = erst_exec_set_dst_address_base, | 
 | 370 | 	}, | 
 | 371 | 	[ACPI_ERST_MOVE_DATA] = { | 
 | 372 | 		.flags = APEI_EXEC_INS_ACCESS_REGISTER, | 
 | 373 | 		.run = erst_exec_move_data, | 
 | 374 | 	}, | 
 | 375 | }; | 
 | 376 |  | 
 | 377 | static inline void erst_exec_ctx_init(struct apei_exec_context *ctx) | 
 | 378 | { | 
 | 379 | 	apei_exec_ctx_init(ctx, erst_ins_type, ARRAY_SIZE(erst_ins_type), | 
 | 380 | 			   ERST_TAB_ENTRY(erst_tab), erst_tab->entries); | 
 | 381 | } | 
 | 382 |  | 
 | 383 | static int erst_get_erange(struct erst_erange *range) | 
 | 384 | { | 
 | 385 | 	struct apei_exec_context ctx; | 
 | 386 | 	int rc; | 
 | 387 |  | 
 | 388 | 	erst_exec_ctx_init(&ctx); | 
 | 389 | 	rc = apei_exec_run(&ctx, ACPI_ERST_GET_ERROR_RANGE); | 
 | 390 | 	if (rc) | 
 | 391 | 		return rc; | 
 | 392 | 	range->base = apei_exec_ctx_get_output(&ctx); | 
 | 393 | 	rc = apei_exec_run(&ctx, ACPI_ERST_GET_ERROR_LENGTH); | 
 | 394 | 	if (rc) | 
 | 395 | 		return rc; | 
 | 396 | 	range->size = apei_exec_ctx_get_output(&ctx); | 
 | 397 | 	rc = apei_exec_run(&ctx, ACPI_ERST_GET_ERROR_ATTRIBUTES); | 
 | 398 | 	if (rc) | 
 | 399 | 		return rc; | 
 | 400 | 	range->attr = apei_exec_ctx_get_output(&ctx); | 
 | 401 |  | 
 | 402 | 	return 0; | 
 | 403 | } | 
 | 404 |  | 
 | 405 | static ssize_t __erst_get_record_count(void) | 
 | 406 | { | 
 | 407 | 	struct apei_exec_context ctx; | 
 | 408 | 	int rc; | 
 | 409 |  | 
 | 410 | 	erst_exec_ctx_init(&ctx); | 
 | 411 | 	rc = apei_exec_run(&ctx, ACPI_ERST_GET_RECORD_COUNT); | 
 | 412 | 	if (rc) | 
 | 413 | 		return rc; | 
 | 414 | 	return apei_exec_ctx_get_output(&ctx); | 
 | 415 | } | 
 | 416 |  | 
 | 417 | ssize_t erst_get_record_count(void) | 
 | 418 | { | 
 | 419 | 	ssize_t count; | 
 | 420 | 	unsigned long flags; | 
 | 421 |  | 
 | 422 | 	if (erst_disable) | 
 | 423 | 		return -ENODEV; | 
 | 424 |  | 
| Huang Ying | 3b38bb5 | 2010-12-02 10:40:53 +0800 | [diff] [blame] | 425 | 	raw_spin_lock_irqsave(&erst_lock, flags); | 
| Huang Ying | a08f82d | 2010-05-18 14:35:21 +0800 | [diff] [blame] | 426 | 	count = __erst_get_record_count(); | 
| Huang Ying | 3b38bb5 | 2010-12-02 10:40:53 +0800 | [diff] [blame] | 427 | 	raw_spin_unlock_irqrestore(&erst_lock, flags); | 
| Huang Ying | a08f82d | 2010-05-18 14:35:21 +0800 | [diff] [blame] | 428 |  | 
 | 429 | 	return count; | 
 | 430 | } | 
 | 431 | EXPORT_SYMBOL_GPL(erst_get_record_count); | 
 | 432 |  | 
| Huang Ying | 885b976 | 2011-02-21 13:54:41 +0800 | [diff] [blame] | 433 | #define ERST_RECORD_ID_CACHE_SIZE_MIN	16 | 
 | 434 | #define ERST_RECORD_ID_CACHE_SIZE_MAX	1024 | 
 | 435 |  | 
 | 436 | struct erst_record_id_cache { | 
 | 437 | 	struct mutex lock; | 
 | 438 | 	u64 *entries; | 
 | 439 | 	int len; | 
 | 440 | 	int size; | 
 | 441 | 	int refcount; | 
 | 442 | }; | 
 | 443 |  | 
 | 444 | static struct erst_record_id_cache erst_record_id_cache = { | 
 | 445 | 	.lock = __MUTEX_INITIALIZER(erst_record_id_cache.lock), | 
 | 446 | 	.refcount = 0, | 
 | 447 | }; | 
 | 448 |  | 
| Huang Ying | a08f82d | 2010-05-18 14:35:21 +0800 | [diff] [blame] | 449 | static int __erst_get_next_record_id(u64 *record_id) | 
 | 450 | { | 
 | 451 | 	struct apei_exec_context ctx; | 
 | 452 | 	int rc; | 
 | 453 |  | 
 | 454 | 	erst_exec_ctx_init(&ctx); | 
 | 455 | 	rc = apei_exec_run(&ctx, ACPI_ERST_GET_RECORD_ID); | 
 | 456 | 	if (rc) | 
 | 457 | 		return rc; | 
 | 458 | 	*record_id = apei_exec_ctx_get_output(&ctx); | 
 | 459 |  | 
 | 460 | 	return 0; | 
 | 461 | } | 
 | 462 |  | 
| Huang Ying | 885b976 | 2011-02-21 13:54:41 +0800 | [diff] [blame] | 463 | int erst_get_record_id_begin(int *pos) | 
 | 464 | { | 
 | 465 | 	int rc; | 
 | 466 |  | 
 | 467 | 	if (erst_disable) | 
 | 468 | 		return -ENODEV; | 
 | 469 |  | 
 | 470 | 	rc = mutex_lock_interruptible(&erst_record_id_cache.lock); | 
 | 471 | 	if (rc) | 
 | 472 | 		return rc; | 
 | 473 | 	erst_record_id_cache.refcount++; | 
 | 474 | 	mutex_unlock(&erst_record_id_cache.lock); | 
 | 475 |  | 
 | 476 | 	*pos = 0; | 
 | 477 |  | 
 | 478 | 	return 0; | 
 | 479 | } | 
 | 480 | EXPORT_SYMBOL_GPL(erst_get_record_id_begin); | 
 | 481 |  | 
 | 482 | /* erst_record_id_cache.lock must be held by caller */ | 
 | 483 | static int __erst_record_id_cache_add_one(void) | 
 | 484 | { | 
 | 485 | 	u64 id, prev_id, first_id; | 
 | 486 | 	int i, rc; | 
 | 487 | 	u64 *entries; | 
 | 488 | 	unsigned long flags; | 
 | 489 |  | 
 | 490 | 	id = prev_id = first_id = APEI_ERST_INVALID_RECORD_ID; | 
 | 491 | retry: | 
 | 492 | 	raw_spin_lock_irqsave(&erst_lock, flags); | 
 | 493 | 	rc = __erst_get_next_record_id(&id); | 
 | 494 | 	raw_spin_unlock_irqrestore(&erst_lock, flags); | 
 | 495 | 	if (rc == -ENOENT) | 
 | 496 | 		return 0; | 
 | 497 | 	if (rc) | 
 | 498 | 		return rc; | 
 | 499 | 	if (id == APEI_ERST_INVALID_RECORD_ID) | 
 | 500 | 		return 0; | 
 | 501 | 	/* can not skip current ID, or loop back to first ID */ | 
 | 502 | 	if (id == prev_id || id == first_id) | 
 | 503 | 		return 0; | 
 | 504 | 	if (first_id == APEI_ERST_INVALID_RECORD_ID) | 
 | 505 | 		first_id = id; | 
 | 506 | 	prev_id = id; | 
 | 507 |  | 
 | 508 | 	entries = erst_record_id_cache.entries; | 
 | 509 | 	for (i = 0; i < erst_record_id_cache.len; i++) { | 
 | 510 | 		if (entries[i] == id) | 
 | 511 | 			break; | 
 | 512 | 	} | 
 | 513 | 	/* record id already in cache, try next */ | 
 | 514 | 	if (i < erst_record_id_cache.len) | 
 | 515 | 		goto retry; | 
 | 516 | 	if (erst_record_id_cache.len >= erst_record_id_cache.size) { | 
 | 517 | 		int new_size, alloc_size; | 
 | 518 | 		u64 *new_entries; | 
 | 519 |  | 
 | 520 | 		new_size = erst_record_id_cache.size * 2; | 
 | 521 | 		new_size = clamp_val(new_size, ERST_RECORD_ID_CACHE_SIZE_MIN, | 
 | 522 | 				     ERST_RECORD_ID_CACHE_SIZE_MAX); | 
 | 523 | 		if (new_size <= erst_record_id_cache.size) { | 
 | 524 | 			if (printk_ratelimit()) | 
 | 525 | 				pr_warning(FW_WARN ERST_PFX | 
 | 526 | 					   "too many record ID!\n"); | 
 | 527 | 			return 0; | 
 | 528 | 		} | 
 | 529 | 		alloc_size = new_size * sizeof(entries[0]); | 
 | 530 | 		if (alloc_size < PAGE_SIZE) | 
 | 531 | 			new_entries = kmalloc(alloc_size, GFP_KERNEL); | 
 | 532 | 		else | 
 | 533 | 			new_entries = vmalloc(alloc_size); | 
 | 534 | 		if (!new_entries) | 
 | 535 | 			return -ENOMEM; | 
 | 536 | 		memcpy(new_entries, entries, | 
 | 537 | 		       erst_record_id_cache.len * sizeof(entries[0])); | 
 | 538 | 		if (erst_record_id_cache.size < PAGE_SIZE) | 
 | 539 | 			kfree(entries); | 
 | 540 | 		else | 
 | 541 | 			vfree(entries); | 
 | 542 | 		erst_record_id_cache.entries = entries = new_entries; | 
 | 543 | 		erst_record_id_cache.size = new_size; | 
 | 544 | 	} | 
 | 545 | 	entries[i] = id; | 
 | 546 | 	erst_record_id_cache.len++; | 
 | 547 |  | 
 | 548 | 	return 1; | 
 | 549 | } | 
 | 550 |  | 
| Huang Ying | a08f82d | 2010-05-18 14:35:21 +0800 | [diff] [blame] | 551 | /* | 
 | 552 |  * Get the record ID of an existing error record on the persistent | 
 | 553 |  * storage. If there is no error record on the persistent storage, the | 
 | 554 |  * returned record_id is APEI_ERST_INVALID_RECORD_ID. | 
 | 555 |  */ | 
| Huang Ying | 885b976 | 2011-02-21 13:54:41 +0800 | [diff] [blame] | 556 | int erst_get_record_id_next(int *pos, u64 *record_id) | 
| Huang Ying | a08f82d | 2010-05-18 14:35:21 +0800 | [diff] [blame] | 557 | { | 
| Huang Ying | 885b976 | 2011-02-21 13:54:41 +0800 | [diff] [blame] | 558 | 	int rc = 0; | 
 | 559 | 	u64 *entries; | 
| Huang Ying | a08f82d | 2010-05-18 14:35:21 +0800 | [diff] [blame] | 560 |  | 
 | 561 | 	if (erst_disable) | 
 | 562 | 		return -ENODEV; | 
 | 563 |  | 
| Huang Ying | 885b976 | 2011-02-21 13:54:41 +0800 | [diff] [blame] | 564 | 	/* must be enclosed by erst_get_record_id_begin/end */ | 
 | 565 | 	BUG_ON(!erst_record_id_cache.refcount); | 
 | 566 | 	BUG_ON(*pos < 0 || *pos > erst_record_id_cache.len); | 
 | 567 |  | 
 | 568 | 	mutex_lock(&erst_record_id_cache.lock); | 
 | 569 | 	entries = erst_record_id_cache.entries; | 
 | 570 | 	for (; *pos < erst_record_id_cache.len; (*pos)++) | 
 | 571 | 		if (entries[*pos] != APEI_ERST_INVALID_RECORD_ID) | 
 | 572 | 			break; | 
 | 573 | 	/* found next record id in cache */ | 
 | 574 | 	if (*pos < erst_record_id_cache.len) { | 
 | 575 | 		*record_id = entries[*pos]; | 
 | 576 | 		(*pos)++; | 
 | 577 | 		goto out_unlock; | 
 | 578 | 	} | 
 | 579 |  | 
 | 580 | 	/* Try to add one more record ID to cache */ | 
 | 581 | 	rc = __erst_record_id_cache_add_one(); | 
 | 582 | 	if (rc < 0) | 
 | 583 | 		goto out_unlock; | 
 | 584 | 	/* successfully add one new ID */ | 
 | 585 | 	if (rc == 1) { | 
 | 586 | 		*record_id = erst_record_id_cache.entries[*pos]; | 
 | 587 | 		(*pos)++; | 
 | 588 | 		rc = 0; | 
 | 589 | 	} else { | 
 | 590 | 		*pos = -1; | 
 | 591 | 		*record_id = APEI_ERST_INVALID_RECORD_ID; | 
 | 592 | 	} | 
 | 593 | out_unlock: | 
 | 594 | 	mutex_unlock(&erst_record_id_cache.lock); | 
| Huang Ying | a08f82d | 2010-05-18 14:35:21 +0800 | [diff] [blame] | 595 |  | 
 | 596 | 	return rc; | 
 | 597 | } | 
| Huang Ying | 885b976 | 2011-02-21 13:54:41 +0800 | [diff] [blame] | 598 | EXPORT_SYMBOL_GPL(erst_get_record_id_next); | 
 | 599 |  | 
 | 600 | /* erst_record_id_cache.lock must be held by caller */ | 
 | 601 | static void __erst_record_id_cache_compact(void) | 
 | 602 | { | 
 | 603 | 	int i, wpos = 0; | 
 | 604 | 	u64 *entries; | 
 | 605 |  | 
 | 606 | 	if (erst_record_id_cache.refcount) | 
 | 607 | 		return; | 
 | 608 |  | 
 | 609 | 	entries = erst_record_id_cache.entries; | 
 | 610 | 	for (i = 0; i < erst_record_id_cache.len; i++) { | 
 | 611 | 		if (entries[i] == APEI_ERST_INVALID_RECORD_ID) | 
 | 612 | 			continue; | 
 | 613 | 		if (wpos != i) | 
 | 614 | 			memcpy(&entries[wpos], &entries[i], sizeof(entries[i])); | 
 | 615 | 		wpos++; | 
 | 616 | 	} | 
 | 617 | 	erst_record_id_cache.len = wpos; | 
 | 618 | } | 
 | 619 |  | 
 | 620 | void erst_get_record_id_end(void) | 
 | 621 | { | 
 | 622 | 	/* | 
 | 623 | 	 * erst_disable != 0 should be detected by invoker via the | 
 | 624 | 	 * return value of erst_get_record_id_begin/next, so this | 
 | 625 | 	 * function should not be called for erst_disable != 0. | 
 | 626 | 	 */ | 
 | 627 | 	BUG_ON(erst_disable); | 
 | 628 |  | 
 | 629 | 	mutex_lock(&erst_record_id_cache.lock); | 
 | 630 | 	erst_record_id_cache.refcount--; | 
 | 631 | 	BUG_ON(erst_record_id_cache.refcount < 0); | 
 | 632 | 	__erst_record_id_cache_compact(); | 
 | 633 | 	mutex_unlock(&erst_record_id_cache.lock); | 
 | 634 | } | 
 | 635 | EXPORT_SYMBOL_GPL(erst_get_record_id_end); | 
| Huang Ying | a08f82d | 2010-05-18 14:35:21 +0800 | [diff] [blame] | 636 |  | 
 | 637 | static int __erst_write_to_storage(u64 offset) | 
 | 638 | { | 
 | 639 | 	struct apei_exec_context ctx; | 
 | 640 | 	u64 timeout = FIRMWARE_TIMEOUT; | 
 | 641 | 	u64 val; | 
 | 642 | 	int rc; | 
 | 643 |  | 
 | 644 | 	erst_exec_ctx_init(&ctx); | 
 | 645 | 	rc = apei_exec_run(&ctx, ACPI_ERST_BEGIN_WRITE); | 
 | 646 | 	if (rc) | 
 | 647 | 		return rc; | 
 | 648 | 	apei_exec_ctx_set_input(&ctx, offset); | 
 | 649 | 	rc = apei_exec_run(&ctx, ACPI_ERST_SET_RECORD_OFFSET); | 
 | 650 | 	if (rc) | 
 | 651 | 		return rc; | 
 | 652 | 	rc = apei_exec_run(&ctx, ACPI_ERST_EXECUTE_OPERATION); | 
 | 653 | 	if (rc) | 
 | 654 | 		return rc; | 
 | 655 | 	for (;;) { | 
 | 656 | 		rc = apei_exec_run(&ctx, ACPI_ERST_CHECK_BUSY_STATUS); | 
 | 657 | 		if (rc) | 
 | 658 | 			return rc; | 
 | 659 | 		val = apei_exec_ctx_get_output(&ctx); | 
 | 660 | 		if (!val) | 
 | 661 | 			break; | 
 | 662 | 		if (erst_timedout(&timeout, SPIN_UNIT)) | 
 | 663 | 			return -EIO; | 
 | 664 | 	} | 
 | 665 | 	rc = apei_exec_run(&ctx, ACPI_ERST_GET_COMMAND_STATUS); | 
 | 666 | 	if (rc) | 
 | 667 | 		return rc; | 
 | 668 | 	val = apei_exec_ctx_get_output(&ctx); | 
 | 669 | 	rc = apei_exec_run(&ctx, ACPI_ERST_END); | 
 | 670 | 	if (rc) | 
 | 671 | 		return rc; | 
 | 672 |  | 
 | 673 | 	return erst_errno(val); | 
 | 674 | } | 
 | 675 |  | 
 | 676 | static int __erst_read_from_storage(u64 record_id, u64 offset) | 
 | 677 | { | 
 | 678 | 	struct apei_exec_context ctx; | 
 | 679 | 	u64 timeout = FIRMWARE_TIMEOUT; | 
 | 680 | 	u64 val; | 
 | 681 | 	int rc; | 
 | 682 |  | 
 | 683 | 	erst_exec_ctx_init(&ctx); | 
 | 684 | 	rc = apei_exec_run(&ctx, ACPI_ERST_BEGIN_READ); | 
 | 685 | 	if (rc) | 
 | 686 | 		return rc; | 
 | 687 | 	apei_exec_ctx_set_input(&ctx, offset); | 
 | 688 | 	rc = apei_exec_run(&ctx, ACPI_ERST_SET_RECORD_OFFSET); | 
 | 689 | 	if (rc) | 
 | 690 | 		return rc; | 
 | 691 | 	apei_exec_ctx_set_input(&ctx, record_id); | 
 | 692 | 	rc = apei_exec_run(&ctx, ACPI_ERST_SET_RECORD_ID); | 
 | 693 | 	if (rc) | 
 | 694 | 		return rc; | 
 | 695 | 	rc = apei_exec_run(&ctx, ACPI_ERST_EXECUTE_OPERATION); | 
 | 696 | 	if (rc) | 
 | 697 | 		return rc; | 
 | 698 | 	for (;;) { | 
 | 699 | 		rc = apei_exec_run(&ctx, ACPI_ERST_CHECK_BUSY_STATUS); | 
 | 700 | 		if (rc) | 
 | 701 | 			return rc; | 
 | 702 | 		val = apei_exec_ctx_get_output(&ctx); | 
 | 703 | 		if (!val) | 
 | 704 | 			break; | 
 | 705 | 		if (erst_timedout(&timeout, SPIN_UNIT)) | 
 | 706 | 			return -EIO; | 
 | 707 | 	}; | 
 | 708 | 	rc = apei_exec_run(&ctx, ACPI_ERST_GET_COMMAND_STATUS); | 
 | 709 | 	if (rc) | 
 | 710 | 		return rc; | 
 | 711 | 	val = apei_exec_ctx_get_output(&ctx); | 
 | 712 | 	rc = apei_exec_run(&ctx, ACPI_ERST_END); | 
 | 713 | 	if (rc) | 
 | 714 | 		return rc; | 
 | 715 |  | 
 | 716 | 	return erst_errno(val); | 
 | 717 | } | 
 | 718 |  | 
 | 719 | static int __erst_clear_from_storage(u64 record_id) | 
 | 720 | { | 
 | 721 | 	struct apei_exec_context ctx; | 
 | 722 | 	u64 timeout = FIRMWARE_TIMEOUT; | 
 | 723 | 	u64 val; | 
 | 724 | 	int rc; | 
 | 725 |  | 
 | 726 | 	erst_exec_ctx_init(&ctx); | 
 | 727 | 	rc = apei_exec_run(&ctx, ACPI_ERST_BEGIN_CLEAR); | 
 | 728 | 	if (rc) | 
 | 729 | 		return rc; | 
 | 730 | 	apei_exec_ctx_set_input(&ctx, record_id); | 
 | 731 | 	rc = apei_exec_run(&ctx, ACPI_ERST_SET_RECORD_ID); | 
 | 732 | 	if (rc) | 
 | 733 | 		return rc; | 
 | 734 | 	rc = apei_exec_run(&ctx, ACPI_ERST_EXECUTE_OPERATION); | 
 | 735 | 	if (rc) | 
 | 736 | 		return rc; | 
 | 737 | 	for (;;) { | 
 | 738 | 		rc = apei_exec_run(&ctx, ACPI_ERST_CHECK_BUSY_STATUS); | 
 | 739 | 		if (rc) | 
 | 740 | 			return rc; | 
 | 741 | 		val = apei_exec_ctx_get_output(&ctx); | 
 | 742 | 		if (!val) | 
 | 743 | 			break; | 
 | 744 | 		if (erst_timedout(&timeout, SPIN_UNIT)) | 
 | 745 | 			return -EIO; | 
 | 746 | 	} | 
 | 747 | 	rc = apei_exec_run(&ctx, ACPI_ERST_GET_COMMAND_STATUS); | 
 | 748 | 	if (rc) | 
 | 749 | 		return rc; | 
 | 750 | 	val = apei_exec_ctx_get_output(&ctx); | 
 | 751 | 	rc = apei_exec_run(&ctx, ACPI_ERST_END); | 
 | 752 | 	if (rc) | 
 | 753 | 		return rc; | 
 | 754 |  | 
 | 755 | 	return erst_errno(val); | 
 | 756 | } | 
 | 757 |  | 
 | 758 | /* NVRAM ERST Error Log Address Range is not supported yet */ | 
 | 759 | static void pr_unimpl_nvram(void) | 
 | 760 | { | 
 | 761 | 	if (printk_ratelimit()) | 
 | 762 | 		pr_warning(ERST_PFX | 
 | 763 | 		"NVRAM ERST Log Address Range is not implemented yet\n"); | 
 | 764 | } | 
 | 765 |  | 
 | 766 | static int __erst_write_to_nvram(const struct cper_record_header *record) | 
 | 767 | { | 
 | 768 | 	/* do not print message, because printk is not safe for NMI */ | 
 | 769 | 	return -ENOSYS; | 
 | 770 | } | 
 | 771 |  | 
 | 772 | static int __erst_read_to_erange_from_nvram(u64 record_id, u64 *offset) | 
 | 773 | { | 
 | 774 | 	pr_unimpl_nvram(); | 
 | 775 | 	return -ENOSYS; | 
 | 776 | } | 
 | 777 |  | 
 | 778 | static int __erst_clear_from_nvram(u64 record_id) | 
 | 779 | { | 
 | 780 | 	pr_unimpl_nvram(); | 
 | 781 | 	return -ENOSYS; | 
 | 782 | } | 
 | 783 |  | 
 | 784 | int erst_write(const struct cper_record_header *record) | 
 | 785 | { | 
 | 786 | 	int rc; | 
 | 787 | 	unsigned long flags; | 
 | 788 | 	struct cper_record_header *rcd_erange; | 
 | 789 |  | 
 | 790 | 	if (erst_disable) | 
 | 791 | 		return -ENODEV; | 
 | 792 |  | 
 | 793 | 	if (memcmp(record->signature, CPER_SIG_RECORD, CPER_SIG_SIZE)) | 
 | 794 | 		return -EINVAL; | 
 | 795 |  | 
 | 796 | 	if (erst_erange.attr & ERST_RANGE_NVRAM) { | 
| Huang Ying | 3b38bb5 | 2010-12-02 10:40:53 +0800 | [diff] [blame] | 797 | 		if (!raw_spin_trylock_irqsave(&erst_lock, flags)) | 
| Huang Ying | a08f82d | 2010-05-18 14:35:21 +0800 | [diff] [blame] | 798 | 			return -EBUSY; | 
 | 799 | 		rc = __erst_write_to_nvram(record); | 
| Huang Ying | 3b38bb5 | 2010-12-02 10:40:53 +0800 | [diff] [blame] | 800 | 		raw_spin_unlock_irqrestore(&erst_lock, flags); | 
| Huang Ying | a08f82d | 2010-05-18 14:35:21 +0800 | [diff] [blame] | 801 | 		return rc; | 
 | 802 | 	} | 
 | 803 |  | 
 | 804 | 	if (record->record_length > erst_erange.size) | 
 | 805 | 		return -EINVAL; | 
 | 806 |  | 
| Huang Ying | 3b38bb5 | 2010-12-02 10:40:53 +0800 | [diff] [blame] | 807 | 	if (!raw_spin_trylock_irqsave(&erst_lock, flags)) | 
| Huang Ying | a08f82d | 2010-05-18 14:35:21 +0800 | [diff] [blame] | 808 | 		return -EBUSY; | 
 | 809 | 	memcpy(erst_erange.vaddr, record, record->record_length); | 
 | 810 | 	rcd_erange = erst_erange.vaddr; | 
 | 811 | 	/* signature for serialization system */ | 
 | 812 | 	memcpy(&rcd_erange->persistence_information, "ER", 2); | 
 | 813 |  | 
 | 814 | 	rc = __erst_write_to_storage(0); | 
| Huang Ying | 3b38bb5 | 2010-12-02 10:40:53 +0800 | [diff] [blame] | 815 | 	raw_spin_unlock_irqrestore(&erst_lock, flags); | 
| Huang Ying | a08f82d | 2010-05-18 14:35:21 +0800 | [diff] [blame] | 816 |  | 
 | 817 | 	return rc; | 
 | 818 | } | 
 | 819 | EXPORT_SYMBOL_GPL(erst_write); | 
 | 820 |  | 
 | 821 | static int __erst_read_to_erange(u64 record_id, u64 *offset) | 
 | 822 | { | 
 | 823 | 	int rc; | 
 | 824 |  | 
 | 825 | 	if (erst_erange.attr & ERST_RANGE_NVRAM) | 
 | 826 | 		return __erst_read_to_erange_from_nvram( | 
 | 827 | 			record_id, offset); | 
 | 828 |  | 
 | 829 | 	rc = __erst_read_from_storage(record_id, 0); | 
 | 830 | 	if (rc) | 
 | 831 | 		return rc; | 
 | 832 | 	*offset = 0; | 
 | 833 |  | 
 | 834 | 	return 0; | 
 | 835 | } | 
 | 836 |  | 
 | 837 | static ssize_t __erst_read(u64 record_id, struct cper_record_header *record, | 
 | 838 | 			   size_t buflen) | 
 | 839 | { | 
 | 840 | 	int rc; | 
 | 841 | 	u64 offset, len = 0; | 
 | 842 | 	struct cper_record_header *rcd_tmp; | 
 | 843 |  | 
 | 844 | 	rc = __erst_read_to_erange(record_id, &offset); | 
 | 845 | 	if (rc) | 
 | 846 | 		return rc; | 
 | 847 | 	rcd_tmp = erst_erange.vaddr + offset; | 
 | 848 | 	len = rcd_tmp->record_length; | 
 | 849 | 	if (len <= buflen) | 
 | 850 | 		memcpy(record, rcd_tmp, len); | 
 | 851 |  | 
 | 852 | 	return len; | 
 | 853 | } | 
 | 854 |  | 
 | 855 | /* | 
 | 856 |  * If return value > buflen, the buffer size is not big enough, | 
 | 857 |  * else if return value < 0, something goes wrong, | 
 | 858 |  * else everything is OK, and return value is record length | 
 | 859 |  */ | 
 | 860 | ssize_t erst_read(u64 record_id, struct cper_record_header *record, | 
 | 861 | 		  size_t buflen) | 
 | 862 | { | 
 | 863 | 	ssize_t len; | 
 | 864 | 	unsigned long flags; | 
 | 865 |  | 
 | 866 | 	if (erst_disable) | 
 | 867 | 		return -ENODEV; | 
 | 868 |  | 
| Huang Ying | 3b38bb5 | 2010-12-02 10:40:53 +0800 | [diff] [blame] | 869 | 	raw_spin_lock_irqsave(&erst_lock, flags); | 
| Huang Ying | a08f82d | 2010-05-18 14:35:21 +0800 | [diff] [blame] | 870 | 	len = __erst_read(record_id, record, buflen); | 
| Huang Ying | 3b38bb5 | 2010-12-02 10:40:53 +0800 | [diff] [blame] | 871 | 	raw_spin_unlock_irqrestore(&erst_lock, flags); | 
| Huang Ying | a08f82d | 2010-05-18 14:35:21 +0800 | [diff] [blame] | 872 | 	return len; | 
 | 873 | } | 
 | 874 | EXPORT_SYMBOL_GPL(erst_read); | 
 | 875 |  | 
| Huang Ying | a08f82d | 2010-05-18 14:35:21 +0800 | [diff] [blame] | 876 | int erst_clear(u64 record_id) | 
 | 877 | { | 
| Huang Ying | 885b976 | 2011-02-21 13:54:41 +0800 | [diff] [blame] | 878 | 	int rc, i; | 
| Huang Ying | a08f82d | 2010-05-18 14:35:21 +0800 | [diff] [blame] | 879 | 	unsigned long flags; | 
| Huang Ying | 885b976 | 2011-02-21 13:54:41 +0800 | [diff] [blame] | 880 | 	u64 *entries; | 
| Huang Ying | a08f82d | 2010-05-18 14:35:21 +0800 | [diff] [blame] | 881 |  | 
 | 882 | 	if (erst_disable) | 
 | 883 | 		return -ENODEV; | 
 | 884 |  | 
| Huang Ying | 885b976 | 2011-02-21 13:54:41 +0800 | [diff] [blame] | 885 | 	rc = mutex_lock_interruptible(&erst_record_id_cache.lock); | 
 | 886 | 	if (rc) | 
 | 887 | 		return rc; | 
| Huang Ying | 3b38bb5 | 2010-12-02 10:40:53 +0800 | [diff] [blame] | 888 | 	raw_spin_lock_irqsave(&erst_lock, flags); | 
| Huang Ying | a08f82d | 2010-05-18 14:35:21 +0800 | [diff] [blame] | 889 | 	if (erst_erange.attr & ERST_RANGE_NVRAM) | 
 | 890 | 		rc = __erst_clear_from_nvram(record_id); | 
 | 891 | 	else | 
 | 892 | 		rc = __erst_clear_from_storage(record_id); | 
| Huang Ying | 3b38bb5 | 2010-12-02 10:40:53 +0800 | [diff] [blame] | 893 | 	raw_spin_unlock_irqrestore(&erst_lock, flags); | 
| Huang Ying | 885b976 | 2011-02-21 13:54:41 +0800 | [diff] [blame] | 894 | 	if (rc) | 
 | 895 | 		goto out; | 
 | 896 | 	entries = erst_record_id_cache.entries; | 
 | 897 | 	for (i = 0; i < erst_record_id_cache.len; i++) { | 
 | 898 | 		if (entries[i] == record_id) | 
 | 899 | 			entries[i] = APEI_ERST_INVALID_RECORD_ID; | 
 | 900 | 	} | 
 | 901 | 	__erst_record_id_cache_compact(); | 
 | 902 | out: | 
 | 903 | 	mutex_unlock(&erst_record_id_cache.lock); | 
| Huang Ying | a08f82d | 2010-05-18 14:35:21 +0800 | [diff] [blame] | 904 | 	return rc; | 
 | 905 | } | 
 | 906 | EXPORT_SYMBOL_GPL(erst_clear); | 
 | 907 |  | 
 | 908 | static int __init setup_erst_disable(char *str) | 
 | 909 | { | 
 | 910 | 	erst_disable = 1; | 
 | 911 | 	return 0; | 
 | 912 | } | 
 | 913 |  | 
 | 914 | __setup("erst_disable", setup_erst_disable); | 
 | 915 |  | 
 | 916 | static int erst_check_table(struct acpi_table_erst *erst_tab) | 
 | 917 | { | 
| Huang Ying | 3a78f96 | 2010-09-29 19:53:51 +0800 | [diff] [blame] | 918 | 	if ((erst_tab->header_length != | 
 | 919 | 	     (sizeof(struct acpi_table_erst) - sizeof(erst_tab->header))) | 
 | 920 | 	    && (erst_tab->header_length != sizeof(struct acpi_table_einj))) | 
| Huang Ying | a08f82d | 2010-05-18 14:35:21 +0800 | [diff] [blame] | 921 | 		return -EINVAL; | 
 | 922 | 	if (erst_tab->header.length < sizeof(struct acpi_table_erst)) | 
 | 923 | 		return -EINVAL; | 
 | 924 | 	if (erst_tab->entries != | 
 | 925 | 	    (erst_tab->header.length - sizeof(struct acpi_table_erst)) / | 
 | 926 | 	    sizeof(struct acpi_erst_entry)) | 
 | 927 | 		return -EINVAL; | 
 | 928 |  | 
 | 929 | 	return 0; | 
 | 930 | } | 
 | 931 |  | 
| Tony Luck | 0bb77c4 | 2011-01-03 14:22:11 -0800 | [diff] [blame] | 932 | static size_t erst_reader(u64 *id, enum pstore_type_id *type, | 
 | 933 | 		       struct timespec *time); | 
 | 934 | static u64 erst_writer(enum pstore_type_id type, size_t size); | 
 | 935 |  | 
 | 936 | static struct pstore_info erst_info = { | 
 | 937 | 	.owner		= THIS_MODULE, | 
 | 938 | 	.name		= "erst", | 
 | 939 | 	.read		= erst_reader, | 
 | 940 | 	.write		= erst_writer, | 
 | 941 | 	.erase		= erst_clear | 
 | 942 | }; | 
 | 943 |  | 
 | 944 | #define CPER_CREATOR_PSTORE						\ | 
 | 945 | 	UUID_LE(0x75a574e3, 0x5052, 0x4b29, 0x8a, 0x8e, 0xbe, 0x2c,	\ | 
 | 946 | 		0x64, 0x90, 0xb8, 0x9d) | 
 | 947 | #define CPER_SECTION_TYPE_DMESG						\ | 
 | 948 | 	UUID_LE(0xc197e04e, 0xd545, 0x4a70, 0x9c, 0x17, 0xa5, 0x54,	\ | 
 | 949 | 		0x94, 0x19, 0xeb, 0x12) | 
 | 950 | #define CPER_SECTION_TYPE_MCE						\ | 
 | 951 | 	UUID_LE(0xfe08ffbe, 0x95e4, 0x4be7, 0xbc, 0x73, 0x40, 0x96,	\ | 
 | 952 | 		0x04, 0x4a, 0x38, 0xfc) | 
 | 953 |  | 
 | 954 | struct cper_pstore_record { | 
 | 955 | 	struct cper_record_header hdr; | 
 | 956 | 	struct cper_section_descriptor sec_hdr; | 
 | 957 | 	char data[]; | 
 | 958 | } __packed; | 
 | 959 |  | 
 | 960 | static size_t erst_reader(u64 *id, enum pstore_type_id *type, | 
 | 961 | 		       struct timespec *time) | 
 | 962 | { | 
 | 963 | 	int rc; | 
 | 964 | 	ssize_t len; | 
 | 965 | 	unsigned long flags; | 
 | 966 | 	u64 record_id; | 
 | 967 | 	struct cper_pstore_record *rcd = (struct cper_pstore_record *) | 
 | 968 | 					(erst_info.buf - sizeof(*rcd)); | 
 | 969 |  | 
 | 970 | 	if (erst_disable) | 
 | 971 | 		return -ENODEV; | 
 | 972 |  | 
 | 973 | 	raw_spin_lock_irqsave(&erst_lock, flags); | 
 | 974 | skip: | 
 | 975 | 	rc = __erst_get_next_record_id(&record_id); | 
 | 976 | 	if (rc) { | 
 | 977 | 		raw_spin_unlock_irqrestore(&erst_lock, flags); | 
 | 978 | 		return rc; | 
 | 979 | 	} | 
 | 980 | 	/* no more record */ | 
 | 981 | 	if (record_id == APEI_ERST_INVALID_RECORD_ID) { | 
 | 982 | 		raw_spin_unlock_irqrestore(&erst_lock, flags); | 
 | 983 | 		return 0; | 
 | 984 | 	} | 
 | 985 |  | 
 | 986 | 	len = __erst_read(record_id, &rcd->hdr, sizeof(*rcd) + | 
 | 987 | 			  erst_erange.size); | 
 | 988 | 	if (uuid_le_cmp(rcd->hdr.creator_id, CPER_CREATOR_PSTORE) != 0) | 
 | 989 | 		goto skip; | 
 | 990 | 	raw_spin_unlock_irqrestore(&erst_lock, flags); | 
 | 991 |  | 
 | 992 | 	*id = record_id; | 
 | 993 | 	if (uuid_le_cmp(rcd->sec_hdr.section_type, | 
 | 994 | 			CPER_SECTION_TYPE_DMESG) == 0) | 
 | 995 | 		*type = PSTORE_TYPE_DMESG; | 
 | 996 | 	else if (uuid_le_cmp(rcd->sec_hdr.section_type, | 
 | 997 | 			     CPER_SECTION_TYPE_MCE) == 0) | 
 | 998 | 		*type = PSTORE_TYPE_MCE; | 
 | 999 | 	else | 
 | 1000 | 		*type = PSTORE_TYPE_UNKNOWN; | 
 | 1001 |  | 
 | 1002 | 	if (rcd->hdr.validation_bits & CPER_VALID_TIMESTAMP) | 
 | 1003 | 		time->tv_sec = rcd->hdr.timestamp; | 
 | 1004 | 	else | 
 | 1005 | 		time->tv_sec = 0; | 
 | 1006 | 	time->tv_nsec = 0; | 
 | 1007 |  | 
 | 1008 | 	return len - sizeof(*rcd); | 
 | 1009 | } | 
 | 1010 |  | 
 | 1011 | static u64 erst_writer(enum pstore_type_id type, size_t size) | 
 | 1012 | { | 
 | 1013 | 	struct cper_pstore_record *rcd = (struct cper_pstore_record *) | 
 | 1014 | 					(erst_info.buf - sizeof(*rcd)); | 
 | 1015 |  | 
 | 1016 | 	memset(rcd, 0, sizeof(*rcd)); | 
 | 1017 | 	memcpy(rcd->hdr.signature, CPER_SIG_RECORD, CPER_SIG_SIZE); | 
 | 1018 | 	rcd->hdr.revision = CPER_RECORD_REV; | 
 | 1019 | 	rcd->hdr.signature_end = CPER_SIG_END; | 
 | 1020 | 	rcd->hdr.section_count = 1; | 
 | 1021 | 	rcd->hdr.error_severity = CPER_SEV_FATAL; | 
 | 1022 | 	/* timestamp valid. platform_id, partition_id are invalid */ | 
 | 1023 | 	rcd->hdr.validation_bits = CPER_VALID_TIMESTAMP; | 
 | 1024 | 	rcd->hdr.timestamp = get_seconds(); | 
 | 1025 | 	rcd->hdr.record_length = sizeof(*rcd) + size; | 
 | 1026 | 	rcd->hdr.creator_id = CPER_CREATOR_PSTORE; | 
 | 1027 | 	rcd->hdr.notification_type = CPER_NOTIFY_MCE; | 
 | 1028 | 	rcd->hdr.record_id = cper_next_record_id(); | 
 | 1029 | 	rcd->hdr.flags = CPER_HW_ERROR_FLAGS_PREVERR; | 
 | 1030 |  | 
 | 1031 | 	rcd->sec_hdr.section_offset = sizeof(*rcd); | 
 | 1032 | 	rcd->sec_hdr.section_length = size; | 
 | 1033 | 	rcd->sec_hdr.revision = CPER_SEC_REV; | 
 | 1034 | 	/* fru_id and fru_text is invalid */ | 
 | 1035 | 	rcd->sec_hdr.validation_bits = 0; | 
 | 1036 | 	rcd->sec_hdr.flags = CPER_SEC_PRIMARY; | 
 | 1037 | 	switch (type) { | 
 | 1038 | 	case PSTORE_TYPE_DMESG: | 
 | 1039 | 		rcd->sec_hdr.section_type = CPER_SECTION_TYPE_DMESG; | 
 | 1040 | 		break; | 
 | 1041 | 	case PSTORE_TYPE_MCE: | 
 | 1042 | 		rcd->sec_hdr.section_type = CPER_SECTION_TYPE_MCE; | 
 | 1043 | 		break; | 
 | 1044 | 	default: | 
 | 1045 | 		return -EINVAL; | 
 | 1046 | 	} | 
 | 1047 | 	rcd->sec_hdr.section_severity = CPER_SEV_FATAL; | 
 | 1048 |  | 
 | 1049 | 	erst_write(&rcd->hdr); | 
 | 1050 |  | 
 | 1051 | 	return rcd->hdr.record_id; | 
 | 1052 | } | 
 | 1053 |  | 
| Huang Ying | a08f82d | 2010-05-18 14:35:21 +0800 | [diff] [blame] | 1054 | static int __init erst_init(void) | 
 | 1055 | { | 
 | 1056 | 	int rc = 0; | 
 | 1057 | 	acpi_status status; | 
 | 1058 | 	struct apei_exec_context ctx; | 
 | 1059 | 	struct apei_resources erst_resources; | 
 | 1060 | 	struct resource *r; | 
| Tony Luck | 0bb77c4 | 2011-01-03 14:22:11 -0800 | [diff] [blame] | 1061 | 	char *buf; | 
| Huang Ying | a08f82d | 2010-05-18 14:35:21 +0800 | [diff] [blame] | 1062 |  | 
 | 1063 | 	if (acpi_disabled) | 
 | 1064 | 		goto err; | 
 | 1065 |  | 
 | 1066 | 	if (erst_disable) { | 
 | 1067 | 		pr_info(ERST_PFX | 
 | 1068 | 	"Error Record Serialization Table (ERST) support is disabled.\n"); | 
 | 1069 | 		goto err; | 
 | 1070 | 	} | 
 | 1071 |  | 
 | 1072 | 	status = acpi_get_table(ACPI_SIG_ERST, 0, | 
 | 1073 | 				(struct acpi_table_header **)&erst_tab); | 
 | 1074 | 	if (status == AE_NOT_FOUND) { | 
| Daniel J Blueman | 980533b | 2010-07-01 23:27:11 +0100 | [diff] [blame] | 1075 | 		pr_info(ERST_PFX "Table is not found!\n"); | 
| Huang Ying | a08f82d | 2010-05-18 14:35:21 +0800 | [diff] [blame] | 1076 | 		goto err; | 
 | 1077 | 	} else if (ACPI_FAILURE(status)) { | 
 | 1078 | 		const char *msg = acpi_format_exception(status); | 
 | 1079 | 		pr_err(ERST_PFX "Failed to get table, %s\n", msg); | 
 | 1080 | 		rc = -EINVAL; | 
 | 1081 | 		goto err; | 
 | 1082 | 	} | 
 | 1083 |  | 
 | 1084 | 	rc = erst_check_table(erst_tab); | 
 | 1085 | 	if (rc) { | 
 | 1086 | 		pr_err(FW_BUG ERST_PFX "ERST table is invalid\n"); | 
 | 1087 | 		goto err; | 
 | 1088 | 	} | 
 | 1089 |  | 
 | 1090 | 	apei_resources_init(&erst_resources); | 
 | 1091 | 	erst_exec_ctx_init(&ctx); | 
 | 1092 | 	rc = apei_exec_collect_resources(&ctx, &erst_resources); | 
 | 1093 | 	if (rc) | 
 | 1094 | 		goto err_fini; | 
 | 1095 | 	rc = apei_resources_request(&erst_resources, "APEI ERST"); | 
 | 1096 | 	if (rc) | 
 | 1097 | 		goto err_fini; | 
 | 1098 | 	rc = apei_exec_pre_map_gars(&ctx); | 
 | 1099 | 	if (rc) | 
 | 1100 | 		goto err_release; | 
 | 1101 | 	rc = erst_get_erange(&erst_erange); | 
 | 1102 | 	if (rc) { | 
 | 1103 | 		if (rc == -ENODEV) | 
 | 1104 | 			pr_info(ERST_PFX | 
 | 1105 | 	"The corresponding hardware device or firmware implementation " | 
 | 1106 | 	"is not available.\n"); | 
 | 1107 | 		else | 
 | 1108 | 			pr_err(ERST_PFX | 
 | 1109 | 			       "Failed to get Error Log Address Range.\n"); | 
 | 1110 | 		goto err_unmap_reg; | 
 | 1111 | 	} | 
 | 1112 |  | 
 | 1113 | 	r = request_mem_region(erst_erange.base, erst_erange.size, "APEI ERST"); | 
 | 1114 | 	if (!r) { | 
 | 1115 | 		pr_err(ERST_PFX | 
 | 1116 | 		"Can not request iomem region <0x%16llx-0x%16llx> for ERST.\n", | 
 | 1117 | 		(unsigned long long)erst_erange.base, | 
 | 1118 | 		(unsigned long long)erst_erange.base + erst_erange.size); | 
 | 1119 | 		rc = -EIO; | 
 | 1120 | 		goto err_unmap_reg; | 
 | 1121 | 	} | 
 | 1122 | 	rc = -ENOMEM; | 
 | 1123 | 	erst_erange.vaddr = ioremap_cache(erst_erange.base, | 
 | 1124 | 					  erst_erange.size); | 
 | 1125 | 	if (!erst_erange.vaddr) | 
 | 1126 | 		goto err_release_erange; | 
 | 1127 |  | 
| Tony Luck | 0bb77c4 | 2011-01-03 14:22:11 -0800 | [diff] [blame] | 1128 | 	buf = kmalloc(erst_erange.size, GFP_KERNEL); | 
 | 1129 | 	mutex_init(&erst_info.buf_mutex); | 
 | 1130 | 	if (buf) { | 
 | 1131 | 		erst_info.buf = buf + sizeof(struct cper_pstore_record); | 
 | 1132 | 		erst_info.bufsize = erst_erange.size - | 
 | 1133 | 				    sizeof(struct cper_pstore_record); | 
 | 1134 | 		if (pstore_register(&erst_info)) { | 
 | 1135 | 			pr_info(ERST_PFX "Could not register with persistent store\n"); | 
 | 1136 | 			kfree(buf); | 
 | 1137 | 		} | 
 | 1138 | 	} | 
 | 1139 |  | 
| Huang Ying | a08f82d | 2010-05-18 14:35:21 +0800 | [diff] [blame] | 1140 | 	pr_info(ERST_PFX | 
 | 1141 | 	"Error Record Serialization Table (ERST) support is initialized.\n"); | 
 | 1142 |  | 
 | 1143 | 	return 0; | 
 | 1144 |  | 
 | 1145 | err_release_erange: | 
 | 1146 | 	release_mem_region(erst_erange.base, erst_erange.size); | 
 | 1147 | err_unmap_reg: | 
 | 1148 | 	apei_exec_post_unmap_gars(&ctx); | 
 | 1149 | err_release: | 
 | 1150 | 	apei_resources_release(&erst_resources); | 
 | 1151 | err_fini: | 
 | 1152 | 	apei_resources_fini(&erst_resources); | 
 | 1153 | err: | 
 | 1154 | 	erst_disable = 1; | 
 | 1155 | 	return rc; | 
 | 1156 | } | 
 | 1157 |  | 
 | 1158 | device_initcall(erst_init); |