aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--HISTORY.md47
-rw-r--r--cp_regs.h111
-rw-r--r--kernel.c153
-rw-r--r--loader_stage1.ld2
-rw-r--r--strings.h9
-rw-r--r--translation_table_descriptors.h137
6 files changed, 445 insertions, 14 deletions
diff --git a/HISTORY.md b/HISTORY.md
new file mode 100644
index 0000000..3d912be
--- /dev/null
+++ b/HISTORY.md
@@ -0,0 +1,47 @@
+#How we were writing it, from the beginning#
+
+So, the goal is to learn and program the MMU of the RaspberryPI.
+
+To be able to start quickly, we, errrmm... copy-pasted some code from [RaspberryPI bare metal tutorial on wiki OSdev](https://wiki.osdev.org/ARM_RaspberryPi_Tutorial_C) with plans to replace these bits with our own later. "An easy way" - one would say. Not really. The wiki code, although useful, is far from working. Consider this part:
+
+ enum
+ {
+ // The GPIO registers base address.
+ switch (raspi) {
+ case 2:
+ case 3: GPIO_BASE = 0x3F200000; break; // for raspi2 & 3
+ case 4: GPIO_BASE = 0xFE200000; break; // for raspi4
+ default: GPIO_BASE = 0x20200000; break; // for raspi1, raspi zero etc.
+ }
+ // more stuff here
+ };
+
+Switch statement inside of an enum?! First thought? That it's some kind of nonstandard extension to C. Well, no. No matter how many own things gcc adds to the language, the ability to do THIS is not one of them. This is just one example.
+
+It's really weird that someone wrote this and made it available online. There were also other, simillar problems with that code. I.e. there was double padding, which resulted in the initial routine being loaded at address 0x10000, not 0x8000...
+
+Maybe the wiki OSdev authors don't actually want people to directly reuse their code or they want to stop inexperienced programmers from doing this kind of low-level bare-metal stuff too easily.
+
+Nevertheless, we had to fix the bugs and then we could run the kernel.elf under qemu emulating the RPI2 and receive the (virtual) uart output.
+
+The real hardware we have is RPI3B, but the versions of qemu available in some distributions don't yet have support for emulating RPI3 and compiling from source seemed like o good way to waste time we don't have (+ we were not going to use aarch64 until we get the 32-bit version working).
+
+There were more problems with running the raw binary version of kernel (which we had to get working if we wanted to ever use real hardware). As it turned out after 1.5 hour of static analysis of the image in radare2 - qemu doesn't load the binary image at 0x8000 as a real RPI would, but rather at 0x10000. This migh also explain the need for double padding the wiki asm and linker code caused. After finding that out, we could finally understand how the ld script and objcopy work and we didn't need to use dd on the image anymore. We temporarily changed the entry point address to 0x10000.
+
+We wrote few lines to check for paging support based on wiki info and we moved uart code into separate files. Then we came up with the idea of bootloader.
+
+Once we would have started working on real RPI, we would have had to take the SD out of it, put into the pc, write the kernel to it and move it from PC back to the PI on every kernel compilation. Sounds terrible, doesn't it? And aside from that it takes a lot of time, it also kills the SD.
+
+We decided to send the kernel through UART. In fact, there already exists a bootloader, called Raspbootin, that does exactly that. This time, however, we wanted to write this ourselves (especially that it seemed rather easy). So, the PI (or qemu) boots the loader instead of the actual kernel, the kernel (prepended with 4 bytes describing it's size) is piped through the uart (or to qemu's stdin), the loader writes the received data into the memory and jumps to it. One problem is that if loader gets loaded at 0x8000, then it cannot just write the kernel at 0x8000, because it would overwrite it's own code. That's why we made a "2nd stage" of bootloader, which is embedded in the main loader executable. The loader copies the stage2 to some other address, e.g. 0x4000 and jumps to it. The 2nd stage then initializes the uart, receives the kernel, writes it at 0x8000 and jumps to it.
+
+By writing the bootloader we also removed the need to change the kernel entry address depending on the environment. The bootloader entry address has to bo changed instead, but this is less problematic, since the bootloader can just sit there on the SD card undisturbed, while we're working on the kernel.
+
+We also finally ran the code on real hardware. And we used [RPI Open Firmare](https://github.com/christinaa/rpi-open-firmware) for that (at last something it CAN be used for). Aside from kernel (which, btw., is supposed to be linux and has to ba named "zImage"), the firmawere expected 2 files (device tree, cmdline.txt). It also turned out to load the kernel at even different address. All this should be changed in the firmware itself **TODO**, but the original version is enough to work on for now.
+
+Another problem appeared when trying to use the bootloader on real RPi. Mainly - how does one pipe the image through UART? GNU Screen we successfully used for communicating with the board doesn't seem to support this. We found a tool called "socat", which was available from the repo and could be used instead. So the makefile rule would first pipe the image using socat and then run screen for the usual io. An additional uart_getc() had to be added at the beginning of kernel main function, so that it's first output wouldn't get lost before screen would start. Socat solution also required the the UART USB adapter and PI's power supply to be replugged in a specific order to work, so we started working on a different solution using libRS232 for UART communication from the PS.
+
+Only at this point we eventually started working on the relevant part - the MMU. It was surprisingly difficult to achieve something in this field. Information on the wiki was incomplete, just as information in the first few reference manuals picked. The source that eventually proved to be good enough is *ARM Architecture Reference Manual ARMv7-A and ARMv7-R edition*. Also, the configuration of the MMU turned out to be way more complicated than first thought. After some hours of digging through dozens of options changed in various coprocessor registers we eventually came up with some code to enable the MMU, with a simple, (obvoiusly) flat mapping of memory and after finding out the bugs (forgetting to also map the part of memory where UART periphs are accessible, forgetting to mark the descriptor as describing section, creating the translation table at the same place the stack was) we got it working in qemu.
+
+The above could be possibly achieved easier, by using others' existing code, but doing the whole project with the Copy-Paste method seemed like a bad idea.
+
+Knowing a good, working sequence of actions needed for enabling the MMU, we could start writing it a cleaner way - using unions and structs with bitfields, which make the code a lot more readable compared to when bit masks and bit shifts are used for work on coprocessor register constents and translation tables entries. \ No newline at end of file
diff --git a/cp_regs.h b/cp_regs.h
new file mode 100644
index 0000000..98a8bb8
--- /dev/null
+++ b/cp_regs.h
@@ -0,0 +1,111 @@
+#include <stdint.h>
+
+
+// SCTLR - System Control Register
+
+// Wandering why I didn't typedef this struct with fields?
+// That's because
+typedef union
+{
+ uint32_t raw;
+ struct
+ {
+ uint32_t M : 1; // bit 0
+ uint32_t A : 1; // bit 1
+ uint32_t C : 1; // bit 2
+ uint32_t Bits_4_3 : 2; // bits 3:4
+ uint32_t CP15BEN : 1; // bit 5
+ uint32_t Bit_6 : 1; // bit 6
+ uint32_t B : 1; // bit 7
+ uint32_t Bits_9_8 : 2; // bits 9:8
+ uint32_t SW : 1; // bit 10
+ uint32_t Z : 1; // bit 11
+ uint32_t I : 1; // bit 12
+ uint32_t V : 1; // bit 13
+ uint32_t RR : 1; // bit 14
+ uint32_t Bit_15 : 1; // bit 15
+ uint32_t Bit_16 : 1; // bit 16
+ uint32_t HA : 1; // bit 17
+ uint32_t Bit_18 : 1; // bit 18
+ uint32_t WXN : 1; // bit 19
+ uint32_t UWXN : 1; // bit 20
+ uint32_t FI : 1; // bit 21
+ uint32_t U : 1; // bit 22
+ uint32_t Bit_23 : 1; // bit 23
+ uint32_t VE : 1; // bit 24
+ uint32_t EE : 1; // bit 25
+ uint32_t Bit_26 : 1; // bit 26
+ uint32_t NMFI : 1; // bit 27
+ uint32_t TRE : 1; // bit 28
+ uint32_t AFE : 1; // bit 29
+ uint32_t TE : 1; // bit 30
+ uint32_t Bit_31 : 1; // bit 31
+ } fields;
+} SCTLR_t;
+
+// DACR - Domain Access Control Register
+// DACR holds 16 pairs of bits; each pair represents access
+// permissions to a respective memory domain. There's no point
+// declaring a union for this.
+typedef uint32_t DACR_t;
+
+inline static uint8_t domain_permissions(DACR_t DACR_contents,
+ int domain)
+{
+ return (DACR_contents << (30 - 2 * domain)) >> 30;
+}
+
+inline static DACR_t set_domain_permissions(DACR_t DACR_contents,
+ int domain,
+ uint8_t permissions)
+{
+ uint32_t clear_domain_permissions_mask = ~(0b11 << (2 * domain));
+ uint32_t new_domain_permissions_mask =
+ ((uint32_t) permissions) << (2 * domain);
+
+ return (DACR_contents & clear_domain_permissions_mask)
+ | new_domain_permissions_mask;
+}
+
+#define DOMAIN_NO_ACCESS 0b00
+#define DOMAIN_CLIENT_ACCESS 0b01
+#define DOMAIN_RESERVED 0b10
+#define DOMAIN_MANAGER_ACCESS 0b11
+
+// TTBR - Translation Table Base Register (there're 2 of them with
+// (almost) the same structure)
+
+// A field in TTBCR determines how long the address field is in TTBR0,
+// but here we'll ignore this and just assume the greatest possible
+// length of this field (18 bits). In TTBR1 it's always 18 bits.
+typedef union
+{
+ uint32_t raw;
+ struct
+ {
+
+ uint32_t C : 1; // bit 0
+ uint32_t S : 1; // bit 1
+ uint32_t IMP : 1; // bit 2
+ uint32_t RGN : 2; // bits 4:3
+ uint32_t NOS : 1; // bit 5
+ uint32_t IRGN_0 : 1; // bit 6
+ uint32_t Bits_13_6 : 7; // bits 13:7
+ uint32_t Bits_31_14 : 18; // bits 31:14
+ // with multiprocessing extensions the cacheable bit becomes
+ // upper IRGN bit
+#define IRGN_1 C
+
+ // i'm not sure 'interprocess region bits' is the right name,
+ // I'm just guessing (by analogy to RGN -> region bits)
+#define CACHEABLE_BIT C
+#define INTERPROCESS_REGION_BITS_1 IRGN_1
+#define SHAREABLE_BIT S
+#define IMPLEMENTATION_DEFINED_BIT IMP
+#define REGION_BITS_1_0 RGN
+#define INTERPROCESS_REGION_BITS_0 IRGN_0
+#define NON_OUTER_SHAREABLE_BIT NOS
+#define TRANSLATION_TABLE_BASE_ADDRESS Bits_31_14
+ } fields;
+} TTBR_t;
+
diff --git a/kernel.c b/kernel.c
index dbc45e4..8a06565 100644
--- a/kernel.c
+++ b/kernel.c
@@ -1,6 +1,13 @@
#include "uart.h"
#include "cpsr.h"
+#include "strings.h"
+#include "translation_table_descriptors.h"
+#include "cp_regs.h"
+extern char __end;
+
+void enabling_code_from_the_net(void);
+
void kernel_main(uint32_t r0, uint32_t r1, uint32_t atags)
{
// Declare as unused
@@ -39,26 +46,146 @@ void kernel_main(uint32_t r0, uint32_t r1, uint32_t atags)
// processor mode
asm("mrs %0, cpsr" : "=r" (CPSR) :: "memory");
- char *mode;
+ char *mode_name;
switch(read_processor_mode())
{
- case 0x10 : mode = "User (PL0)\r\n"; break;
- case 0x11 : mode = "FIQ (PL1)\r\n"; break;
- case 0x12 : mode = "IRQ (PL1)\r\n"; break;
- case 0x13 : mode = "Supervisor (PL1)\r\n"; break;
- case 0x16 : mode = "Monitor (PL1)\r\n"; break;
- case 0x17 : mode = "Abort (PL1)\r\n"; break;
- case 0x1a : mode = "Hyp (PL2)\r\n"; break;
- case 0x1b : mode = "Undefined (PL1)\r\n"; break;
- case 0x1f : mode = "System (PL1)\r\n"; break;
- default : mode = "Unknown mode\r\n"; break;
+ case MODE_USER : mode_name = "User (PL0)\r\n"; break;
+ case MODE_FIQ : mode_name = "FIQ (PL1)\r\n"; break;
+ case MODE_IRQ : mode_name = "IRQ (PL1)\r\n"; break;
+ case MODE_SUPERVISOR : mode_name = "Supervisor (PL1)\r\n"; break;
+ case MODE_MONITOR : mode_name = "Monitor (PL1)\r\n"; break;
+ case MODE_ABORT : mode_name = "Abort (PL1)\r\n"; break;
+ case MODE_HYPERVISOR : mode_name = "Hyp (PL2)\r\n"; break;
+ case MODE_UNDEFINED : mode_name = "Undefined (PL1)\r\n"; break;
+ case MODE_SYSTEM : mode_name = "System (PL1)\r\n"; break;
+ default : mode_name = "Unknown mode\r\n"; break;
}
+ uart_puts("current mode: ");
+ uart_puts(mode_name);
+
+ uart_puts("setting mode to system (PL1)...\r\n");
set_system_mode();
- uart_puts(mode);
+ char bits[33]; // for printing uint32_t bit values
+
+ // compute translation table base address
+ // translation table shall start at first 2^14-bytes aligned
+ // address after the kernel image
+ uint32_t kernel_end = (uint32_t) &__end;
+ uint32_t translation_table_base =
+ ((kernel_end - 1) & ~((uint32_t) 0x3fff)) + (uint32_t) 0x4000;
+
+ uint32_to_bits(translation_table_base, bits);
+ uart_puts("binary representation of chosen"
+ " lvl1 translation table address: ");
+ uart_puts(bits); uart_puts("\n\r");
+
+ // flat map all memory
+ uart_puts("preparing translation table\n\r");
+ short_descriptor_t *translation_table =
+ (short_descriptor_t*) translation_table_base;
+
+ for (uint32_t i = 0; i < 4096; i++)
+ translation_table[i].section_fields =
+ (short_section_descriptor_t) {
+ .SECTION_BASE_ADDRESS_31_20 = i,
+ .SECTION_OR_SUPERSECTION_BIT = DESCRIBES_SECTION,
+ .ACCESS_PERMISSIONS_2 = AP_2_0_MODEL_RW_PL1 >> 2,
+ .ACCESS_PERMISSIONS_1_0 = AP_2_0_MODEL_RW_PL1 & 0b011,
+ .DESCRIPTOR_TYPE_2 =
+ SHORT_DESCRIPTOR_SECTION_OR_SUPERSECTION >> 1,
+ // rest of fields are 0s
+ };
+
+ // meddle with domain settings
+ uart_puts("setting domain0 to client access"
+ " and blocking other domains\n\r");
+
+ DACR_t DACR = 0;
+ DACR = set_domain_permissions(DACR, 0, DOMAIN_CLIENT_ACCESS);
+ for (int i = 1; i < 16; i++)
+ DACR = set_domain_permissions(DACR, i, DOMAIN_NO_ACCESS);
+
+ // the above should do the same as this:
+ // DACR = 1;
+
+ asm("mcr p15, 0, %0, c3, c0, 0" :: "r" (DACR));
+
+ // meddle with SCTLR, which determines how some bits in
+ // table descriptors work and also controls caches
+ // we don't want to use access flag, so we set AFE to 0
+ // we don't want TEX remap, so we set TRE to 0
+ // we also disable data and instruction caches and the MMU
+
+ // some of this is redundant (i.e. MMU should already be disabled)
+ uart_puts("setting C, I, AFE and TRE to 0 in SCTLR\n\r");
+
+ SCTLR_t SCTLR;
+ asm("mrc p15, 0, %0, c1, c0, 0" : "=r" (SCTLR.raw));
+
+ SCTLR.fields.M = 0; // disable MMU
+ SCTLR.fields.C = 0; // disable data cache
+ SCTLR.fields.I = 0; // disable instruction cache
+ SCTLR.fields.TRE = 0; // disable TEX remap
+ SCTLR.fields.AFE = 0; // disable access flag usage
+ asm("mcr p15, 0, %0, c1, c0, 0\n\r"
+ "isb" :: "r" (SCTLR.raw) : "memory");
+
+ // TODO: move invalidation instructions to some header as inlines
+
+ uart_puts("invalidating instruction cache, branch prediction,"
+ " and entire main TLB\n\r");
+
+ // invalidate instruction cache
+ asm("mcr p15, 0, r0, c7, c5, 0\n\r" // r0 gets ignored
+ "isb" ::: "memory");
+
+ // invalidate branch-prediction
+ asm("mcr p15, 0, r0, c7, c5, 6\n\r" // r0 - same as above
+ "isb" ::: "memory");
+
+ // invalidate main Translation Lookup Buffer
+ asm("mcr p15, 0, %0, c8, c7, 0\n\r"
+ "isb" :: "r" (0) : "memory");
+
+ // now set TTBCR to use TTBR0 exclusively
+ uart_puts("Setting TTBCR.N to 0, so that"
+ " TTBR0 is used everywhere\n\r");
+
+ uint32_t TTBCR = 0;
+ asm("mcr p15, 0, %0, c2, c0, 2" :: "r" (TTBCR));
+
+ // Now do stuff with TTBR0
+ TTBR_t TTBR0;
+ TTBR0.raw = 0;
+ TTBR0.fields.TRANSLATION_TABLE_BASE_ADDRESS =
+ translation_table_base >> 14;
+ // rest of TTBR0 remains 0s
+
+ asm("mcr p15, 0, %0, c2, c0, 0" :: "r" (TTBR0.raw));
+
+ // enable MMU
+ uart_puts("enabling the MMU\n\r");
+
+ // redundant - we already have SCTLR contents in the variable
+ // asm("mrc p15, 0, %0, c1, c0, 0" : "=r" (SCTLR.raw));
+
+ SCTLR.fields.M = 1;
+
+ asm("mcr p15, 0, %0, c1, c0, 0\n\r"
+ "isb" :: "r" (SCTLR.raw) : "memory");
while (1)
- uart_putc(uart_getc());
+ {
+ char c;
+ switch(c = uart_getc())
+ {
+ case '\r':
+ uart_putc('\n');
+ default:
+ uart_putc(c);
+ }
+ }
}
diff --git a/loader_stage1.ld b/loader_stage1.ld
index 507e367..18fe477 100644
--- a/loader_stage1.ld
+++ b/loader_stage1.ld
@@ -11,7 +11,7 @@ SECTIONS
/* rpi-open-firmware, on the other hand, loads it at 0x2000000 */
/* (and this should be not-so-hard to change by modifying the */
- /* firmware */
+ /* firmware) */
. = 0x2000000;
diff --git a/strings.h b/strings.h
new file mode 100644
index 0000000..7721f63
--- /dev/null
+++ b/strings.h
@@ -0,0 +1,9 @@
+#include <stdint.h>
+
+void uint32_to_bits(uint32_t number, char *buf)
+{
+ for (int i = 0; i < 32; i++)
+ buf[i] = ((number >> (32 - i - 1)) & 1) ? '1' : '0';
+
+ buf[32] = '\0';
+}
diff --git a/translation_table_descriptors.h b/translation_table_descriptors.h
new file mode 100644
index 0000000..8f92473
--- /dev/null
+++ b/translation_table_descriptors.h
@@ -0,0 +1,137 @@
+#include <stdint.h>
+
+// ARM lets you choose between 32-bit abd 64-bit translation table
+// descriptors (called short and long descriptors respectively).
+// The format of the descriptor differs depending on what it describes
+// (section, supersection, a page table, etc...) and table of which
+// level of lookup it belongs to.
+
+// Even in case of descriptor of a specified type (e.g. short-format
+// section descriptor), a given field inside it may have different
+// meanings depending on settings in coprocessor registers... (yeah, ARM
+// looks a bit messy... all for backward compatibility, i guess)
+
+typedef struct
+{
+ uint32_t PXN : 1; // bit 0
+ uint32_t Bit_1 : 1; // bit 1
+ uint32_t B : 1; // bit 2
+ uint32_t C : 1; // bit 3
+ uint32_t XN : 1; // bit 4
+ uint32_t Domain_3_0 : 4; // bits 8:5
+ uint32_t Bit_9 : 1; // bit 9
+ uint32_t AP_1_0 : 2; // bit 11:10
+ uint32_t TEX_2_0 : 3; // bits 14:12
+ uint32_t AP_2 : 1; // bit 15
+ uint32_t S : 1; // bit 16
+ uint32_t nG : 1; // bit 17
+ uint32_t Bit_18 : 1; // bit 18
+ uint32_t NS : 1; // bit 19
+ uint32_t PA_31_20 : 12; // bits 31:20
+#define PRIVILEGED_EXECUTE_NEVER_BIT PXN
+#define DESCRIPTOR_TYPE_2 Bit_1
+#define BUFFERABLE_BIT B
+#define CACHEABLE_BIT C
+#define EXECUTE_NEVER_BIT XN
+#define DOMAIN_3_0 Domain_3_0
+#define IMPLEMENTATION_DEFINED_BIT Bit_9
+#define ACCESS_PERMISSIONS_1_0 AP_1_0
+#define TYPE_EXTENSION_2_0 TEX_2_0
+#define ACCESS_PERMISSIONS_2 AP_2
+#define SHAREABLE_BIT S
+#define NON_GLOBAL_BIT nG
+#define SECTION_OR_SUPERSECTION_BIT Bit_18
+#define NON_SECURE_BIT NS
+#define SECTION_BASE_ADDRESS_31_20 PA_31_20
+} short_section_descriptor_t;
+
+
+// How AP[2:0] is used depends on settings in SCTLR.AFE
+
+// Meaning of #define'd names below:
+// RW - read-write
+// RO - read-only
+// PL1 - a given permission applies to privilege level PL1
+// PL2 - a given permission applies to privilege level PL2
+// ALL - a given permission applies to both privilege levels
+// If only a permission for one privilege level is given in the name,
+// it means the other one has no access.
+
+// When SCTLR.AFE is 0 (access flag not used) and short-format
+// descritor table is used, the following access permission control
+// schema for AP[2:0] is used:
+#define AP_2_0_MODEL_NO_ACCESS 0b000
+#define AP_2_0_MODEL_RW_PL1 0b001
+#define AP_2_0_MODEL_RW_PL1_RO_PL0 0b010
+#define AP_2_0_MODEL_RW_ALL 0b011
+#define AP_2_0_MODEL_RESERVED 0b100
+#define AP_2_0_MODEL_RO_PL1 0b101
+#define AP_2_0_MODEL_RO_ALL_DEPRECATED 0b110 // use 0b111 instead
+#define AP_2_0_MODEL_RO_ALL 0b111 // reserved in VMSAv6
+// TODO: the #define's of RO_ALL and reserved could be done
+// conditionally depending on the VMSA version available (either give
+// the programmer #including this the possibility to #define their
+// VMSA version or assume the VMSA version respective to the ARM
+// version we're compiling against)
+
+// Values for bit18, that determines whether a descriptor describes
+// section or supersection:
+#define DESCRIBES_SECTION 0b0
+#define DESCRIBES_SUPERSECTION 0b1
+
+typedef struct
+{
+ uint32_t PXN : 1; // bit 0
+ uint32_t Bit_1 : 1; // bit 1
+ uint32_t B : 1; // bit 2
+ uint32_t C : 1; // bit 3
+ uint32_t XN : 1; // bit 4
+ uint32_t PA_39_36 : 4; // bits 8:5
+ uint32_t Bit_9 : 1; // bit 9
+ uint32_t AP_1_0 : 2; // bit 11:10
+ uint32_t TEX_2_0 : 3; // bits 14:12
+ uint32_t AP_2 : 1; // bit 15
+ uint32_t S : 1; // bit 16
+ uint32_t nG : 1; // bit 17
+ uint32_t Bit_18 : 1; // bit 18
+ uint32_t NS : 1; // bit 19
+ uint32_t PA_35_32 : 4; // bits 23:20
+ uint32_t PA_31_24 : 8; // bits 31:24
+ // most of these are already defined the same for section
+ //#define PRIVILEGED_EXECUTE_NEVER_BIT PXN
+ //#define DESCRIPTOR_TYPE_2 Bit_1
+ //#define BUFFERABLE_BIT B
+ //#define CACHEABLE_BIT C
+ //#define EXECUTE_NEVER_BIT XN
+#define SUPERSECTION_BASE_ADDRESS_39_36 PA_39_36
+ //#define IMPLEMENTATION_DEFINED_BIT Bit_9
+ //#define ACCESS_PERMISSIONS_1_0 AP_1_0
+ //#define TYPE_EXTENSION_2_0 TEX_2_0
+ //#define ACCESS_PERMISSIONS_2 AP_2
+ //#define SHAREABLE_BIT S
+ //#define NON_GLOBAL_BIT nG
+ //#define SECTION_OR_SUPERSECTION_BIT Bit_18
+ //#define NON_SECURE_BIT NS
+#define SUPERSECTION_BASE_ADDRESS_35_32 PA_35_32
+#define SUPERSECTION_BASE_ADDRESS_31_24 PA_31_24
+} short_supersection_descriptor_t;
+
+typedef union
+{
+ uint32_t raw;
+ uint8_t descriptor_type;
+
+ short_section_descriptor_t section_fields;
+ short_supersection_descriptor_t supersection_fields;
+ // more to come here (e.g. short_supersection_descriptor_t)
+} short_descriptor_t;
+
+// possible values of descriptor_type field:
+#define SHORT_DESCRIPTOR_INVALID 0b00
+#define SHORT_DESCRIPTOR_PAGE_TABLE 0b01
+#define SHORT_DESCRIPTOR_SECTION_OR_SUPERSECTION 0b10
+#define SHORT_DESCRIPTOR_SECTION_OR_SUPERSECTION_PXN 0b11
+// on an implementation that does not support the PXN attribute
+// 0b11 should not be used
+#define SHORT_DESCRIPTOR_RESERVED 0b11
+