drm/radeon/kms: catch atombios infinite loop and break out of it
authorJerome Glisse <jglisse@redhat.com>
Tue, 2 Mar 2010 19:37:52 +0000 (20:37 +0100)
committerDave Airlie <airlied@redhat.com>
Sun, 14 Mar 2010 23:35:45 +0000 (09:35 +1000)
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>
drivers/gpu/drm/radeon/atom.c
drivers/gpu/drm/radeon/atom.h

index d75788feac6c6a64fe042e0e7d3e4a446d8b2e26..b7fe660985c43efe602c05ec3b273158ba33e0c6 100644 (file)
 
 typedef struct {
        struct atom_context *ctx;
-
        uint32_t *ps, *ws;
        int ps_shift;
        uint16_t start;
+       unsigned last_jump;
+       unsigned long last_jump_jiffies;
+       bool abort;
 } atom_exec_context;
 
 int atom_debug = 0;
-static void 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);
+static int atom_execute_table_locked(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] =
     { 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)
 {
        int idx = U8((*ptr)++);
+       int r = 0;
+
        if (idx < ATOM_TABLE_NAMES_CNT)
                SDEBUG("   table: %d (%s)\n", idx, atom_table_names[idx]);
        else
                SDEBUG("   table: %d\n", 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)
@@ -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)
 {
        int execute = 0, target = U16(*ptr);
+       unsigned long cjiffies;
+
        (*ptr) += 2;
        switch (arg) {
        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)
                SDEBUG("   taken: %s\n", execute ? "yes" : "no");
        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;
+       }
 }
 
 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_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 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;
 
        if (!base)
-               return;
+               return -EINVAL;
 
        len = CU16(base + ATOM_CT_SIZE_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.start = base;
        ectx.ps = params;
+       ectx.abort = false;
+       ectx.last_jump = 0;
        if (ws)
                ectx.ws = kzalloc(4 * ws, GFP_KERNEL);
        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);
                else
                        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)
                        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)
                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);
        /* reset reg block */
        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;
        /* reset io mode */
        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);
+       return r;
 }
 
 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))
                return 1;
-       atom_execute_table(ctx, ATOM_CMD_INIT, ps);
-
-       return 0;
+       return atom_execute_table(ctx, ATOM_CMD_INIT, ps);
 }
 
 void atom_destroy(struct atom_context *ctx)
index bc73781423a17599fadf9e2bfa1bca556b5504ca..1b2626314804fdb27b36dbb2b88fb6f5ea28e196 100644 (file)
@@ -140,7 +140,7 @@ struct atom_context {
 extern int atom_debug;
 
 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 *);
 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);