summaryrefslogtreecommitdiff
path: root/source/notes/pic-mcu/pickit2/pk2cmd-stuff/pk2_firmware_notes.md
blob: 0d3535b774248febf338f558d2777b96b1bd6844 (plain)
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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
usb handler?

walking through from the entry point for the script interpreter proves to be difficult. can't even find usb handler. alternative idea: look for instructions and registers that would be used by the usb handler, try to follow from there to the command handler, and from there maybe get to the script interpreter

found a function that appears to handle usb status bits but no clear indication of reading packets.. next thought is to disassemble all of the firmware and look for what might be a dispatch table for script commands

three jump tables found:
0x2334?
0x3b0a?
0x429e?

there also seems to be some bit bang style logic around 0x525c, through lata 3 and somethig to do with trisa 4

the last two jump tables seem related, not sure how exactly.

alternate approach again, maybe it's easier to find the implementation of some script commands?

decent targets: SCMD_BUSY_LED_OFF, SCMD_BUSY_LED_ON aka 0xF4, 0xF5
busy LED is pin 4 in portb, searching PORTB yields a lot of btfss and btfsc, but searching LATB is entirely bsf and bcf. further refining to `latb, 4` yields 12 possibly interesting instructions
because these would be the result of command dispatch, and are adjacent, they are probably close together

0x26d0 (set)
0x26d4 (clear)

0x3b60 (clear)
0x3b64 (set)

further reading, noticed some interesting constants before the first jump table:
000022ae: xorlw #0x42
000022b0: bz 0x2300 (ip+2+0x4e)
000022b2: xorlw #0x34
000022b4: bz 0x22c0 (ip+2+0xa)
000022b6: xorlw #0x2c
000022b8: bz 0x22bc (ip+2+0x2)
000022ba: bra 0x2306 (ip+2+0x4a)

immediately eyecatching on account of 0x42 being FWCMD_ENTER_BOOTLOADER
the following xorlw #0x34 functionally results in W == W_original ^ 0x42 ^ 0x34. because xor is associative that can be read as W == W_original ^ (0x42 ^ 0x34), or W == W_original ^ (0x76). 0x76 is, itself, also interesting, as that's the opcode for FWCMD_FIRMWARE_VERSION.
the third xor at 0x22b6 is equivalent to W == W_original ^ (0x42 ^ 0x34 ^ 0x2c) aka W == W_original ^ (0x5a). 0x53 maps to FWCMD_NO_OPERATION, so at this point this is definitely the start of the command interpreter dispatch table.

added some labels on those branches so we don't forget.

at this point there's something to closely inspect, so it's worth investigating a structure i've seen many times:
```
; a call target
0x222c: movff FSR2L, POSTINC1
0x2230: movff FSR1L, FSR2L
; ^ what's up with this and why?
; and at the return... (different function)
0x7532: movf POSTDEC1, POSTDEC1
0x7534: movff INDF1, FSR2L
0x7536: return

POSTDEC1 and INDF1 are ~special~ ~magic~ registers. they, along with PREDEC1, PREINC1, POSTINC1, and PLUSW1 are indirect registers that actually write to memory pointed at by the pair FSR1H:FSR1L.
* `INDF1` is an access to memory at `FSR1H:FSR1L`.
* `PLUSW1` is an access to memory at `FSR1H:FSR1L + W`.
* `POSTDEC`, `POSTINC`, and `PREINC` refer to additional functionality: before (or after) writing, the pair of FSR1 registers are incremented (or decremented) as described by the register name.

pic18 2550 has three sets of FSR registers: 0, 1, and 2. each of these are distinct but have corresponding `PREDEC`, `POSTINC`, `INDF`, etc.

so at function entry the code stores the previous FSR2L to `[FSR1++]`, then `FSR1 => FSR2`. because the pic18 2550 doesn't have a stack, this seems like a decent approximation of the same context-saving behavior. and of course, at return, context is restored appropriately.

this provides some illumination on another interesting idiom:
```
0x22d2: setf POSTINC1
0x22d4: movlw #0x4
0x22d6: movwf POSTINC1
0x22d8: call 0x5466, 0
0x22dc: movf POSTDEC1, POSTDEC1
0x22de: movf POSTDEC1, POSTDEC1
```

again, FSR1 is being used akin to a stack to pass parameters

`setf POSTINC1` stores `0xFF` through `FSR1` and increments, the subsequent `movlw #0x4; movwf POSTINC1` provides a second argument, which aligns with the two `movf POSTDEC` after the call (undoing the increments to provide parameters)

