summaryrefslogtreecommitdiffstats
path: root/c/src/lib/libcpu/powerpc/new-exceptions/bspsupport/ppc_exc_prologue.c
blob: ec109dc46f66e3dcc140cb9e95e3c15eafe7ba24 (plain) (blame)
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
/**
 * @file
 *
 * @ingroup ppc_exc
 *
 * @brief PowerPC Exceptions implementation.
 */

/*
 * Copyright (C) 2007 Till Straumann <strauman@slac.stanford.edu>
 *
 * Copyright (C) 2009-2012 embedded brains GmbH.
 *
 * The license and distribution terms for this file may be
 * found in the file LICENSE in this distribution or at
 * http://www.rtems.com/license/LICENSE.
 */

#include <string.h>

#include <bsp/vectors.h>

/* Offset into minimal prolog where vector number is hardcoded */
#define PPC_EXC_PROLOG_VEC_OFFSET 2

/* Symbols are defined by the linker */
extern const char ppc_exc_min_prolog_size [];
extern const char ppc_exc_tgpr_clr_prolog_size [];

/* Special prologue for handling register shadowing on 603-style CPUs */
extern const uint32_t ppc_exc_tgpr_clr_prolog [];

/*
 * Classic prologue which determines the vector dynamically from the offset
 * address. This must only be used for classic, synchronous exceptions with a
 * vector offset aligned on a 256-byte boundary.
 */
extern const uint32_t ppc_exc_min_prolog_auto [];

/* Minimal prologue templates */
extern const uint32_t ppc_exc_min_prolog_async_tmpl_std [];
extern const uint32_t ppc_exc_min_prolog_sync_tmpl_std [];
extern const uint32_t ppc_exc_min_prolog_async_tmpl_p405_crit [];
extern const uint32_t ppc_exc_min_prolog_sync_tmpl_p405_crit [];
extern const uint32_t ppc_exc_min_prolog_async_tmpl_bookE_crit [];
extern const uint32_t ppc_exc_min_prolog_sync_tmpl_bookE_crit [];
extern const uint32_t ppc_exc_min_prolog_sync_tmpl_e500_mchk [];
extern const uint32_t ppc_exc_min_prolog_async_tmpl_e500_mchk [];
extern const uint32_t ppc_exc_min_prolog_tmpl_naked [];
extern const uint32_t ppc_exc_min_prolog_async_tmpl_normal [];

static const uint32_t *const ppc_exc_prologue_templates [] = {
  [PPC_EXC_CLASSIC] = ppc_exc_min_prolog_sync_tmpl_std,
  [PPC_EXC_CLASSIC_ASYNC] = ppc_exc_min_prolog_async_tmpl_std,
  [PPC_EXC_405_CRITICAL] = ppc_exc_min_prolog_sync_tmpl_p405_crit,
  [PPC_EXC_405_CRITICAL_ASYNC] = ppc_exc_min_prolog_async_tmpl_p405_crit,
  [PPC_EXC_BOOKE_CRITICAL] = ppc_exc_min_prolog_sync_tmpl_bookE_crit,
  [PPC_EXC_BOOKE_CRITICAL_ASYNC] = ppc_exc_min_prolog_async_tmpl_bookE_crit,
  [PPC_EXC_E500_MACHCHK] = ppc_exc_min_prolog_sync_tmpl_e500_mchk,
  [PPC_EXC_E500_MACHCHK_ASYNC] = ppc_exc_min_prolog_async_tmpl_e500_mchk,
  [PPC_EXC_NAKED] = ppc_exc_min_prolog_tmpl_naked
};

