#define TB_JMP_CACHE_BITS 12
#define TB_JMP_CACHE_SIZE (1 << TB_JMP_CACHE_BITS)
-#define CPU_TLB_SIZE 256
+#define CPU_TLB_BITS 8
+#define CPU_TLB_SIZE (1 << CPU_TLB_BITS)
typedef struct CPUTLBEntry {
/* bit 31 to TARGET_PAGE_BITS : virtual address
bit 3 : indicates that the entry is invalid
bit 2..0 : zero
*/
- target_ulong address;
+ target_ulong addr_read;
+ target_ulong addr_write;
+ target_ulong addr_code;
/* addend to virtual address to get physical address */
target_phys_addr_t addend;
} CPUTLBEntry;
target_ulong mem_write_vaddr; /* target virtual addr at which the \
memory was written */ \
/* 0 = kernel, 1 = user */ \
- CPUTLBEntry tlb_read[2][CPU_TLB_SIZE]; \
- CPUTLBEntry tlb_write[2][CPU_TLB_SIZE]; \
+ CPUTLBEntry tlb_table[2][CPU_TLB_SIZE]; \
struct TranslationBlock *tb_jmp_cache[TB_JMP_CACHE_SIZE]; \
\
/* from this point: preserved by CPU reset */ \
void tb_invalidate_page_range(target_ulong start, target_ulong end);
void tlb_flush_page(CPUState *env, target_ulong addr);
void tlb_flush(CPUState *env, int flush_global);
-int tlb_set_page(CPUState *env, target_ulong vaddr,
- target_phys_addr_t paddr, int prot,
- int is_user, int is_softmmu);
+int tlb_set_page_exec(CPUState *env, target_ulong vaddr,
+ target_phys_addr_t paddr, int prot,
+ int is_user, int is_softmmu);
+static inline int tlb_set_page(CPUState *env, target_ulong vaddr,
+ target_phys_addr_t paddr, int prot,
+ int is_user, int is_softmmu)
+{
+ if (prot & PAGE_READ)
+ prot |= PAGE_EXEC;
+ return tlb_set_page_exec(env, vaddr, paddr, prot, is_user, is_softmmu);
+}
#define CODE_GEN_MAX_SIZE 65536
#define CODE_GEN_ALIGN 16 /* must be >= of the size of a icache line */
#else
#error unimplemented CPU
#endif
- if (__builtin_expect(env->tlb_read[is_user][index].address !=
+ if (__builtin_expect(env->tlb_table[is_user][index].addr_code !=
(addr & TARGET_PAGE_MASK), 0)) {
ldub_code(addr);
}
- pd = env->tlb_read[is_user][index].address & ~TARGET_PAGE_MASK;
+ pd = env->tlb_table[is_user][index].addr_code & ~TARGET_PAGE_MASK;
if (pd > IO_MEM_ROM) {
cpu_abort(env, "Trying to execute code outside RAM or ROM at 0x%08lx\n", addr);
}
- return addr + env->tlb_read[is_user][index].addend - (unsigned long)phys_ram_base;
+ return addr + env->tlb_table[is_user][index].addend - (unsigned long)phys_ram_base;
}
#endif
env->current_tb = NULL;
for(i = 0; i < CPU_TLB_SIZE; i++) {
- env->tlb_read[0][i].address = -1;
- env->tlb_write[0][i].address = -1;
- env->tlb_read[1][i].address = -1;
- env->tlb_write[1][i].address = -1;
+ env->tlb_table[0][i].addr_read = -1;
+ env->tlb_table[0][i].addr_write = -1;
+ env->tlb_table[0][i].addr_code = -1;
+ env->tlb_table[1][i].addr_read = -1;
+ env->tlb_table[1][i].addr_write = -1;
+ env->tlb_table[1][i].addr_code = -1;
}
memset (env->tb_jmp_cache, 0, TB_JMP_CACHE_SIZE * sizeof (void *));
static inline void tlb_flush_entry(CPUTLBEntry *tlb_entry, target_ulong addr)
{
- if (addr == (tlb_entry->address &
- (TARGET_PAGE_MASK | TLB_INVALID_MASK)))
- tlb_entry->address = -1;
+ if (addr == (tlb_entry->addr_read &
+ (TARGET_PAGE_MASK | TLB_INVALID_MASK)) ||
+ addr == (tlb_entry->addr_write &
+ (TARGET_PAGE_MASK | TLB_INVALID_MASK)) ||
+ addr == (tlb_entry->addr_code &
+ (TARGET_PAGE_MASK | TLB_INVALID_MASK))) {
+ tlb_entry->addr_read = -1;
+ tlb_entry->addr_write = -1;
+ tlb_entry->addr_code = -1;
+ }
}
void tlb_flush_page(CPUState *env, target_ulong addr)
addr &= TARGET_PAGE_MASK;
i = (addr >> TARGET_PAGE_BITS) & (CPU_TLB_SIZE - 1);
- tlb_flush_entry(&env->tlb_read[0][i], addr);
- tlb_flush_entry(&env->tlb_write[0][i], addr);
- tlb_flush_entry(&env->tlb_read[1][i], addr);
- tlb_flush_entry(&env->tlb_write[1][i], addr);
+ tlb_flush_entry(&env->tlb_table[0][i], addr);
+ tlb_flush_entry(&env->tlb_table[1][i], addr);
for(i = 0; i < TB_JMP_CACHE_SIZE; i++) {
tb = env->tb_jmp_cache[i];
unsigned long start, unsigned long length)
{
unsigned long addr;
- if ((tlb_entry->address & ~TARGET_PAGE_MASK) == IO_MEM_RAM) {
- addr = (tlb_entry->address & TARGET_PAGE_MASK) + tlb_entry->addend;
+ if ((tlb_entry->addr_write & ~TARGET_PAGE_MASK) == IO_MEM_RAM) {
+ addr = (tlb_entry->addr_write & TARGET_PAGE_MASK) + tlb_entry->addend;
if ((addr - start) < length) {
- tlb_entry->address = (tlb_entry->address & TARGET_PAGE_MASK) | IO_MEM_NOTDIRTY;
+ tlb_entry->addr_write = (tlb_entry->addr_write & TARGET_PAGE_MASK) | IO_MEM_NOTDIRTY;
}
}
}
start1 = start + (unsigned long)phys_ram_base;
for(env = first_cpu; env != NULL; env = env->next_cpu) {
for(i = 0; i < CPU_TLB_SIZE; i++)
- tlb_reset_dirty_range(&env->tlb_write[0][i], start1, length);
+ tlb_reset_dirty_range(&env->tlb_table[0][i], start1, length);
for(i = 0; i < CPU_TLB_SIZE; i++)
- tlb_reset_dirty_range(&env->tlb_write[1][i], start1, length);
+ tlb_reset_dirty_range(&env->tlb_table[1][i], start1, length);
}
#if !defined(CONFIG_SOFTMMU)
{
ram_addr_t ram_addr;
- if ((tlb_entry->address & ~TARGET_PAGE_MASK) == IO_MEM_RAM) {
- ram_addr = (tlb_entry->address & TARGET_PAGE_MASK) +
+ if ((tlb_entry->addr_write & ~TARGET_PAGE_MASK) == IO_MEM_RAM) {
+ ram_addr = (tlb_entry->addr_write & TARGET_PAGE_MASK) +
tlb_entry->addend - (unsigned long)phys_ram_base;
if (!cpu_physical_memory_is_dirty(ram_addr)) {
- tlb_entry->address |= IO_MEM_NOTDIRTY;
+ tlb_entry->addr_write |= IO_MEM_NOTDIRTY;
}
}
}
{
int i;
for(i = 0; i < CPU_TLB_SIZE; i++)
- tlb_update_dirty(&env->tlb_write[0][i]);
+ tlb_update_dirty(&env->tlb_table[0][i]);
for(i = 0; i < CPU_TLB_SIZE; i++)
- tlb_update_dirty(&env->tlb_write[1][i]);
+ tlb_update_dirty(&env->tlb_table[1][i]);
}
static inline void tlb_set_dirty1(CPUTLBEntry *tlb_entry,
unsigned long start)
{
unsigned long addr;
- if ((tlb_entry->address & ~TARGET_PAGE_MASK) == IO_MEM_NOTDIRTY) {
- addr = (tlb_entry->address & TARGET_PAGE_MASK) + tlb_entry->addend;
+ if ((tlb_entry->addr_write & ~TARGET_PAGE_MASK) == IO_MEM_NOTDIRTY) {
+ addr = (tlb_entry->addr_write & TARGET_PAGE_MASK) + tlb_entry->addend;
if (addr == start) {
- tlb_entry->address = (tlb_entry->address & TARGET_PAGE_MASK) | IO_MEM_RAM;
+ tlb_entry->addr_write = (tlb_entry->addr_write & TARGET_PAGE_MASK) | IO_MEM_RAM;
}
}
}
addr &= TARGET_PAGE_MASK;
i = (vaddr >> TARGET_PAGE_BITS) & (CPU_TLB_SIZE - 1);
- tlb_set_dirty1(&env->tlb_write[0][i], addr);
- tlb_set_dirty1(&env->tlb_write[1][i], addr);
+ tlb_set_dirty1(&env->tlb_table[0][i], addr);
+ tlb_set_dirty1(&env->tlb_table[1][i], addr);
}
/* add a new TLB entry. At most one entry for a given virtual address
is permitted. Return 0 if OK or 2 if the page could not be mapped
(can only happen in non SOFTMMU mode for I/O pages or pages
conflicting with the host address space). */
-int tlb_set_page(CPUState *env, target_ulong vaddr,
- target_phys_addr_t paddr, int prot,
- int is_user, int is_softmmu)
+int tlb_set_page_exec(CPUState *env, target_ulong vaddr,
+ target_phys_addr_t paddr, int prot,
+ int is_user, int is_softmmu)
{
PhysPageDesc *p;
unsigned long pd;
target_ulong address;
target_phys_addr_t addend;
int ret;
+ CPUTLBEntry *te;
p = phys_page_find(paddr >> TARGET_PAGE_BITS);
if (!p) {
}
#if defined(DEBUG_TLB)
printf("tlb_set_page: vaddr=" TARGET_FMT_lx " paddr=0x%08x prot=%x u=%d smmu=%d pd=0x%08lx\n",
- vaddr, paddr, prot, is_user, is_softmmu, pd);
+ vaddr, (int)paddr, prot, is_user, is_softmmu, pd);
#endif
ret = 0;
index = (vaddr >> TARGET_PAGE_BITS) & (CPU_TLB_SIZE - 1);
addend -= vaddr;
+ te = &env->tlb_table[is_user][index];
+ te->addend = addend;
if (prot & PAGE_READ) {
- env->tlb_read[is_user][index].address = address;
- env->tlb_read[is_user][index].addend = addend;
+ te->addr_read = address;
+ } else {
+ te->addr_read = -1;
+ }
+ if (prot & PAGE_EXEC) {
+ te->addr_code = address;
} else {
- env->tlb_read[is_user][index].address = -1;
- env->tlb_read[is_user][index].addend = -1;
+ te->addr_code = -1;
}
if (prot & PAGE_WRITE) {
if ((pd & ~TARGET_PAGE_MASK) == IO_MEM_ROM) {
/* ROM: access is ignored (same as unassigned) */
- env->tlb_write[is_user][index].address = vaddr | IO_MEM_ROM;
- env->tlb_write[is_user][index].addend = addend;
+ te->addr_write = vaddr | IO_MEM_ROM;
} else if ((pd & ~TARGET_PAGE_MASK) == IO_MEM_RAM &&
!cpu_physical_memory_is_dirty(pd)) {
- env->tlb_write[is_user][index].address = vaddr | IO_MEM_NOTDIRTY;
- env->tlb_write[is_user][index].addend = addend;
+ te->addr_write = vaddr | IO_MEM_NOTDIRTY;
} else {
- env->tlb_write[is_user][index].address = address;
- env->tlb_write[is_user][index].addend = addend;
+ te->addr_write = address;
}
} else {
- env->tlb_write[is_user][index].address = -1;
- env->tlb_write[is_user][index].addend = -1;
+ te->addr_write = -1;
}
}
#if !defined(CONFIG_SOFTMMU)
{
}
-int tlb_set_page(CPUState *env, target_ulong vaddr,
- target_phys_addr_t paddr, int prot,
- int is_user, int is_softmmu)
+int tlb_set_page_exec(CPUState *env, target_ulong vaddr,
+ target_phys_addr_t paddr, int prot,
+ int is_user, int is_softmmu)
{
return 0;
}
return val;
}
+/* warning: addr must be aligned */
+uint64_t ldq_phys(target_phys_addr_t addr)
+{
+ int io_index;
+ uint8_t *ptr;
+ uint64_t val;
+ unsigned long pd;
+ PhysPageDesc *p;
+
+ p = phys_page_find(addr >> TARGET_PAGE_BITS);
+ if (!p) {
+ pd = IO_MEM_UNASSIGNED;
+ } else {
+ pd = p->phys_offset;
+ }
+
+ if ((pd & ~TARGET_PAGE_MASK) > IO_MEM_ROM) {
+ /* I/O case */
+ io_index = (pd >> IO_MEM_SHIFT) & (IO_MEM_NB_ENTRIES - 1);
+#ifdef TARGET_WORDS_BIGENDIAN
+ val = (uint64_t)io_mem_read[io_index][2](io_mem_opaque[io_index], addr) << 32;
+ val |= io_mem_read[io_index][2](io_mem_opaque[io_index], addr + 4);
+#else
+ val = io_mem_read[io_index][2](io_mem_opaque[io_index], addr);
+ val |= (uint64_t)io_mem_read[io_index][2](io_mem_opaque[io_index], addr + 4) << 32;
+#endif
+ } else {
+ /* RAM case */
+ ptr = phys_ram_base + (pd & TARGET_PAGE_MASK) +
+ (addr & ~TARGET_PAGE_MASK);
+ val = ldq_p(ptr);
+ }
+ return val;
+}
+
/* XXX: optimize */
uint32_t ldub_phys(target_phys_addr_t addr)
{
return tswap16(val);
}
-/* XXX: optimize */
-uint64_t ldq_phys(target_phys_addr_t addr)
-{
- uint64_t val;
- cpu_physical_memory_read(addr, (uint8_t *)&val, 8);
- return tswap64(val);
-}
-
/* warning: addr must be aligned. The ram page is not masked as dirty
and the code inside is not invalidated. It is useful if the dirty
bits are used to track modified PTEs */
#define RES_TYPE int
#endif
+#if ACCESS_TYPE == 3
+#define ADDR_READ addr_code
+#else
+#define ADDR_READ addr_read
+#endif
DATA_TYPE REGPARM(1) glue(glue(__ld, SUFFIX), MMUSUFFIX)(target_ulong addr,
int is_user);
#if (DATA_SIZE <= 4) && (TARGET_LONG_BITS == 32) && defined(__i386__) && \
(ACCESS_TYPE <= 1) && defined(ASM_SOFTMMU)
+#define CPU_TLB_ENTRY_BITS 4
+
static inline RES_TYPE glue(glue(ld, USUFFIX), MEMSUFFIX)(target_ulong ptr)
{
int res;
"movl %%eax, %0\n"
"jmp 2f\n"
"1:\n"
- "addl 4(%%edx), %%eax\n"
+ "addl 12(%%edx), %%eax\n"
#if DATA_SIZE == 1
"movzbl (%%eax), %0\n"
#elif DATA_SIZE == 2
"2:\n"
: "=r" (res)
: "r" (ptr),
- "i" ((CPU_TLB_SIZE - 1) << 3),
- "i" (TARGET_PAGE_BITS - 3),
+ "i" ((CPU_TLB_SIZE - 1) << CPU_TLB_ENTRY_BITS),
+ "i" (TARGET_PAGE_BITS - CPU_TLB_ENTRY_BITS),
"i" (TARGET_PAGE_MASK | (DATA_SIZE - 1)),
- "m" (*(uint32_t *)offsetof(CPUState, tlb_read[CPU_MEM_INDEX][0].address)),
+ "m" (*(uint32_t *)offsetof(CPUState, tlb_table[CPU_MEM_INDEX][0].addr_read)),
"i" (CPU_MEM_INDEX),
"m" (*(uint8_t *)&glue(glue(__ld, SUFFIX), MMUSUFFIX))
: "%eax", "%ecx", "%edx", "memory", "cc");
#endif
"jmp 2f\n"
"1:\n"
- "addl 4(%%edx), %%eax\n"
+ "addl 12(%%edx), %%eax\n"
#if DATA_SIZE == 1
"movsbl (%%eax), %0\n"
#elif DATA_SIZE == 2
"2:\n"
: "=r" (res)
: "r" (ptr),
- "i" ((CPU_TLB_SIZE - 1) << 3),
- "i" (TARGET_PAGE_BITS - 3),
+ "i" ((CPU_TLB_SIZE - 1) << CPU_TLB_ENTRY_BITS),
+ "i" (TARGET_PAGE_BITS - CPU_TLB_ENTRY_BITS),
"i" (TARGET_PAGE_MASK | (DATA_SIZE - 1)),
- "m" (*(uint32_t *)offsetof(CPUState, tlb_read[CPU_MEM_INDEX][0].address)),
+ "m" (*(uint32_t *)offsetof(CPUState, tlb_table[CPU_MEM_INDEX][0].addr_read)),
"i" (CPU_MEM_INDEX),
"m" (*(uint8_t *)&glue(glue(__ld, SUFFIX), MMUSUFFIX))
: "%eax", "%ecx", "%edx", "memory", "cc");
"popl %%eax\n"
"jmp 2f\n"
"1:\n"
- "addl 4(%%edx), %%eax\n"
+ "addl 8(%%edx), %%eax\n"
#if DATA_SIZE == 1
"movb %b1, (%%eax)\n"
#elif DATA_SIZE == 2
/* NOTE: 'q' would be needed as constraint, but we could not use it
with T1 ! */
"r" (v),
- "i" ((CPU_TLB_SIZE - 1) << 3),
- "i" (TARGET_PAGE_BITS - 3),
+ "i" ((CPU_TLB_SIZE - 1) << CPU_TLB_ENTRY_BITS),
+ "i" (TARGET_PAGE_BITS - CPU_TLB_ENTRY_BITS),
"i" (TARGET_PAGE_MASK | (DATA_SIZE - 1)),
- "m" (*(uint32_t *)offsetof(CPUState, tlb_write[CPU_MEM_INDEX][0].address)),
+ "m" (*(uint32_t *)offsetof(CPUState, tlb_table[CPU_MEM_INDEX][0].addr_write)),
"i" (CPU_MEM_INDEX),
"m" (*(uint8_t *)&glue(glue(__st, SUFFIX), MMUSUFFIX))
: "%eax", "%ecx", "%edx", "memory", "cc");
addr = ptr;
index = (addr >> TARGET_PAGE_BITS) & (CPU_TLB_SIZE - 1);
is_user = CPU_MEM_INDEX;
- if (__builtin_expect(env->tlb_read[is_user][index].address !=
+ if (__builtin_expect(env->tlb_table[is_user][index].ADDR_READ !=
(addr & (TARGET_PAGE_MASK | (DATA_SIZE - 1))), 0)) {
res = glue(glue(__ld, SUFFIX), MMUSUFFIX)(addr, is_user);
} else {
- physaddr = addr + env->tlb_read[is_user][index].addend;
+ physaddr = addr + env->tlb_table[is_user][index].addend;
res = glue(glue(ld, USUFFIX), _raw)((uint8_t *)physaddr);
}
return res;
addr = ptr;
index = (addr >> TARGET_PAGE_BITS) & (CPU_TLB_SIZE - 1);
is_user = CPU_MEM_INDEX;
- if (__builtin_expect(env->tlb_read[is_user][index].address !=
+ if (__builtin_expect(env->tlb_table[is_user][index].ADDR_READ !=
(addr & (TARGET_PAGE_MASK | (DATA_SIZE - 1))), 0)) {
res = (DATA_STYPE)glue(glue(__ld, SUFFIX), MMUSUFFIX)(addr, is_user);
} else {
- physaddr = addr + env->tlb_read[is_user][index].addend;
+ physaddr = addr + env->tlb_table[is_user][index].addend;
res = glue(glue(lds, SUFFIX), _raw)((uint8_t *)physaddr);
}
return res;
}
#endif
+#if ACCESS_TYPE != 3
+
/* generic store macro */
static inline void glue(glue(st, SUFFIX), MEMSUFFIX)(target_ulong ptr, RES_TYPE v)
addr = ptr;
index = (addr >> TARGET_PAGE_BITS) & (CPU_TLB_SIZE - 1);
is_user = CPU_MEM_INDEX;
- if (__builtin_expect(env->tlb_write[is_user][index].address !=
+ if (__builtin_expect(env->tlb_table[is_user][index].addr_write !=
(addr & (TARGET_PAGE_MASK | (DATA_SIZE - 1))), 0)) {
glue(glue(__st, SUFFIX), MMUSUFFIX)(addr, v, is_user);
} else {
- physaddr = addr + env->tlb_write[is_user][index].addend;
+ physaddr = addr + env->tlb_table[is_user][index].addend;
glue(glue(st, SUFFIX), _raw)((uint8_t *)physaddr, v);
}
}
-#endif
+#endif /* ACCESS_TYPE != 3 */
+
+#endif /* !asm */
+
+#if ACCESS_TYPE != 3
#if DATA_SIZE == 8
static inline float64 glue(ldfq, MEMSUFFIX)(target_ulong ptr)
}
#endif /* DATA_SIZE == 4 */
+#endif /* ACCESS_TYPE != 3 */
+
#undef RES_TYPE
#undef DATA_TYPE
#undef DATA_STYPE
#undef DATA_SIZE
#undef CPU_MEM_INDEX
#undef MMUSUFFIX
+#undef ADDR_READ
#ifdef SOFTMMU_CODE_ACCESS
#define READ_ACCESS_TYPE 2
+#define ADDR_READ addr_code
#else
#define READ_ACCESS_TYPE 0
+#define ADDR_READ addr_read
#endif
static DATA_TYPE glue(glue(slow_ld, SUFFIX), MMUSUFFIX)(target_ulong addr,
/* XXX: could done more in memory macro in a non portable way */
index = (addr >> TARGET_PAGE_BITS) & (CPU_TLB_SIZE - 1);
redo:
- tlb_addr = env->tlb_read[is_user][index].address;
+ tlb_addr = env->tlb_table[is_user][index].ADDR_READ;
if ((addr & TARGET_PAGE_MASK) == (tlb_addr & (TARGET_PAGE_MASK | TLB_INVALID_MASK))) {
- physaddr = addr + env->tlb_read[is_user][index].addend;
+ physaddr = addr + env->tlb_table[is_user][index].addend;
if (tlb_addr & ~TARGET_PAGE_MASK) {
/* IO access */
if ((addr & (DATA_SIZE - 1)) != 0)
index = (addr >> TARGET_PAGE_BITS) & (CPU_TLB_SIZE - 1);
redo:
- tlb_addr = env->tlb_read[is_user][index].address;
+ tlb_addr = env->tlb_table[is_user][index].ADDR_READ;
if ((addr & TARGET_PAGE_MASK) == (tlb_addr & (TARGET_PAGE_MASK | TLB_INVALID_MASK))) {
- physaddr = addr + env->tlb_read[is_user][index].addend;
+ physaddr = addr + env->tlb_table[is_user][index].addend;
if (tlb_addr & ~TARGET_PAGE_MASK) {
/* IO access */
if ((addr & (DATA_SIZE - 1)) != 0)
index = (addr >> TARGET_PAGE_BITS) & (CPU_TLB_SIZE - 1);
redo:
- tlb_addr = env->tlb_write[is_user][index].address;
+ tlb_addr = env->tlb_table[is_user][index].addr_write;
if ((addr & TARGET_PAGE_MASK) == (tlb_addr & (TARGET_PAGE_MASK | TLB_INVALID_MASK))) {
- physaddr = addr + env->tlb_write[is_user][index].addend;
+ physaddr = addr + env->tlb_table[is_user][index].addend;
if (tlb_addr & ~TARGET_PAGE_MASK) {
/* IO access */
if ((addr & (DATA_SIZE - 1)) != 0)
index = (addr >> TARGET_PAGE_BITS) & (CPU_TLB_SIZE - 1);
redo:
- tlb_addr = env->tlb_write[is_user][index].address;
+ tlb_addr = env->tlb_table[is_user][index].addr_write;
if ((addr & TARGET_PAGE_MASK) == (tlb_addr & (TARGET_PAGE_MASK | TLB_INVALID_MASK))) {
- physaddr = addr + env->tlb_write[is_user][index].addend;
+ physaddr = addr + env->tlb_table[is_user][index].addend;
if (tlb_addr & ~TARGET_PAGE_MASK) {
/* IO access */
if ((addr & (DATA_SIZE - 1)) != 0)
#undef SUFFIX
#undef USUFFIX
#undef DATA_SIZE
+#undef ADDR_READ