returning to this jump table stuff, register 0x54 is compared with 0xb9 (in the range of opcodes, though well into `SCMD_` space), and if the opcode is less than 0xb9, a branch is taken to `0x2306`:
```
code from 0x2306
```

at `0x2306` the opcode is turned into an offset into the subsequent jump table and added to PC, doing the switch by opcode. at this point it's reasonable to add labels for the cases to indicate what command they correspond to.

confirmation: `FWCMD_RESET` matches up with the easily verifiably logic of the `reset` instruction.

of note for later are:
* `FWCMD_EXECUTE_SCRIPT`: this is relevant for programmer operation. all operations seem to be scripts.
* `FWCMD_RUN_SCRIPT`: how is this different from "execute"?
* `FWCMD_DOWNLOAD_SCRIPT`: this is the command to download a script to the programmer. can we read script memory? how many scripts can there be? what's their size?
* `FWCMD_DOWNLOAD_DATA`: seems interesting. download to programmer or to target?
* `FWCMD_UPLOAD_DATA`: same as above
* `FWCMD_END_OF_BUFFER`: might be interesting to know later

... it's not clear how SCMD commands are dispatched, which is probably a different table. time to keep looking.

onward to `0x3b0a`...

at `0x3aec` the opcode (again in register `0x54`) is compared against `0xd5`. if the opcode is that or above, `0x3af0` branches to a similar switch based on the value.

since the entries are in order from `0xd5` up, script command names apply cleanly to each branch:
```
TODO: before/after with script command names
```

and if the opcode was below `0xd5`, the branch at `0x3af2` kicks in and takes us to...

```
TODO: CODE AT prelude_to_jump_tbl_3
```

very similar structure to other tables, but starts with `0xb3`. `0xb3` is not in the header file and is unknown, but the rest can be filled in.

```
TODO: CODE AT prelude_to_jump_tbl_3 with labels
```

