Message ID | 20180829015047.7355-14-rashmica.g@gmail.com |
---|---|
State | Superseded |
Headers | show |
Series | Basic gdbserver for POWER8 | expand |
Context | Check | Description |
---|---|---|
snowpatch_ozlabs/apply_patch | success | master/apply_patch Successfully applied |
snowpatch_ozlabs/build-multiarch | fail | Test build-multiarch on branch master |
On Wed, 29 Aug 2018 11:50:41 +1000 Rashmica Gupta <rashmica.g@gmail.com> wrote: > From: Alistair Popple <alistair@popple.id.au> > > I have changed a few bits here and there but this patch is largely > authored by Alistair Popple. So what's missing for P9? enable_attn? Anything else? > diff --git a/libpdbg/target.c b/libpdbg/target.c > index 88ae94a..997db3b 100644 > --- a/libpdbg/target.c > +++ b/libpdbg/target.c > @@ -391,3 +391,19 @@ void pdbg_target_priv_set(struct pdbg_target *target, void *priv) > { > target->priv = priv; > } > + > +int poll_target(struct pdbg_target *target, uint64_t addr, uint64_t mask, uint64_t *value) > +{ > + uint64_t val, val1; > + int rc = 0; > + > + do { > + pib_read(target, addr, &val); > + rc = pib_read(target, addr, &val1); > + if (val != val1) > + continue; > + } while ((val & mask) != *value); > + > + *value = val; > + return rc; > +} Is this used anywhere? > + > +/* 32 registers represented as 16 char hex numbers with null-termination */ > +#define REG_DATA_SIZE (32*16+1) > +static void get_gprs(uint64_t *stack, void *priv) > +{ > + char data[REG_DATA_SIZE] = ""; > + uint64_t regs[32]; > + int i; > + > + for (i = 0; i < 32; i++) { > + if (ram_getgpr(thread_target, i, ®s[i])) > + PR_ERROR("Error reading register %d\n", i); > + printf("r%d = 0x%016lx\n", i, regs[i]); > + snprintf(data + i*16, 17, "%016lx", __builtin_bswap64(regs[i])); > + } > + > + send_response(fd, data); > +} It's likely to be a bit faster to batch the ram setup here. If you do a ram_setup, you can run multiple ram operations then end with a ram_destroy and it will just do the setup and destroy once. > + > +static void get_spr(uint64_t *stack, void *priv) > +{ > + char data[REG_DATA_SIZE]; > + uint64_t value; > + > + switch (stack[0]) { > + case 0x40: > + /* Get PC/NIA */ It would be nice to have some #defines for these, then you don't even need the comment. > + if (ram_getnia(thread_target, &value)) > + PR_ERROR("Error reading NIA\n"); > + snprintf(data, REG_DATA_SIZE, "%016lx", __builtin_bswap64(value)); > + send_response(fd, data); > + break; > + > + case 0x43: > + /* Get LR */ > + if (ram_getspr(thread_target, 8, &value)) > + PR_ERROR("Error reading LR\n"); > + snprintf(data, REG_DATA_SIZE, "%016lx", __builtin_bswap64(value)); > + send_response(fd, data); > + break; > + > + default: > + send_response(fd, "xxxxxxxxxxxxxxxx"); > + break; > + } > +} > + > +#define MAX_DATA 0x1000 > + > +/* Returns a real address to use with adu_getmem or -1UL if we > + * couldn't determine a real address. At the moment we only deal with > + * kernel linear mapping but in future we could walk that page > + * tables. */ > +static uint64_t get_addr(uint64_t addr) > +{ > + if (GETFIELD(PPC_BITMASK(0, 3), addr) == 0xc) > + /* Assume all 0xc... addresses are part of the linux linear map */ > + addr &= ~PPC_BITMASK(0, 1); > + else > + addr = -1UL; > + > + return addr; > +} > + > +static void get_mem(uint64_t *stack, void *priv) > +{ > + struct pdbg_target *adu; > + uint64_t addr, len, linear_map; > + int i, err = 0; > + uint64_t data[MAX_DATA/sizeof(uint64_t)]; > + char result[2*MAX_DATA]; > + > + /* stack[0] is the address and stack[1] is the length */ > + addr = stack[0]; > + len = stack[1]; > + > + pdbg_for_each_class_target("adu", adu) { > + if (pdbg_target_probe(adu) == PDBG_TARGET_ENABLED) > + break; > + } > + > + if (adu == NULL) { > + PR_ERROR("ADU NOT FOUND\n"); > + err=3; > + goto out; > + } > + > + if (len > MAX_DATA) { > + printf("Too much memory requested, truncating\n"); > + len = MAX_DATA; > + } > + > + if (!addr) { > + err = 2; > + goto out; > + } > + > + linear_map = get_addr(addr); > + if (linear_map != -1UL) { > + if (adu_getmem(adu, addr, (uint8_t *) data, len)) { > + PR_ERROR("Unable to read memory\n"); > + err = 1; > + } > + } else { > + /* Virtual address */ > + for (i = 0; i < len; i += sizeof(uint64_t)) { > + if (ram_getmem(thread_target, addr, &data[i/sizeof(uint64_t)])) { > + PR_ERROR("Fault reading memory\n"); > + err = 2; > + break; > + } > + } Yeah, ram_getmem seems to do horrible, horrible things on P9. If you look at it wrong it will checkstop and guard out the core. I'd say we will need to do virtual memory translation by hand, sadly. > + printf("put_mem 0x%016lx = 0x%016lx\n", addr, stack[2]); > + > + if (len == 4 && stack[2] == 0x0810827d) { > + /* According to linux-ppc-low.c gdb only uses this > + * op-code for sw break points so we replace it with > + * the correct attn opcode which is what we need for > + * breakpoints. > + * > + * TODO: Upstream a patch to gdb so that it uses the > + * right opcode for baremetal debug. */ > + PR_INFO("Breakpoint opcode detected, replacing with attn\n"); > + data = attn_opcode; > + > + /* Need to enable the attn instruction in HID0 */ > + if (thread->enable_attn(thread_target)) > + goto out; Do you disable attn on detach or break if it was enabled? How does this work here, do we save the instruction, replace it with break, and then restore the instruction after the break? Do we need to do any kind of icache flushing here? Icache is coherent on POWER, the flush I think is just needed to synchronize the ifetch pipeline. That's probably all pretty well shut down while we are stopped and ramming so that's probably not a problem. > + } else > + stack[2] = __builtin_bswap64(stack[2]) >> 32; > + > + if (adu_putmem(adu, addr, data, len)) { > + PR_ERROR("Unable to write memory\n"); > + err = 3; > + } > + > +out: > + if (err) > + send_response(fd, "E01"); > + else > + send_response(fd, "OK"); > +} > + > +static void v_conts(uint64_t *stack, void *priv) > +{ > + ram_step_thread(thread_target, 1); > + send_response(fd, "S05"); > +} > + > +#define VCONT_POLL_DELAY 100000 > +static void v_contc(uint64_t *stack, void *priv) > +{ > + ram_start_thread(thread_target); > + state = SIGNAL_WAIT; > +} > + > +static void interrupt(uint64_t *stack, void *priv) > +{ > + printf("Interrupt\n"); > + ram_stop_thread(thread_target); > + send_response(fd, "S05"); These would be more good #define candidates I think. Also in some cases you have a newline in the response, and not others. Intentional? > +static int gdbserver(uint16_t port) > +{ > + struct pdbg_target *target; > + > + for_each_class_target("thread", target) { > + if (!target_selected(target)) > + continue; > + if (pdbg_target_probe(target) == PDBG_TARGET_ENABLED) > + break; > + } > + assert(!strcmp(target->class, "thread")); > + // Temporary until I can get this working a bit smoother on p9 > + if (strcmp(target->compatible, "ibm,power8-thread")) { > + PR_ERROR("GDBSERVER is only tested on POWER8\n"); > + return -1; > + } I would say since making a lot of improvements to power9 direct controls, this should just about work out of the box there too. Thanks, Nick
On 29/08/18 18:36, Nicholas Piggin wrote: > On Wed, 29 Aug 2018 11:50:41 +1000 > Rashmica Gupta <rashmica.g@gmail.com> wrote: > >> From: Alistair Popple <alistair@popple.id.au> >> >> I have changed a few bits here and there but this patch is largely >> authored by Alistair Popple. > So what's missing for P9? enable_attn? Anything else? There's a couple of things that I'm trying to work out regarding P9. - we can't mtnia from an arbitrary register in ramming mode (can only do it from the link register). - p9 doesn't play happily with breakpoints yet. I have tried clearing the icache as you suggested below but that didn't seem to help. I think there was something else, but it escapes me atm. >> diff --git a/libpdbg/target.c b/libpdbg/target.c >> index 88ae94a..997db3b 100644 >> --- a/libpdbg/target.c >> +++ b/libpdbg/target.c >> @@ -391,3 +391,19 @@ void pdbg_target_priv_set(struct pdbg_target *target, void *priv) >> { >> target->priv = priv; >> } >> + >> +int poll_target(struct pdbg_target *target, uint64_t addr, uint64_t mask, uint64_t *value) >> +{ >> + uint64_t val, val1; >> + int rc = 0; >> + >> + do { >> + pib_read(target, addr, &val); >> + rc = pib_read(target, addr, &val1); >> + if (val != val1) >> + continue; >> + } while ((val & mask) != *value); >> + >> + *value = val; >> + return rc; >> +} > Is this used anywhere? Not anymore :) thanks for picking this up. >> + >> +/* 32 registers represented as 16 char hex numbers with null-termination */ >> +#define REG_DATA_SIZE (32*16+1) >> +static void get_gprs(uint64_t *stack, void *priv) >> +{ >> + char data[REG_DATA_SIZE] = ""; >> + uint64_t regs[32]; >> + int i; >> + >> + for (i = 0; i < 32; i++) { >> + if (ram_getgpr(thread_target, i, ®s[i])) >> + PR_ERROR("Error reading register %d\n", i); >> + printf("r%d = 0x%016lx\n", i, regs[i]); >> + snprintf(data + i*16, 17, "%016lx", __builtin_bswap64(regs[i])); >> + } >> + >> + send_response(fd, data); >> +} > It's likely to be a bit faster to batch the ram setup here. If you do a > ram_setup, you can run multiple ram operations then end with a > ram_destroy and it will just do the setup and destroy once. Good point. Will do this. >> + >> +static void get_spr(uint64_t *stack, void *priv) >> +{ >> + char data[REG_DATA_SIZE]; >> + uint64_t value; >> + >> + switch (stack[0]) { >> + case 0x40: >> + /* Get PC/NIA */ > It would be nice to have some #defines for these, then you don't even need > the comment. Touche. >> + if (ram_getnia(thread_target, &value)) >> + PR_ERROR("Error reading NIA\n"); >> + snprintf(data, REG_DATA_SIZE, "%016lx", __builtin_bswap64(value)); >> + send_response(fd, data); >> + break; >> + >> + case 0x43: >> + /* Get LR */ >> + if (ram_getspr(thread_target, 8, &value)) >> + PR_ERROR("Error reading LR\n"); >> + snprintf(data, REG_DATA_SIZE, "%016lx", __builtin_bswap64(value)); >> + send_response(fd, data); >> + break; >> + >> + default: >> + send_response(fd, "xxxxxxxxxxxxxxxx"); >> + break; >> + } >> +} >> + >> +#define MAX_DATA 0x1000 >> + >> +/* Returns a real address to use with adu_getmem or -1UL if we >> + * couldn't determine a real address. At the moment we only deal with >> + * kernel linear mapping but in future we could walk that page >> + * tables. */ >> +static uint64_t get_addr(uint64_t addr) >> +{ >> + if (GETFIELD(PPC_BITMASK(0, 3), addr) == 0xc) >> + /* Assume all 0xc... addresses are part of the linux linear map */ >> + addr &= ~PPC_BITMASK(0, 1); >> + else >> + addr = -1UL; >> + >> + return addr; >> +} >> + >> +static void get_mem(uint64_t *stack, void *priv) >> +{ >> + struct pdbg_target *adu; >> + uint64_t addr, len, linear_map; >> + int i, err = 0; >> + uint64_t data[MAX_DATA/sizeof(uint64_t)]; >> + char result[2*MAX_DATA]; >> + >> + /* stack[0] is the address and stack[1] is the length */ >> + addr = stack[0]; >> + len = stack[1]; >> + >> + pdbg_for_each_class_target("adu", adu) { >> + if (pdbg_target_probe(adu) == PDBG_TARGET_ENABLED) >> + break; >> + } >> + >> + if (adu == NULL) { >> + PR_ERROR("ADU NOT FOUND\n"); >> + err=3; >> + goto out; >> + } >> + >> + if (len > MAX_DATA) { >> + printf("Too much memory requested, truncating\n"); >> + len = MAX_DATA; >> + } >> + >> + if (!addr) { >> + err = 2; >> + goto out; >> + } >> + >> + linear_map = get_addr(addr); >> + if (linear_map != -1UL) { >> + if (adu_getmem(adu, addr, (uint8_t *) data, len)) { >> + PR_ERROR("Unable to read memory\n"); >> + err = 1; >> + } >> + } else { >> + /* Virtual address */ >> + for (i = 0; i < len; i += sizeof(uint64_t)) { >> + if (ram_getmem(thread_target, addr, &data[i/sizeof(uint64_t)])) { >> + PR_ERROR("Fault reading memory\n"); >> + err = 2; >> + break; >> + } >> + } > Yeah, ram_getmem seems to do horrible, horrible things on P9. If you > look at it wrong it will checkstop and guard out the core. > > I'd say we will need to do virtual memory translation by hand, sadly. > >> + printf("put_mem 0x%016lx = 0x%016lx\n", addr, stack[2]); >> + >> + if (len == 4 && stack[2] == 0x0810827d) { >> + /* According to linux-ppc-low.c gdb only uses this >> + * op-code for sw break points so we replace it with >> + * the correct attn opcode which is what we need for >> + * breakpoints. >> + * >> + * TODO: Upstream a patch to gdb so that it uses the >> + * right opcode for baremetal debug. */ >> + PR_INFO("Breakpoint opcode detected, replacing with attn\n"); >> + data = attn_opcode; >> + >> + /* Need to enable the attn instruction in HID0 */ >> + if (thread->enable_attn(thread_target)) >> + goto out; > Do you disable attn on detach or break if it was enabled? Nope, I probably should do that! > How does > this work here, do we save the instruction, replace it with break, and > then restore the instruction after the break? GDB handles this for us. GDB reads and saves the value at the addr. It asks us to write the attn instruction to the addr, and then tells us to continue execution. When we detect that we have paused (in the SIGNAL_WAIT case of the poll function) we send a trap signal to GDB. GDB then asks us to write back the original value at the address. > > Do we need to do any kind of icache flushing here? Icache is coherent > on POWER, the flush I think is just needed to synchronize the ifetch > pipeline. That's probably all pretty well shut down while we are > stopped and ramming so that's probably not a problem. I wasn't entirely sure if icache flushing was applicable in ramming mode and it didn't seem to make a difference in behaviour iirc. Will double check though. >> + } else >> + stack[2] = __builtin_bswap64(stack[2]) >> 32; >> + >> + if (adu_putmem(adu, addr, data, len)) { >> + PR_ERROR("Unable to write memory\n"); >> + err = 3; >> + } >> + >> +out: >> + if (err) >> + send_response(fd, "E01"); >> + else >> + send_response(fd, "OK"); >> +} >> + >> +static void v_conts(uint64_t *stack, void *priv) >> +{ >> + ram_step_thread(thread_target, 1); >> + send_response(fd, "S05"); >> +} >> + >> +#define VCONT_POLL_DELAY 100000 >> +static void v_contc(uint64_t *stack, void *priv) >> +{ >> + ram_start_thread(thread_target); >> + state = SIGNAL_WAIT; >> +} >> + >> +static void interrupt(uint64_t *stack, void *priv) >> +{ >> + printf("Interrupt\n"); >> + ram_stop_thread(thread_target); >> + send_response(fd, "S05"); > These would be more good #define candidates I think. Also in some cases > you have a newline in the response, and not others. Intentional? I agree and no, not intentional :/ >> +static int gdbserver(uint16_t port) >> +{ >> + struct pdbg_target *target; >> + >> + for_each_class_target("thread", target) { >> + if (!target_selected(target)) >> + continue; >> + if (pdbg_target_probe(target) == PDBG_TARGET_ENABLED) >> + break; >> + } >> + assert(!strcmp(target->class, "thread")); >> + // Temporary until I can get this working a bit smoother on p9 >> + if (strcmp(target->compatible, "ibm,power8-thread")) { >> + PR_ERROR("GDBSERVER is only tested on POWER8\n"); >> + return -1; >> + } > I would say since making a lot of improvements to power9 direct > controls, this should just about work out of the box there too. What are the direct controls? > Thanks, > Nick >
On Thu, 30 Aug 2018 10:18:30 +1000 Rashmica <rashmica.g@gmail.com> wrote: > On 29/08/18 18:36, Nicholas Piggin wrote: > > > On Wed, 29 Aug 2018 11:50:41 +1000 > > Rashmica Gupta <rashmica.g@gmail.com> wrote: > > > >> From: Alistair Popple <alistair@popple.id.au> > >> > >> I have changed a few bits here and there but this patch is largely > >> authored by Alistair Popple. > > So what's missing for P9? enable_attn? Anything else? > > There's a couple of things that I'm trying to work out regarding P9. > > - we can't mtnia from an arbitrary register in ramming mode (can only do it > from the link register). I had a bit of a hack at this. I'll try to dig it out. > > - p9 doesn't play happily with breakpoints yet. I have tried clearing > the icache as you suggested below but that didn't seem to help. Yeah I didn't *think* icache flush would be an issue but it was worth a quick try. PC workbook doesn't seem to give any insight. Could make the break point facility optional until we sort it out? > > I think there was something else, but it escapes me atm. Okay well that answers my question. You needn't get P9 working before the P8 support is merged. > > How does > > this work here, do we save the instruction, replace it with break, and > > then restore the instruction after the break? > > GDB handles this for us. GDB reads and saves the value at the addr. It asks us > to write the attn instruction to the addr, and then tells us to continue execution. > When we detect that we have paused (in the SIGNAL_WAIT case of the poll function) > we send a trap signal to GDB. GDB then asks us to write back the original value > at the address. Okay cool. > >> + assert(!strcmp(target->class, "thread")); > >> + // Temporary until I can get this working a bit smoother on p9 > >> + if (strcmp(target->compatible, "ibm,power8-thread")) { > >> + PR_ERROR("GDBSERVER is only tested on POWER8\n"); > >> + return -1; > >> + } > > I would say since making a lot of improvements to power9 direct > > controls, this should just about work out of the box there too. > > What are the direct controls? Oh just all the stop/start/ram etc scoms. Thanks, Nick
On Thu, 30 Aug 2018 10:18:30 +1000 Rashmica <rashmica.g@gmail.com> wrote: > On 29/08/18 18:36, Nicholas Piggin wrote: > > > On Wed, 29 Aug 2018 11:50:41 +1000 > > Rashmica Gupta <rashmica.g@gmail.com> wrote: > > > >> From: Alistair Popple <alistair@popple.id.au> > >> > >> I have changed a few bits here and there but this patch is largely > >> authored by Alistair Popple. > > So what's missing for P9? enable_attn? Anything else? > > There's a couple of things that I'm trying to work out regarding P9. > > - we can't mtnia from an arbitrary register in ramming mode (can only do it > from the link register). There's a few bits that seem to make P9 work. No guarantee I didn't break P8 MTNIA because I haven't tested it. As we can see, the need for target specific .ram_spr function is increasing. P9 still isn't quite right with all its MTSPRs either. -- P9 MTNIA and MTMSR fixes --- libpdbg/chip.c | 19 +++++++++++++++---- libpdbg/p9chip.c | 6 +++--- src/reg.c | 2 +- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/libpdbg/chip.c b/libpdbg/chip.c index 80a2261..1d41d87 100644 --- a/libpdbg/chip.c +++ b/libpdbg/chip.c @@ -145,8 +145,8 @@ int ram_sreset_thread(struct pdbg_target *thread_target) * into *results. *results must point to an array the same size as * *opcodes. Each entry from *results is put into SCR0 prior to * executing an opcode so that it may also be used to pass in - * data. Note that only register r0 is saved and restored so opcodes - * must not touch other registers. + * data. Note that only registers r0 and r1 are saved and restored so + * opcode sequences must preserve other registers. */ static int ram_instructions(struct pdbg_target *thread_target, uint64_t *opcodes, uint64_t *results, int len, unsigned int lpar) @@ -242,10 +242,21 @@ int ram_getnia(struct pdbg_target *thread, uint64_t *value) return 0; } +/* + * P9 must MTNIA from LR, P8 can MTNIA from R0. So we set both LR and R0 + * to value. LR must be saved and restored. + * + * This is a hack and should be made much cleaner once we have target + * specific putspr commands. + */ int ram_putnia(struct pdbg_target *thread, uint64_t value) { - uint64_t opcodes[] = {mfspr(0, 277), mtnia(0)}; - uint64_t results[] = {value, 0}; + uint64_t opcodes[] = { mfspr(1, 8), /* mflr r1 */ + mfspr(0, 277), /* value -> r0 */ + mtspr(8, 0), /* mtlr r0 */ + mtnia(0), + mtspr(8, 1), }; /* mtlr r1 */ + uint64_t results[] = {0, value, 0, 0, 0}; CHECK_ERR(ram_instructions(thread, opcodes, results, ARRAY_SIZE(opcodes), 0)); return 0; diff --git a/libpdbg/p9chip.c b/libpdbg/p9chip.c index 189d80a..9094c24 100644 --- a/libpdbg/p9chip.c +++ b/libpdbg/p9chip.c @@ -291,10 +291,9 @@ static int __p9_ram_instruction(struct thread *thread, uint64_t opcode, uint64_t switch(opcode & OPCODE_MASK) { case MTNIA_OPCODE: + opcode = 0x4c0000a4; + opcode |= 0x001E0000; predecode = 8; - - /* Not currently supported as we can only MTNIA from LR */ - PR_ERROR("MTNIA is not currently supported\n"); break; case MFNIA_OPCODE: @@ -304,6 +303,7 @@ static int __p9_ram_instruction(struct thread *thread, uint64_t opcode, uint64_t case MTMSR_OPCODE: predecode = 8; + opcode |= 0x001E0000; break; case MFSPR_OPCODE: diff --git a/src/reg.c b/src/reg.c index aa77a8a..c63f6da 100644 --- a/src/reg.c +++ b/src/reg.c @@ -116,7 +116,7 @@ OPTCMD_DEFINE_CMD(getnia, getnia); static int putnia(uint64_t nia) { uint64_t reg = REG_NIA; - return for_each_target("thread", getprocreg, ®, &nia); + return for_each_target("thread", putprocreg, ®, &nia); } OPTCMD_DEFINE_CMD_WITH_ARGS(putnia, putnia, (DATA));
On 30/08/18 21:25, Nicholas Piggin wrote: > On Thu, 30 Aug 2018 10:18:30 +1000 > Rashmica <rashmica.g@gmail.com> wrote: >> On 29/08/18 18:36, Nicholas Piggin wrote: >>> On Wed, 29 Aug 2018 11:50:41 +1000 >>> Rashmica Gupta <rashmica.g@gmail.com> wrote: >>>> From: Alistair Popple <alistair@popple.id.au> >>>> >>>> I have changed a few bits here and there but this patch is largely >>>> authored by Alistair Popple. >>> So what's missing for P9? enable_attn? Anything else? >> There's a couple of things that I'm trying to work out regarding P9. >> >> - we can't mtnia from an arbitrary register in ramming mode (can only do it >> from the link register). > There's a few bits that seem to make P9 work. No guarantee I didn't > break P8 MTNIA because I haven't tested it. > > As we can see, the need for target specific .ram_spr function is > increasing. P9 still isn't quite right with all its MTSPRs either. When I tried doing value -> LR -> NIA like below, the last two bits of NIA on p9 were always set to 0. I'll try on some other machines to see if it happens there. > -- > > P9 MTNIA and MTMSR fixes > > --- > libpdbg/chip.c | 19 +++++++++++++++---- > libpdbg/p9chip.c | 6 +++--- > src/reg.c | 2 +- > 3 files changed, 19 insertions(+), 8 deletions(-) > > diff --git a/libpdbg/chip.c b/libpdbg/chip.c > index 80a2261..1d41d87 100644 > --- a/libpdbg/chip.c > +++ b/libpdbg/chip.c > @@ -145,8 +145,8 @@ int ram_sreset_thread(struct pdbg_target *thread_target) > * into *results. *results must point to an array the same size as > * *opcodes. Each entry from *results is put into SCR0 prior to > * executing an opcode so that it may also be used to pass in > - * data. Note that only register r0 is saved and restored so opcodes > - * must not touch other registers. > + * data. Note that only registers r0 and r1 are saved and restored so > + * opcode sequences must preserve other registers. > */ > static int ram_instructions(struct pdbg_target *thread_target, uint64_t *opcodes, > uint64_t *results, int len, unsigned int lpar) > @@ -242,10 +242,21 @@ int ram_getnia(struct pdbg_target *thread, uint64_t *value) > return 0; > } > > +/* > + * P9 must MTNIA from LR, P8 can MTNIA from R0. So we set both LR and R0 > + * to value. LR must be saved and restored. > + * > + * This is a hack and should be made much cleaner once we have target > + * specific putspr commands. > + */ > int ram_putnia(struct pdbg_target *thread, uint64_t value) > { > - uint64_t opcodes[] = {mfspr(0, 277), mtnia(0)}; > - uint64_t results[] = {value, 0}; > + uint64_t opcodes[] = { mfspr(1, 8), /* mflr r1 */ > + mfspr(0, 277), /* value -> r0 */ > + mtspr(8, 0), /* mtlr r0 */ > + mtnia(0), > + mtspr(8, 1), }; /* mtlr r1 */ > + uint64_t results[] = {0, value, 0, 0, 0}; > > CHECK_ERR(ram_instructions(thread, opcodes, results, ARRAY_SIZE(opcodes), 0)); > return 0; > diff --git a/libpdbg/p9chip.c b/libpdbg/p9chip.c > index 189d80a..9094c24 100644 > --- a/libpdbg/p9chip.c > +++ b/libpdbg/p9chip.c > @@ -291,10 +291,9 @@ static int __p9_ram_instruction(struct thread *thread, uint64_t opcode, uint64_t > > switch(opcode & OPCODE_MASK) { > case MTNIA_OPCODE: > + opcode = 0x4c0000a4; > + opcode |= 0x001E0000; > predecode = 8; > - > - /* Not currently supported as we can only MTNIA from LR */ > - PR_ERROR("MTNIA is not currently supported\n"); > break; > > case MFNIA_OPCODE: > @@ -304,6 +303,7 @@ static int __p9_ram_instruction(struct thread *thread, uint64_t opcode, uint64_t > > case MTMSR_OPCODE: > predecode = 8; > + opcode |= 0x001E0000; > break; > > case MFSPR_OPCODE: > diff --git a/src/reg.c b/src/reg.c > index aa77a8a..c63f6da 100644 > --- a/src/reg.c > +++ b/src/reg.c > @@ -116,7 +116,7 @@ OPTCMD_DEFINE_CMD(getnia, getnia); > static int putnia(uint64_t nia) > { > uint64_t reg = REG_NIA; > - return for_each_target("thread", getprocreg, ®, &nia); > + return for_each_target("thread", putprocreg, ®, &nia); > } > OPTCMD_DEFINE_CMD_WITH_ARGS(putnia, putnia, (DATA)); > >
On 04/09/18 16:23, Rashmica wrote: > On 30/08/18 21:25, Nicholas Piggin wrote: > >> On Thu, 30 Aug 2018 10:18:30 +1000 >> Rashmica <rashmica.g@gmail.com> wrote: >>> On 29/08/18 18:36, Nicholas Piggin wrote: >>>> On Wed, 29 Aug 2018 11:50:41 +1000 >>>> Rashmica Gupta <rashmica.g@gmail.com> wrote: >>>>> From: Alistair Popple <alistair@popple.id.au> >>>>> >>>>> I have changed a few bits here and there but this patch is largely >>>>> authored by Alistair Popple. >>>> So what's missing for P9? enable_attn? Anything else? >>> There's a couple of things that I'm trying to work out regarding P9. >>> >>> - we can't mtnia from an arbitrary register in ramming mode (can only do it >>> from the link register). >> There's a few bits that seem to make P9 work. No guarantee I didn't >> break P8 MTNIA because I haven't tested it. >> >> As we can see, the need for target specific .ram_spr function is >> increasing. P9 still isn't quite right with all its MTSPRs either. > When I tried doing value -> LR -> NIA like below, the last two bits of NIA on p9 > were always set to 0. I'll try on some other machines to see if it happens there. derp that's what should happen! Ignore me :) >> -- >> >> P9 MTNIA and MTMSR fixes >> >> --- >> libpdbg/chip.c | 19 +++++++++++++++---- >> libpdbg/p9chip.c | 6 +++--- >> src/reg.c | 2 +- >> 3 files changed, 19 insertions(+), 8 deletions(-) >> >> diff --git a/libpdbg/chip.c b/libpdbg/chip.c >> index 80a2261..1d41d87 100644 >> --- a/libpdbg/chip.c >> +++ b/libpdbg/chip.c >> @@ -145,8 +145,8 @@ int ram_sreset_thread(struct pdbg_target *thread_target) >> * into *results. *results must point to an array the same size as >> * *opcodes. Each entry from *results is put into SCR0 prior to >> * executing an opcode so that it may also be used to pass in >> - * data. Note that only register r0 is saved and restored so opcodes >> - * must not touch other registers. >> + * data. Note that only registers r0 and r1 are saved and restored so >> + * opcode sequences must preserve other registers. >> */ >> static int ram_instructions(struct pdbg_target *thread_target, uint64_t *opcodes, >> uint64_t *results, int len, unsigned int lpar) >> @@ -242,10 +242,21 @@ int ram_getnia(struct pdbg_target *thread, uint64_t *value) >> return 0; >> } >> >> +/* >> + * P9 must MTNIA from LR, P8 can MTNIA from R0. So we set both LR and R0 >> + * to value. LR must be saved and restored. >> + * >> + * This is a hack and should be made much cleaner once we have target >> + * specific putspr commands. >> + */ >> int ram_putnia(struct pdbg_target *thread, uint64_t value) >> { >> - uint64_t opcodes[] = {mfspr(0, 277), mtnia(0)}; >> - uint64_t results[] = {value, 0}; >> + uint64_t opcodes[] = { mfspr(1, 8), /* mflr r1 */ >> + mfspr(0, 277), /* value -> r0 */ >> + mtspr(8, 0), /* mtlr r0 */ >> + mtnia(0), >> + mtspr(8, 1), }; /* mtlr r1 */ >> + uint64_t results[] = {0, value, 0, 0, 0}; >> >> CHECK_ERR(ram_instructions(thread, opcodes, results, ARRAY_SIZE(opcodes), 0)); >> return 0; >> diff --git a/libpdbg/p9chip.c b/libpdbg/p9chip.c >> index 189d80a..9094c24 100644 >> --- a/libpdbg/p9chip.c >> +++ b/libpdbg/p9chip.c >> @@ -291,10 +291,9 @@ static int __p9_ram_instruction(struct thread *thread, uint64_t opcode, uint64_t >> >> switch(opcode & OPCODE_MASK) { >> case MTNIA_OPCODE: >> + opcode = 0x4c0000a4; >> + opcode |= 0x001E0000; >> predecode = 8; >> - >> - /* Not currently supported as we can only MTNIA from LR */ >> - PR_ERROR("MTNIA is not currently supported\n"); >> break; >> >> case MFNIA_OPCODE: >> @@ -304,6 +303,7 @@ static int __p9_ram_instruction(struct thread *thread, uint64_t opcode, uint64_t >> >> case MTMSR_OPCODE: >> predecode = 8; >> + opcode |= 0x001E0000; >> break; >> >> case MFSPR_OPCODE: >> diff --git a/src/reg.c b/src/reg.c >> index aa77a8a..c63f6da 100644 >> --- a/src/reg.c >> +++ b/src/reg.c >> @@ -116,7 +116,7 @@ OPTCMD_DEFINE_CMD(getnia, getnia); >> static int putnia(uint64_t nia) >> { >> uint64_t reg = REG_NIA; >> - return for_each_target("thread", getprocreg, ®, &nia); >> + return for_each_target("thread", putprocreg, ®, &nia); >> } >> OPTCMD_DEFINE_CMD_WITH_ARGS(putnia, putnia, (DATA)); >> >> >>
diff --git a/.gitignore b/.gitignore index c25b053..79f1ef9 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,4 @@ optcmd_test *.trs *.log test-driver +src/gdb_parser.c diff --git a/Makefile.am b/Makefile.am index 1397b0a..5b78624 100644 --- a/Makefile.am +++ b/Makefile.am @@ -77,7 +77,9 @@ pdbg_SOURCES = \ src/optcmd.h \ src/options.h \ src/parsers.h \ - src/progress.h + src/progress.h \ + src/pdbgproxy.c \ + src/gdb_parser.c pdbg_LDADD = $(DT_objects) libpdbg.la libfdt.la libccan.a \ -L.libs -lrt @@ -157,6 +159,10 @@ GEN_V = $(GEN_V_$(V)) GEN_V_ = $(GEN_V_$(AM_DEFAULT_VERBOSITY)) GEN_V_0 = @echo " GEN " $@; +RAGEL_V = $(RAGEL_V_$(V)) +RAGEL_V_ = $(RAGEL_V_$(AM_DEFAULT_VERBOSITY)) +RAGEL_V_0 = @echo " RAGEL " $@; + %.dts: %.dts.m4 $(M4_V)$(M4) -I$(dir $<) $< | $(DTC) -I dts -O dts > $@ @@ -177,4 +183,7 @@ p9z-fsi.dts: p9z-fsi.dts.m4 p9-fsi.dtsi %.dtb.o: %.dtb $(AM_V_CC)$(CC) -c $(srcdir)/template.S -DSYMBOL_PREFIX=$(shell echo $@ | tr '.-' '_') -DFILENAME=\"$<\" -o $@ -MOSTLYCLEANFILES = *.dtb *.dts *.dt.h p9-fsi.dtsi +%.c: %.rl + $(RAGEL_V)$(RAGEL) -o $@ $< + +MOSTLYCLEANFILES = *.dtb *.dts *.dt.h p9-fsi.dtsi src/gdb_parser.c diff --git a/configure.ac b/configure.ac index e48e80f..3486969 100644 --- a/configure.ac +++ b/configure.ac @@ -17,6 +17,9 @@ if test x"$ac_cv_path_DTC" = x ; then fi AC_SUBST([DTC]) +AC_PATH_PROG([RAGEL], [ragel]) +AC_SUBST([RAGEL]) + AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_HEADERS([config.h]) AC_CONFIG_FILES([Makefile]) diff --git a/libpdbg/libpdbg.h b/libpdbg/libpdbg.h index 25b2afd..869d553 100644 --- a/libpdbg/libpdbg.h +++ b/libpdbg/libpdbg.h @@ -138,6 +138,7 @@ struct thread_regs { }; int ram_putmsr(struct pdbg_target *target, uint64_t val); +int ram_getmem(struct pdbg_target *thread, uint64_t addr, uint64_t *value); int ram_putnia(struct pdbg_target *target, uint64_t val); int ram_putspr(struct pdbg_target *target, int spr, uint64_t val); int ram_putgpr(struct pdbg_target *target, int spr, uint64_t val); diff --git a/libpdbg/operations.h b/libpdbg/operations.h index 93e5df5..96a7c01 100644 --- a/libpdbg/operations.h +++ b/libpdbg/operations.h @@ -70,9 +70,6 @@ #define MXSPR_SPR(opcode) (((opcode >> 16) & 0x1f) | ((opcode >> 6) & 0x3e0)) -/* GDB server functionality */ -int gdbserver_start(uint16_t port); - enum fsi_system_type {FSI_SYSTEM_P8, FSI_SYSTEM_P9W, FSI_SYSTEM_P9R, FSI_SYSTEM_P9Z}; enum chip_type get_chip_type(uint64_t chip_id); diff --git a/libpdbg/target.c b/libpdbg/target.c index 88ae94a..997db3b 100644 --- a/libpdbg/target.c +++ b/libpdbg/target.c @@ -391,3 +391,19 @@ void pdbg_target_priv_set(struct pdbg_target *target, void *priv) { target->priv = priv; } + +int poll_target(struct pdbg_target *target, uint64_t addr, uint64_t mask, uint64_t *value) +{ + uint64_t val, val1; + int rc = 0; + + do { + pib_read(target, addr, &val); + rc = pib_read(target, addr, &val1); + if (val != val1) + continue; + } while ((val & mask) != *value); + + *value = val; + return rc; +} diff --git a/libpdbg/target.h b/libpdbg/target.h index c8da048..c6b715d 100644 --- a/libpdbg/target.h +++ b/libpdbg/target.h @@ -165,4 +165,7 @@ struct chiplet { int (*getring)(struct chiplet *, uint64_t, int64_t, uint32_t[]); }; #define target_to_chiplet(x) container_of(x, struct chiplet, target) + +int poll_target(struct pdbg_target *target, uint64_t addr, uint64_t mask, uint64_t *value); + #endif diff --git a/src/gdb_parser.rl b/src/gdb_parser.rl new file mode 100644 index 0000000..18dfac7 --- /dev/null +++ b/src/gdb_parser.rl @@ -0,0 +1,143 @@ +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <assert.h> + +#include "src/pdbgproxy.h" + +%%{ + machine gdb; + + action reset { + cmd = 0; + rsp = NULL; + data = stack; + memset(stack, 0, sizeof(stack)); + crc = 0; + } + + action crc { + crc += *p; + } + + action push { + data++; + assert(data < &stack[10]); + } + + action hex_digit { + *data *= 16; + + if (*p >= '0' && *p <= '9') + *data += *p - '0'; + else if (*p >= 'a' && *p <= 'f') + *data += *p - 'a' + 10; + else if (*p >= 'A' && *p <= 'F') + *data += *p - 'A' + 10; + } + + action end { + /* *data should point to the CRC */ + if (crc != *data) { + printf("CRC error\n"); + send_nack(priv); + } else { + printf("Cmd %d\n", cmd); + send_ack(priv); + + /* Push the response onto the stack */ + if (rsp) + *data = (uintptr_t)rsp; + else + *data = 0; + + command_callbacks[cmd](stack, priv); + } + } + + get_mem = ('m' @{cmd = GET_MEM;} + xdigit+ $hex_digit %push + ',' + xdigit+ $hex_digit %push); + + put_mem = ('M' any* @{cmd = PUT_MEM;} + xdigit+ $hex_digit %push + ',' + xdigit+ $hex_digit %push + ':' + xdigit+ $hex_digit %push); + + get_gprs = ('g' @{cmd = GET_GPRS;}); + + get_spr = ('p' @{cmd = GET_SPR;} + xdigit+ $hex_digit %push); + + stop_reason = ('?' @{cmd = STOP_REASON;}); + + set_thread = ('H' any* @{cmd = SET_THREAD;}); + + # TODO: We don't actually listen to what's supported + q_attached = ('qAttached:' xdigit* @{rsp = "1";}); + q_C = ('qC' @{rsp = "QC1";}); + q_supported = ('qSupported:' any* @{rsp = "multiprocess+;vContSupported+";}); + qf_threadinfo = ('qfThreadInfo' @{rsp = "m1l";}); + + # vCont packet parsing + v_contq = ('vCont?' @{rsp = "vCont;c;C;s;S";}); + v_contc = ('vCont;c' any* @{cmd = V_CONTC;}); + v_conts = ('vCont;s' any* @{cmd = V_CONTS;}); + + interrupt = (3 @{command_callbacks[INTERRUPT](stack, priv);}); + + commands = (get_mem | get_gprs | get_spr | stop_reason | set_thread | + q_attached | q_C | q_supported | qf_threadinfo | q_C | + v_contq | v_contc | v_conts | put_mem ); + + cmd = ((commands & ^'#'*) | ^'#'*) $crc + ('#' xdigit{2} $hex_digit @end); + + # We ignore ACK/NACK for the moment + ack = ('+'); + nack = ('-'); + + main := (( ^('$' | interrupt)*('$' | interrupt) @reset) (cmd | ack | nack))*; + +}%% + +static enum gdb_command cmd = NONE; +static uint64_t stack[10], *data = stack; +static char *rsp; +static uint8_t crc; +static int cs; + +command_cb *command_callbacks; + +%%write data; + +void parser_init(command_cb *callbacks) +{ + %%write init; + + command_callbacks = callbacks; +} + +int parse_buffer(char *buf, size_t len, void *priv) +{ + char *p = buf; + char *pe = p + len + 1; + + %%write exec; + + return 0; +} + +#if 0 +int main(int argc, char **argv) +{ + parser_init(NULL); + + if (argc > 1) + parse_buffer(argv[1], strlen(argv[1]), NULL); + return 0; +} +#endif diff --git a/src/main.c b/src/main.c index ff3ee2b..82978d9 100644 --- a/src/main.c +++ b/src/main.c @@ -37,6 +37,7 @@ #include "htm.h" #include "options.h" #include "optcmd.h" +#include "pdbgproxy.h" #define PR_ERROR(x, args...) \ pdbg_log(PDBG_ERROR, x, ##args) @@ -87,7 +88,7 @@ extern struct optcmd_cmd optcmd_getring, optcmd_start, optcmd_stop, optcmd_step, optcmd_threadstatus, optcmd_sreset, optcmd_regs, optcmd_probe, optcmd_getmem, optcmd_putmem, optcmd_getxer, optcmd_putxer, - optcmd_getcr, optcmd_putcr; + optcmd_getcr, optcmd_putcr, optcmd_gdbserver; static struct optcmd_cmd *cmds[] = { &optcmd_getscom, &optcmd_putscom, &optcmd_getcfam, &optcmd_putcfam, @@ -96,7 +97,7 @@ static struct optcmd_cmd *cmds[] = { &optcmd_getring, &optcmd_start, &optcmd_stop, &optcmd_step, &optcmd_threadstatus, &optcmd_sreset, &optcmd_regs, &optcmd_probe, &optcmd_getmem, &optcmd_putmem, &optcmd_getxer, &optcmd_putxer, - &optcmd_getcr, &optcmd_putcr, + &optcmd_getcr, &optcmd_putcr, &optcmd_gdbserver, }; /* Purely for printing usage text. We could integrate printing argument and flag @@ -135,6 +136,7 @@ static struct action actions[] = { { "threadstatus", "", "Print the status of a thread" }, { "sreset", "", "Reset" }, { "regs", "", "State" }, + { "gdbserver", "", "Start a gdb server" }, }; static void print_usage(char *pname) diff --git a/src/pdbgproxy.c b/src/pdbgproxy.c new file mode 100644 index 0000000..7a7da41 --- /dev/null +++ b/src/pdbgproxy.c @@ -0,0 +1,476 @@ +#define _GNU_SOURCE +#include <stdint.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#include <inttypes.h> +#include <assert.h> +#include <getopt.h> +#include <errno.h> + +#include <backend.h> +#include <operations.h> + +#include "pdbgproxy.h" +#include "main.h" +#include "optcmd.h" + +/* Maximum packet size */ +#define BUFFER_SIZE 8192 + +#define PR_ERROR(x, args...) \ + pdbg_log(PDBG_ERROR, x, ##args) + +static struct pdbg_target *thread_target = NULL; +static struct timeval timeout; +static int poll_interval; +static int fd = -1; +enum client_state {IDLE, SIGNAL_WAIT}; +static enum client_state state = IDLE; + +static uint8_t gdbcrc(char *data) +{ + uint8_t crc = 0; + int i; + + for (i = 0; i < strlen(data); i++) + crc += data[i]; + + return crc; +} + +static void send_response(int fd, char *response) +{ + int len; + char *result; + + len = asprintf(&result, "$%s#%02x", response, gdbcrc(response)); + printf("Send: %s\n", result); + send(fd, result, len, 0); + free(result); +} + +void send_nack(void *priv) +{ + printf("Send: -\n"); + send(fd, "-", 1, 0); +} + +void send_ack(void *priv) +{ + printf("Send: +\n"); + send(fd, "+", 1, 0); +} + +static void set_thread(uint64_t *stack, void *priv) +{ + send_response(fd, "OK"); +} + +static void stop_reason(uint64_t *stack, void *priv) +{ + send_response(fd, "S05"); +} + +/* 32 registers represented as 16 char hex numbers with null-termination */ +#define REG_DATA_SIZE (32*16+1) +static void get_gprs(uint64_t *stack, void *priv) +{ + char data[REG_DATA_SIZE] = ""; + uint64_t regs[32]; + int i; + + for (i = 0; i < 32; i++) { + if (ram_getgpr(thread_target, i, ®s[i])) + PR_ERROR("Error reading register %d\n", i); + printf("r%d = 0x%016lx\n", i, regs[i]); + snprintf(data + i*16, 17, "%016lx", __builtin_bswap64(regs[i])); + } + + send_response(fd, data); +} + +static void get_spr(uint64_t *stack, void *priv) +{ + char data[REG_DATA_SIZE]; + uint64_t value; + + switch (stack[0]) { + case 0x40: + /* Get PC/NIA */ + if (ram_getnia(thread_target, &value)) + PR_ERROR("Error reading NIA\n"); + snprintf(data, REG_DATA_SIZE, "%016lx", __builtin_bswap64(value)); + send_response(fd, data); + break; + + case 0x43: + /* Get LR */ + if (ram_getspr(thread_target, 8, &value)) + PR_ERROR("Error reading LR\n"); + snprintf(data, REG_DATA_SIZE, "%016lx", __builtin_bswap64(value)); + send_response(fd, data); + break; + + default: + send_response(fd, "xxxxxxxxxxxxxxxx"); + break; + } +} + +#define MAX_DATA 0x1000 + +/* Returns a real address to use with adu_getmem or -1UL if we + * couldn't determine a real address. At the moment we only deal with + * kernel linear mapping but in future we could walk that page + * tables. */ +static uint64_t get_addr(uint64_t addr) +{ + if (GETFIELD(PPC_BITMASK(0, 3), addr) == 0xc) + /* Assume all 0xc... addresses are part of the linux linear map */ + addr &= ~PPC_BITMASK(0, 1); + else + addr = -1UL; + + return addr; +} + +static void get_mem(uint64_t *stack, void *priv) +{ + struct pdbg_target *adu; + uint64_t addr, len, linear_map; + int i, err = 0; + uint64_t data[MAX_DATA/sizeof(uint64_t)]; + char result[2*MAX_DATA]; + + /* stack[0] is the address and stack[1] is the length */ + addr = stack[0]; + len = stack[1]; + + pdbg_for_each_class_target("adu", adu) { + if (pdbg_target_probe(adu) == PDBG_TARGET_ENABLED) + break; + } + + if (adu == NULL) { + PR_ERROR("ADU NOT FOUND\n"); + err=3; + goto out; + } + + if (len > MAX_DATA) { + printf("Too much memory requested, truncating\n"); + len = MAX_DATA; + } + + if (!addr) { + err = 2; + goto out; + } + + linear_map = get_addr(addr); + if (linear_map != -1UL) { + if (adu_getmem(adu, addr, (uint8_t *) data, len)) { + PR_ERROR("Unable to read memory\n"); + err = 1; + } + } else { + /* Virtual address */ + for (i = 0; i < len; i += sizeof(uint64_t)) { + if (ram_getmem(thread_target, addr, &data[i/sizeof(uint64_t)])) { + PR_ERROR("Fault reading memory\n"); + err = 2; + break; + } + } + } + +out: + if (!err) + for (i = 0; i < len; i ++) { + sprintf(&result[i*2], "%02x", *(((uint8_t *) data) + i)); + } + else + sprintf(result, "E%02x", err); + + send_response(fd, result); +} + +static void put_mem(uint64_t *stack, void *priv) +{ + struct pdbg_target *adu; + uint64_t addr, len; + uint8_t *data; + uint8_t attn_opcode[] = {0x00, 0x02, 0x00, 0x00}; + int err = 0; + struct thread *thread = target_to_thread(thread_target); + + addr = stack[0]; + len = stack[1]; + data = (uint8_t *) &stack[2]; + + pdbg_for_each_class_target("adu", adu) { + if (pdbg_target_probe(adu) == PDBG_TARGET_ENABLED) + break; + } + + if (adu == NULL) { + PR_ERROR("ADU NOT FOUND\n"); + err=3; + goto out; + } + + addr = get_addr(addr); + if (addr == -1UL) { + PR_ERROR("TODO: No virtual address support for putmem\n"); + err = 1; + goto out; + } + + if (len > 8) { + PR_ERROR("TODO: Only support writing at most 8 bytes of memory at a time\n"); + err = 2; + goto out; + } + + printf("put_mem 0x%016lx = 0x%016lx\n", addr, stack[2]); + + if (len == 4 && stack[2] == 0x0810827d) { + /* According to linux-ppc-low.c gdb only uses this + * op-code for sw break points so we replace it with + * the correct attn opcode which is what we need for + * breakpoints. + * + * TODO: Upstream a patch to gdb so that it uses the + * right opcode for baremetal debug. */ + PR_INFO("Breakpoint opcode detected, replacing with attn\n"); + data = attn_opcode; + + /* Need to enable the attn instruction in HID0 */ + if (thread->enable_attn(thread_target)) + goto out; + } else + stack[2] = __builtin_bswap64(stack[2]) >> 32; + + if (adu_putmem(adu, addr, data, len)) { + PR_ERROR("Unable to write memory\n"); + err = 3; + } + +out: + if (err) + send_response(fd, "E01"); + else + send_response(fd, "OK"); +} + +static void v_conts(uint64_t *stack, void *priv) +{ + ram_step_thread(thread_target, 1); + send_response(fd, "S05"); +} + +#define VCONT_POLL_DELAY 100000 +static void v_contc(uint64_t *stack, void *priv) +{ + ram_start_thread(thread_target); + state = SIGNAL_WAIT; +} + +static void interrupt(uint64_t *stack, void *priv) +{ + printf("Interrupt\n"); + ram_stop_thread(thread_target); + send_response(fd, "S05"); + + return; +} + +static void poll(void) +{ + uint64_t nia; + struct thread *thread = target_to_thread(thread_target); + + switch (state) { + case IDLE: + break; + + case SIGNAL_WAIT: + if (!(thread->status.quiesced)) + break; + + state = IDLE; + poll_interval = VCONT_POLL_DELAY; + if (!(thread->status.active)) { + PR_ERROR("Thread inactive after trap\n"); + send_response(fd, "E01\n"); + return; + } + + /* Restore NIA */ + if (ram_getnia(thread_target, &nia)) + PR_ERROR("Error during getnia\n"); + if (ram_putnia(thread_target, nia - 4)) + PR_ERROR("Error during putnia\n"); + send_response(fd, "S05"); + break; + } +} + +static void cmd_default(uint64_t *stack, void *priv) +{ + uintptr_t tmp = stack[0]; + if (stack[0]) { + send_response(fd, (char *) tmp); + } else + send_response(fd, ""); +} + +static void create_client(int new_fd) +{ + printf("Client connected\n"); + fd = new_fd; +} + +static void destroy_client(int dead_fd) +{ + printf("Client disconnected\n"); + close(dead_fd); + fd = -1; +} + +static int read_from_client(int fd) +{ + char buffer[BUFFER_SIZE + 1]; + int nbytes; + + nbytes = read(fd, buffer, sizeof(buffer)); + if (nbytes < 0) { + perror(__FUNCTION__); + return -1; + } else if (nbytes == 0) { + printf("0 bytes\n"); + return -1; + } else { + buffer[nbytes] = '\0'; + printf("%x\n", buffer[0]); + printf("Recv: %s\n", buffer); + parse_buffer(buffer, nbytes, &fd); + } + + return 0; +} + +command_cb callbacks[LAST_CMD + 1] = { + cmd_default, + get_gprs, + get_spr, + get_mem, + stop_reason, + set_thread, + v_contc, + v_conts, + put_mem, + interrupt, + NULL}; + +int gdbserver_start(struct pdbg_target *target, uint16_t port) +{ + int sock, i; + struct sockaddr_in name; + fd_set active_fd_set, read_fd_set; + + parser_init(callbacks); + thread_target = target; + + sock = socket(PF_INET, SOCK_STREAM, 0); + if (sock < 0) { + perror(__FUNCTION__); + return -1; + } + + name.sin_family = AF_INET; + name.sin_port = htons(port); + name.sin_addr.s_addr = htonl(INADDR_ANY); + if (bind(sock, (struct sockaddr *) &name, sizeof(name)) < 0) { + perror(__FUNCTION__); + return -1; + } + + if (listen(sock, 1) < 0) { + perror(__FUNCTION__); + return -1; + } + + FD_ZERO(&active_fd_set); + FD_SET(sock, &active_fd_set); + + while (1) { + read_fd_set = active_fd_set; + timeout.tv_sec = 0; + timeout.tv_usec = poll_interval; + if (select(FD_SETSIZE, &read_fd_set, NULL, NULL, &timeout) < 0) { + perror(__FUNCTION__); + return -1; + } + + for (i = 0; i < FD_SETSIZE; i++) { + if (FD_ISSET(i, &read_fd_set)) { + if (i == sock) { + int new; + new = accept(sock, NULL, NULL); + if (new < 0) { + perror(__FUNCTION__); + return -1; + } + + if (fd > 0) + /* It only makes sense to accept a single client */ + close(new); + else { + create_client(new); + FD_SET(new, &active_fd_set); + } + } else { + if (read_from_client(i) < 0) { + destroy_client(i); + FD_CLR(i, &active_fd_set); + } + } + } + } + + poll(); + } + + return 1; +} + + +static int gdbserver(uint16_t port) +{ + struct pdbg_target *target; + + for_each_class_target("thread", target) { + if (!target_selected(target)) + continue; + if (pdbg_target_probe(target) == PDBG_TARGET_ENABLED) + break; + } + assert(!strcmp(target->class, "thread")); + // Temporary until I can get this working a bit smoother on p9 + if (strcmp(target->compatible, "ibm,power8-thread")) { + PR_ERROR("GDBSERVER is only tested on POWER8\n"); + return -1; + } + gdbserver_start(target, port); + return 0; +} +OPTCMD_DEFINE_CMD_WITH_ARGS(gdbserver, gdbserver, (DATA16)); diff --git a/src/pdbgproxy.h b/src/pdbgproxy.h new file mode 100644 index 0000000..bd12b5e --- /dev/null +++ b/src/pdbgproxy.h @@ -0,0 +1,13 @@ +#ifndef __PDBGPROXY_H +#define __PDBGPROXY_H + +enum gdb_command {NONE, GET_GPRS, GET_SPR, GET_MEM, + STOP_REASON, SET_THREAD, V_CONTC, V_CONTS, + PUT_MEM, INTERRUPT, LAST_CMD}; +typedef void (*command_cb)(uint64_t *stack, void *priv); + +void parser_init(command_cb *callbacks); +int parse_buffer(char *buf, size_t len, void *priv); +void send_nack(void *priv); +void send_ack(void *priv); +#endif