static bool ppc_exc_create_branch_op(
  unsigned vector,
  void *vector_base,
  uint32_t *prologue,
  size_t prologue_size
)
{
  static const uintptr_t BRANCH_OP_CODE = 18 << 26;
/*  static const uintptr_t BRANCH_OP_LINK = 0x1; */
  static const uintptr_t BRANCH_OP_ABS = 0x2;
  static const uintptr_t BRANCH_OP_MSK = 0x3ffffff;
  size_t branch_op_index = prologue_size / 4 - 1;
  uintptr_t vector_address =
    (uintptr_t) ppc_exc_vector_address(vector, vector_base);
  uintptr_t branch_op_address = vector_address + 4 * branch_op_index;

  /* This value may have BRANCH_OP_LINK set */
  uintptr_t target_address = prologue [branch_op_index];

  uintptr_t branch_target_address = target_address - branch_op_address;

  /*
   * We prefer to use a relative branch.  This has the benefit that custom
   * minimal prologues in a read-only area are relocatable.
   */
  if ((branch_target_address & ~BRANCH_OP_MSK) != 0) {
    /* Target to far for relative branch (PC ± 32M) */
    if (target_address >= 0xfe000001 || target_address < 0x01fffffd) {
      /* Can use an absolute branch */
      branch_target_address = (target_address | BRANCH_OP_ABS) & BRANCH_OP_MSK;
    } else {
      return false;
    }
  }

  prologue [branch_op_index] = BRANCH_OP_CODE | branch_target_address;

  return true;
}

rtems_status_code ppc_exc_make_prologue(
  unsigned vector,
  void *vector_base,
  ppc_exc_category category,
  uint32_t *prologue,
  size_t *prologue_size
)
{
  const uint32_t *prologue_template = NULL;
  size_t prologue_template_size = 0;
  bool fixup_vector = false;

  if (!ppc_exc_is_valid_category(category)) {
    return RTEMS_INVALID_NUMBER;
  }

  if (
    ppc_cpu_has_shadowed_gprs()
      && (vector == ASM_60X_IMISS_VECTOR
        || vector == ASM_60X_DLMISS_VECTOR
        || vector == ASM_60X_DSMISS_VECTOR)
  ) {
    prologue_template = ppc_exc_tgpr_clr_prolog;
    prologue_template_size = (size_t) ppc_exc_tgpr_clr_prolog_size;
  } else if (
    category == PPC_EXC_CLASSIC
      && ppc_cpu_is_bookE() != PPC_BOOKE_STD
      && ppc_cpu_is_bookE() != PPC_BOOKE_E500
  ) {
    prologue_template = ppc_exc_min_prolog_auto;
    prologue_template_size = (size_t) ppc_exc_min_prolog_size;
  } else if (
    category == PPC_EXC_CLASSIC_ASYNC
      && ppc_cpu_is_bookE() == PPC_BOOKE_E500
      && (ppc_interrupt_get_disable_mask() & MSR_CE) == 0
  ) {
    prologue_template = ppc_exc_min_prolog_async_tmpl_normal;
#ifndef PPC_EXC_CONFIG_USE_FIXED_HANDLER
    prologue_template_size = (size_t) ppc_exc_min_prolog_size;
    fixup_vector = true;
#else /* PPC_EXC_CONFIG_USE_FIXED_HANDLER */
    prologue_template_size = 8;
#endif /* PPC_EXC_CONFIG_USE_FIXED_HANDLER */
  } else {
    prologue_template = ppc_exc_prologue_templates [category];
    prologue_template_size = (size_t) ppc_exc_min_prolog_size;
    fixup_vector = true;
  }

  if (prologue_template_size <= *prologue_size) {
    *prologue_size = prologue_template_size;

    memcpy(prologue, prologue_template, prologue_template_size);

    if (
      !ppc_exc_create_branch_op(
        vector,
        vector_base,
        prologue,
        prologue_template_size
      )
    ) {
      return RTEMS_INVALID_ADDRESS;
    }

    if (fixup_vector) {
      if (vector <= 0x7fffU) {
        prologue [PPC_EXC_PROLOG_VEC_OFFSET] =
          (prologue [PPC_EXC_PROLOG_VEC_OFFSET] & 0xffff8000U)
            | (vector & 0x7fffU);
      } else {
        return RTEMS_INVALID_ID;
      }
    }
  } else {
    return RTEMS_INVALID_SIZE;
  }

  return RTEMS_SUCCESSFUL;
}