drm/radeon/kms: catch atombios infinite loop and break out of it

In somecase the atombios code might lead to infinite loop because
the GPU is in broken state, this patch track the jump history and
will abort atombios execution if we are stuck executing the same
jump for more than 1sec. Note that otherwise in some case we might
enter an infinite loop in the kernel context which is bad.

Signed-off-by: Jerome Glisse <jglisse@redhat.com>
Signed-off-by: Dave Airlie <airlied@redhat.com>
This commit is contained in:
Jerome Glisse 2010-03-02 20:37:52 +01:00 committed by Dave Airlie
parent 338e2b1d57
commit c21b0fe6de
2 changed files with 48 additions and 13 deletions

View File

@ -52,15 +52,17 @@
typedef struct { typedef struct {
struct atom_context *ctx; struct atom_context *ctx;
uint32_t *ps, *ws; uint32_t *ps, *ws;
int ps_shift; int ps_shift;
uint16_t start; uint16_t start;
unsigned last_jump;
unsigned long last_jump_jiffies;
bool abort;
} atom_exec_context; } atom_exec_context;
int atom_debug = 0; int atom_debug = 0;
static void atom_execute_table_locked(struct atom_context *ctx, int index, uint32_t * params); static int atom_execute_table_locked(struct atom_context *ctx, int index, uint32_t * params);
void atom_execute_table(struct atom_context *ctx, int index, uint32_t * params); int atom_execute_table(struct atom_context *ctx, int index, uint32_t * params);
static uint32_t atom_arg_mask[8] = static uint32_t atom_arg_mask[8] =
{ 0xFFFFFFFF, 0xFFFF, 0xFFFF00, 0xFFFF0000, 0xFF, 0xFF00, 0xFF0000, { 0xFFFFFFFF, 0xFFFF, 0xFFFF00, 0xFFFF0000, 0xFF, 0xFF00, 0xFF0000,
@ -604,12 +606,17 @@ static void atom_op_beep(atom_exec_context *ctx, int *ptr, int arg)
static void atom_op_calltable(atom_exec_context *ctx, int *ptr, int arg) static void atom_op_calltable(atom_exec_context *ctx, int *ptr, int arg)
{ {
int idx = U8((*ptr)++); int idx = U8((*ptr)++);
int r = 0;
if (idx < ATOM_TABLE_NAMES_CNT) if (idx < ATOM_TABLE_NAMES_CNT)
SDEBUG(" table: %d (%s)\n", idx, atom_table_names[idx]); SDEBUG(" table: %d (%s)\n", idx, atom_table_names[idx]);
else else
SDEBUG(" table: %d\n", idx); SDEBUG(" table: %d\n", idx);
if (U16(ctx->ctx->cmd_table + 4 + 2 * idx)) if (U16(ctx->ctx->cmd_table + 4 + 2 * idx))
atom_execute_table_locked(ctx->ctx, idx, ctx->ps + ctx->ps_shift); r = atom_execute_table_locked(ctx->ctx, idx, ctx->ps + ctx->ps_shift);
if (r) {
ctx->abort = true;
}
} }
static void atom_op_clear(atom_exec_context *ctx, int *ptr, int arg) static void atom_op_clear(atom_exec_context *ctx, int *ptr, int arg)
@ -673,6 +680,8 @@ static void atom_op_eot(atom_exec_context *ctx, int *ptr, int arg)
static void atom_op_jump(atom_exec_context *ctx, int *ptr, int arg) static void atom_op_jump(atom_exec_context *ctx, int *ptr, int arg)
{ {
int execute = 0, target = U16(*ptr); int execute = 0, target = U16(*ptr);
unsigned long cjiffies;
(*ptr) += 2; (*ptr) += 2;
switch (arg) { switch (arg) {
case ATOM_COND_ABOVE: case ATOM_COND_ABOVE:
@ -700,8 +709,25 @@ static void atom_op_jump(atom_exec_context *ctx, int *ptr, int arg)
if (arg != ATOM_COND_ALWAYS) if (arg != ATOM_COND_ALWAYS)
SDEBUG(" taken: %s\n", execute ? "yes" : "no"); SDEBUG(" taken: %s\n", execute ? "yes" : "no");
SDEBUG(" target: 0x%04X\n", target); SDEBUG(" target: 0x%04X\n", target);
if (execute) if (execute) {
if (ctx->last_jump == (ctx->start + target)) {
cjiffies = jiffies;
if (time_after(cjiffies, ctx->last_jump_jiffies)) {
cjiffies -= ctx->last_jump_jiffies;
if ((jiffies_to_msecs(cjiffies) > 1000)) {
DRM_ERROR("atombios stuck in loop for more than 1sec aborting\n");
ctx->abort = true;
}
} else {
/* jiffies wrap around we will just wait a little longer */
ctx->last_jump_jiffies = jiffies;
}
} else {
ctx->last_jump = ctx->start + target;
ctx->last_jump_jiffies = jiffies;
}
*ptr = ctx->start + target; *ptr = ctx->start + target;
}
} }
static void atom_op_mask(atom_exec_context *ctx, int *ptr, int arg) static void atom_op_mask(atom_exec_context *ctx, int *ptr, int arg)
@ -1104,7 +1130,7 @@ static struct {
atom_op_shr, ATOM_ARG_MC}, { atom_op_shr, ATOM_ARG_MC}, {
atom_op_debug, 0},}; atom_op_debug, 0},};
static void atom_execute_table_locked(struct atom_context *ctx, int index, uint32_t * params) static int atom_execute_table_locked(struct atom_context *ctx, int index, uint32_t * params)
{ {
int base = CU16(ctx->cmd_table + 4 + 2 * index); int base = CU16(ctx->cmd_table + 4 + 2 * index);
int len, ws, ps, ptr; int len, ws, ps, ptr;
@ -1112,7 +1138,7 @@ static void atom_execute_table_locked(struct atom_context *ctx, int index, uint3
atom_exec_context ectx; atom_exec_context ectx;
if (!base) if (!base)
return; return -EINVAL;
len = CU16(base + ATOM_CT_SIZE_PTR); len = CU16(base + ATOM_CT_SIZE_PTR);
ws = CU8(base + ATOM_CT_WS_PTR); ws = CU8(base + ATOM_CT_WS_PTR);
@ -1125,6 +1151,8 @@ static void atom_execute_table_locked(struct atom_context *ctx, int index, uint3
ectx.ps_shift = ps / 4; ectx.ps_shift = ps / 4;
ectx.start = base; ectx.start = base;
ectx.ps = params; ectx.ps = params;
ectx.abort = false;
ectx.last_jump = 0;
if (ws) if (ws)
ectx.ws = kzalloc(4 * ws, GFP_KERNEL); ectx.ws = kzalloc(4 * ws, GFP_KERNEL);
else else
@ -1137,6 +1165,11 @@ static void atom_execute_table_locked(struct atom_context *ctx, int index, uint3
SDEBUG("%s @ 0x%04X\n", atom_op_names[op], ptr - 1); SDEBUG("%s @ 0x%04X\n", atom_op_names[op], ptr - 1);
else else
SDEBUG("[%d] @ 0x%04X\n", op, ptr - 1); SDEBUG("[%d] @ 0x%04X\n", op, ptr - 1);
if (ectx.abort) {
DRM_ERROR("atombios stuck executing %04X (len %d, WS %d, PS %d) @ 0x%04X\n",
base, len, ws, ps, ptr - 1);
return -EINVAL;
}
if (op < ATOM_OP_CNT && op > 0) if (op < ATOM_OP_CNT && op > 0)
opcode_table[op].func(&ectx, &ptr, opcode_table[op].func(&ectx, &ptr,
@ -1152,10 +1185,13 @@ static void atom_execute_table_locked(struct atom_context *ctx, int index, uint3
if (ws) if (ws)
kfree(ectx.ws); kfree(ectx.ws);
return 0;
} }
void atom_execute_table(struct atom_context *ctx, int index, uint32_t * params) int atom_execute_table(struct atom_context *ctx, int index, uint32_t * params)
{ {
int r;
mutex_lock(&ctx->mutex); mutex_lock(&ctx->mutex);
/* reset reg block */ /* reset reg block */
ctx->reg_block = 0; ctx->reg_block = 0;
@ -1163,8 +1199,9 @@ void atom_execute_table(struct atom_context *ctx, int index, uint32_t * params)
ctx->fb_base = 0; ctx->fb_base = 0;
/* reset io mode */ /* reset io mode */
ctx->io_mode = ATOM_IO_MM; ctx->io_mode = ATOM_IO_MM;
atom_execute_table_locked(ctx, index, params); r = atom_execute_table_locked(ctx, index, params);
mutex_unlock(&ctx->mutex); mutex_unlock(&ctx->mutex);
return r;
} }
static int atom_iio_len[] = { 1, 2, 3, 3, 3, 3, 4, 4, 4, 3 }; static int atom_iio_len[] = { 1, 2, 3, 3, 3, 3, 4, 4, 4, 3 };
@ -1248,9 +1285,7 @@ int atom_asic_init(struct atom_context *ctx)
if (!CU16(ctx->cmd_table + 4 + 2 * ATOM_CMD_INIT)) if (!CU16(ctx->cmd_table + 4 + 2 * ATOM_CMD_INIT))
return 1; return 1;
atom_execute_table(ctx, ATOM_CMD_INIT, ps); return atom_execute_table(ctx, ATOM_CMD_INIT, ps);
return 0;
} }
void atom_destroy(struct atom_context *ctx) void atom_destroy(struct atom_context *ctx)

View File

@ -140,7 +140,7 @@ struct atom_context {
extern int atom_debug; extern int atom_debug;
struct atom_context *atom_parse(struct card_info *, void *); struct atom_context *atom_parse(struct card_info *, void *);
void atom_execute_table(struct atom_context *, int, uint32_t *); int atom_execute_table(struct atom_context *, int, uint32_t *);
int atom_asic_init(struct atom_context *); int atom_asic_init(struct atom_context *);
void atom_destroy(struct atom_context *); void atom_destroy(struct atom_context *);
void atom_parse_data_header(struct atom_context *ctx, int index, uint16_t *size, uint8_t *frev, uint8_t *crev, uint16_t *data_start); void atom_parse_data_header(struct atom_context *ctx, int index, uint16_t *size, uint8_t *frev, uint8_t *crev, uint16_t *data_start);