(note: at this point as i was keymashing i accidentally hit ctrl+c, which my script doesn't handle, and lost three hours of notes give or take. largely looking through the interpreter cases, and easy enough to rebuild, but beware sigint)

so to return to original questions:
* what script opcodes take how many parameters?
* how does programming actually take place?

## Script Opcodes
easiest to start with obvious opcodes where i can assume that i know the intended functionality, but not the implementation. SCMD_NOP24 is an obvious candidate. functionally it *probably* does nothing, but does likely increment some pointers on the programmer side (current script opcode pointer, if such a thing exists), and possibly on the device side (to cause the target device to execute a no-op).

```
TODO: SCMD_NOP24 implementation
```

SCMD_NOP24 makes four calls to the same function, with parameters that vary:
* 0x5066(4, 0)
* 0x5066(8, 0)
* 0x5066(8, 0)
* 0x5066(8, 0)

the sequence of three calls with (8, 0) probably correspond to the bytes 0x00, 0x00, 0x00, which decode under PIC24 to a NOP. so this seems sane! the remaining question if this is true, is why is there a call with arguments (4, 0)?

to answer that, we have to look at 0x5066:
```
TODO: fn at 0x5066
```

so this function reads two parameters, stores the later parameter to 0x54, and the first to 0x55. as the first parameter for these calls is either 8 or 4, and the latter parameter is all 0's, this continues to make sense.

the function then tests if 0x2e1 > 0x2 and branches below if so.

```
bitbang loop
```

this sequence of four instructions is responsible for putting pin 2 of latch A into a high or low state. bit 0 in 0x54 can only be 1 or 0, so exactly one of `btfss` or `btfcs` will result in a fallthrough. as a result, only one of `bcf LATA, 2; bsf LATA, 2` will be executed. afterward, the `nop; bsf LATA, 3; nop; bcf LATA, 3. This makes sense given that the programmer is responsible for directly driving clock signals on the remote chip in programming modes.

??? ... programmer dialog, scmd, loop, arguments, ... ???

there is another region of ths function that's similar but just a little different, predicated on the value in 0x2e1. if that value is above 2, a different bit banging loop is reached, down at 0x509c:

```
loop 2
```

this loop is the same as before as far as setting the data pin (LATA bit 2). the difference is that before alternating the clock three nops are used (rather than the earlier one nop), and those nops now have a loop around them. the timing loop counts down [0x2e1] times through W and advances when 0, and overall looks to be a bitbanging loop with longer timings between clock cycles. this may be relevant for other target devices, and a single "send this data" abstraction makes sense.

back to the question of the two arguments, while one is sent one bit at a time, the other seems to count the number of bits to send:

```
TODO: 0x50c8
```

so (4, 0) sends the bits `0000`, while (8, 0) sends `00000000`.

moving on to SCMD_COREINST24, which functionally should send three user-specified bytes to the target...

```
TODO: SCMD_COREINST24
```

as expected, this calls the same transmit function, and passes 4 and 8 in the same places. the major difference is how the second parameter is provided, where this version involves a bit of an incantation:

```
TODO: SCMD_COREINST24 0x41c6 to 0x41e2
```

since the three bytes for a pic24 instruction are part of the script (in fact, the three bytes after the `SCMD_COREINST24`), it stands to reason that the value passed is read out of the script buffer. interestingly, the logic appears to be:
* read the current script buffer offset (through `INDF2` at `0x41c6`)
* increment the offset (`0x41c8`)
* load the pointer to the script array (`0x41cc`-`0x41d4`) into `FRS0` - this comes from an argument!
* add the offset into the pointer (`0x41d8`)
* read that address (`0x41e0`)
* "push" the parameter (`0x41e2`)

this confirms that for the various commands we can count `INDF2` modification to determine the number of parameters for the various commands!

first ones of interest: `SCMD_WRITE_BUFBYTE_W` and `SCMD_WRITE_BUFWORD_W`

## `SCMD_WRITE_BUFWORD_W`

```
TODO: SCMD_WRITE_BUFWORD_W
```

this handler involves the same transmit function from earlier and also another function, `0x5006`.

guessing from context it appears to read from a global address and return the byte in W. `SCMD_WRITE_BUFWORD_W` then uses that as bits to send down the wire.

`SCMD_WRITE_BUFBYTE_W` does the same, but only reads one byte, the second value transmitted is a 0.

`SCMD_WRITE_BITS_LITERAL` takes two parameters, the number of bits and the pattern to send.
`SCMD_WRITE_BYTE_LITERAL` takes one parameter, the pattern, and assumes the number of bits to send is 8.
TODO: `SCMD_RD2_BYTE_BUFFER` is an unknown.
TODO: `SCMD_READ_BYTE`.
TODO: `SCMD_VISI24`. (transmits (4, 1), (8, 0), then some unknown calls.)

## `SCMD_DELAY_LONG`

passes the single byte parameter along to the function at `0x4966`, which...
```
TODO: 11 instructions at 0x4966
```

at `0x496e` the flag indicating `timer0` has fired is cleared, followed by multiplying the argument by 0xff and loading the result into `tmr0h`? this really has no result other than to negate it before setting the timer. from there, the timer is enabled and the controller spins until the timer fires (`0x497e`).

## `SCMD_LOOP`