Skip to content

Commit 593b377

Browse files
committed
target/riscv: fix read/write virtual memory across page boundaries
When read/write virtual addresses cross page boundaries, the physical addresses are not necessarily contiguous and need to call virt2phys again.
1 parent a4020f1 commit 593b377

File tree

2 files changed

+68
-12
lines changed

2 files changed

+68
-12
lines changed

src/target/riscv/riscv.c

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3061,6 +3061,27 @@ static int riscv_virt2phys(struct target *target, target_addr_t virtual, target_
30613061
virtual, physical);
30623062
}
30633063

3064+
static int check_access_request(struct target *target, target_addr_t address,
3065+
uint32_t size, uint32_t count, bool is_write)
3066+
{
3067+
const bool is_misaligned = address % size != 0;
3068+
// TODO: This assumes that size of each page is 4 KiB, which is not necessarily the case.
3069+
const bool crosses_page_boundary = RISCV_PGBASE(address + size * count - 1) != RISCV_PGBASE(address);
3070+
if (is_misaligned && crosses_page_boundary) {
3071+
int mmu_enabled;
3072+
int result = riscv_mmu(target, &mmu_enabled);
3073+
if (result != ERROR_OK)
3074+
return result;
3075+
if (mmu_enabled) {
3076+
LOG_TARGET_ERROR(target, "Mis-aligned memory %s (address=0x%" TARGET_PRIxADDR ", size=%d, count=%d)"
3077+
" would access an element across page boundary. This is not supported.",
3078+
is_write ? "write" : "read", address, size, count);
3079+
return ERROR_FAIL;
3080+
}
3081+
}
3082+
return ERROR_OK;
3083+
}
3084+
30643085
static int riscv_read_phys_memory(struct target *target, target_addr_t phys_address,
30653086
uint32_t size, uint32_t count, uint8_t *buffer)
30663087
{
@@ -3076,15 +3097,32 @@ static int riscv_read_memory(struct target *target, target_addr_t address,
30763097
return ERROR_OK;
30773098
}
30783099

3079-
target_addr_t physical_addr;
3080-
int result = target->type->virt2phys(target, address, &physical_addr);
3081-
if (result != ERROR_OK) {
3082-
LOG_TARGET_ERROR(target, "Address translation failed.");
3100+
int result = check_access_request(target, address, size, count, false);
3101+
if (result != ERROR_OK)
30833102
return result;
3084-
}
30853103

30863104
RISCV_INFO(r);
3087-
return r->read_memory(target, physical_addr, size, count, buffer, size);
3105+
uint32_t current_count = 0;
3106+
while (current_count < count) {
3107+
target_addr_t physical_addr;
3108+
result = target->type->virt2phys(target, address, &physical_addr);
3109+
if (result != ERROR_OK) {
3110+
LOG_TARGET_ERROR(target, "Address translation failed.");
3111+
return result;
3112+
}
3113+
3114+
/* TODO: For simplicity, this algorithm assumes the worst case - the smallest possible page size,
3115+
* which is 4 KiB. The algorithm can be improved to detect the real page size, and allow to use larger
3116+
* memory transfers and avoid extra unnecessary virt2phys address translations. */
3117+
uint32_t chunk_count = MIN(count - current_count, (RISCV_PGSIZE - RISCV_PGOFFSET(address)) / size);
3118+
result = r->read_memory(target, physical_addr, size, chunk_count, buffer + current_count * size, size);
3119+
if (result != ERROR_OK)
3120+
return result;
3121+
3122+
current_count += chunk_count;
3123+
address += chunk_count * size;
3124+
}
3125+
return ERROR_OK;
30883126
}
30893127

30903128
static int riscv_write_phys_memory(struct target *target, target_addr_t phys_address,
@@ -3104,17 +3142,32 @@ static int riscv_write_memory(struct target *target, target_addr_t address,
31043142
return ERROR_OK;
31053143
}
31063144

3107-
target_addr_t physical_addr;
3108-
int result = target->type->virt2phys(target, address, &physical_addr);
3109-
if (result != ERROR_OK) {
3110-
LOG_TARGET_ERROR(target, "Address translation failed.");
3145+
int result = check_access_request(target, address, size, count, true);
3146+
if (result != ERROR_OK)
31113147
return result;
3112-
}
31133148

31143149
struct target_type *tt = get_target_type(target);
31153150
if (!tt)
31163151
return ERROR_FAIL;
3117-
return tt->write_memory(target, physical_addr, size, count, buffer);
3152+
3153+
uint32_t current_count = 0;
3154+
while (current_count < count) {
3155+
target_addr_t physical_addr;
3156+
result = target->type->virt2phys(target, address, &physical_addr);
3157+
if (result != ERROR_OK) {
3158+
LOG_TARGET_ERROR(target, "Address translation failed.");
3159+
return result;
3160+
}
3161+
3162+
uint32_t chunk_count = MIN(count - current_count, (RISCV_PGSIZE - RISCV_PGOFFSET(address)) / size);
3163+
result = tt->write_memory(target, physical_addr, size, chunk_count, buffer + current_count * size);
3164+
if (result != ERROR_OK)
3165+
return result;
3166+
3167+
current_count += chunk_count;
3168+
address += chunk_count * size;
3169+
}
3170+
return ERROR_OK;
31183171
}
31193172

31203173
static const char *riscv_get_gdb_arch(const struct target *target)

src/target/riscv/riscv.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ struct riscv_program;
2929
#define RISCV_HGATP_MODE(xlen) ((xlen) == 32 ? HGATP32_MODE : HGATP64_MODE)
3030
#define RISCV_HGATP_PPN(xlen) ((xlen) == 32 ? HGATP32_PPN : HGATP64_PPN)
3131
#define RISCV_PGSHIFT 12
32+
#define RISCV_PGSIZE BIT(RISCV_PGSHIFT)
33+
#define RISCV_PGBASE(addr) ((addr) & ~(RISCV_PGSIZE - 1))
34+
#define RISCV_PGOFFSET(addr) ((addr) & (RISCV_PGSIZE - 1))
3235

3336
#define PG_MAX_LEVEL 5
3437

0 commit comments

Comments
 (0)