Mirror of CollapseOS
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

468 line
10KB

  1. ; shell
  2. ;
  3. ; Runs a shell over a block device interface.
  4. ; Status: incomplete. As it is now, it spits a welcome prompt, wait for input
  5. ; and compare the first 4 chars of the input with a command table and call the
  6. ; appropriate routine if it's found, an error if it's not.
  7. ;
  8. ; Commands, for now, are partially implemented.
  9. ;
  10. ; See constants below for error codes.
  11. ;
  12. ; All numerical values in the Collapse OS shell are represented and parsed in
  13. ; hexadecimal form, without prefix or suffix.
  14. ; *** DEFINES ***
  15. ; SHELL_GETC: Macro that calls a GetC routine for tty interface
  16. ; SHELL_PUTC: Macro that calls a PutC routine for tty interface
  17. ; SHELL_IO_GETC: Macro that calls a GetC routine for I/O ("load" cmd)
  18. ; SHELL_EXTRA_CMD_COUNT: Number of extra cmds to be expected after the regular
  19. ; ones. See comment in COMMANDS section for details.
  20. ; SHELL_RAMSTART
  21. ; *** CONSTS ***
  22. ; number of entries in shellCmdTbl
  23. SHELL_CMD_COUNT .equ 4+SHELL_EXTRA_CMD_COUNT
  24. ; maximum number of bytes to receive as args in all commands. Determines the
  25. ; size of the args variable.
  26. SHELL_CMD_ARGS_MAXSIZE .equ 3
  27. ; The command that was type isn't known to the shell
  28. SHELL_ERR_UNKNOWN_CMD .equ 0x01
  29. ; Arguments for the command weren't properly formatted
  30. SHELL_ERR_BAD_ARGS .equ 0x02
  31. ; Size of the shell command buffer. If a typed command reaches this size, the
  32. ; command is flushed immediately (same as pressing return).
  33. SHELL_BUFSIZE .equ 0x20
  34. ; *** VARIABLES ***
  35. ; Memory address that the shell is currently "pointing at" for peek, load, call
  36. ; operations. Set with mptr.
  37. SHELL_MEM_PTR .equ SHELL_RAMSTART
  38. ; Used to store formatted hex values just before printing it.
  39. SHELL_HEX_FMT .equ SHELL_MEM_PTR+2
  40. ; Places where we store arguments specifiers and where resulting values are
  41. ; written to after parsing.
  42. SHELL_CMD_ARGS .equ SHELL_HEX_FMT+2
  43. ; Command buffer. We read types chars into this buffer until return is pressed
  44. ; This buffer is null-terminated and we don't keep an index around: we look
  45. ; for the null-termination every time we write to it. Simpler that way.
  46. SHELL_BUF .equ SHELL_CMD_ARGS+SHELL_CMD_ARGS_MAXSIZE
  47. SHELL_RAMEND .equ SHELL_BUF+SHELL_BUFSIZE
  48. ; *** CODE ***
  49. shellInit:
  50. xor a
  51. ld (SHELL_MEM_PTR), a
  52. ld (SHELL_BUF), a
  53. ; print welcome
  54. ld hl, .welcome
  55. call printstr
  56. ret
  57. .welcome:
  58. .db "Collapse OS", ASCII_CR, ASCII_LF, "> ", 0
  59. shellLoop:
  60. ; First, let's wait until something is typed.
  61. SHELL_GETC
  62. ; got it. Now, is it a CR or LF?
  63. cp ASCII_CR
  64. jr z, .do ; char is CR? do!
  65. cp ASCII_LF
  66. jr z, .do ; char is LF? do!
  67. ; Echo the received character right away so that we see what we type
  68. SHELL_PUTC
  69. ; Ok, gotta add it do the buffer
  70. ; save char for later
  71. ex af, af'
  72. ld hl, SHELL_BUF
  73. call findnull ; HL points to where we need to write
  74. ; A is the number of chars in the buf
  75. cp SHELL_BUFSIZE
  76. jr z, .do ; A == bufsize? then our buffer is full. do!
  77. ; bring the char back in A
  78. ex af, af'
  79. ; Buffer not full, not CR or LF. Let's put that char in our buffer and
  80. ; read again.
  81. ld (hl), a
  82. ; Now, write a zero to the next byte to properly terminate our string.
  83. inc hl
  84. xor a
  85. ld (hl), a
  86. jr shellLoop
  87. .do:
  88. call printcrlf
  89. ld hl, SHELL_BUF
  90. call shellParse
  91. ; empty our buffer by writing a zero to its first char
  92. xor a
  93. ld (hl), a
  94. ld hl, .prompt
  95. call printstr
  96. jr shellLoop
  97. .prompt:
  98. .db "> ", 0
  99. printcrlf:
  100. ld a, ASCII_CR
  101. SHELL_PUTC
  102. ld a, ASCII_LF
  103. SHELL_PUTC
  104. ret
  105. ; Print the hex char in A
  106. printHex:
  107. push af
  108. push hl
  109. ld hl, SHELL_HEX_FMT
  110. call fmtHexPair
  111. ld a, 2
  112. call printnstr
  113. pop hl
  114. pop af
  115. ret
  116. ; Parse command (null terminated) at HL and calls it
  117. shellParse:
  118. push af
  119. push bc
  120. push de
  121. push hl
  122. push ix
  123. ld de, shellCmdTbl
  124. ld a, SHELL_CMD_COUNT
  125. ld b, a
  126. .loop:
  127. push de ; we need to keep that table entry around...
  128. call intoDE ; Jump from the table entry to the cmd addr.
  129. ld a, 4 ; 4 chars to compare
  130. call strncmp
  131. pop de
  132. jr z, .found
  133. inc de
  134. inc de
  135. djnz .loop
  136. ; exhausted loop? not found
  137. ld a, SHELL_ERR_UNKNOWN_CMD
  138. jr .error
  139. .found:
  140. ; we found our command. DE points to its table entry. Now, let's parse
  141. ; our args.
  142. call intoDE ; Jump from the table entry to the cmd addr.
  143. ; advance the HL pointer to the beginning of the args.
  144. ld a, 4
  145. call addHL
  146. ; Now, let's have DE point to the argspecs
  147. ld a, 4
  148. call addDE
  149. ; We're ready to parse args
  150. call shellParseArgs
  151. cp 0
  152. jr nz, .parseerror
  153. ld hl, SHELL_CMD_ARGS
  154. ; Args parsed, now we can load the routine address and call it.
  155. ; let's have DE point to the jump line
  156. ld a, SHELL_CMD_ARGS_MAXSIZE
  157. call addDE
  158. ld ixh, d
  159. ld ixl, e
  160. ; Ready to roll!
  161. call callIX
  162. cp 0
  163. jr nz, .error ; if A is non-zero, we have an error
  164. jr .end
  165. .parseerror:
  166. ld a, SHELL_ERR_BAD_ARGS
  167. .error:
  168. call shellPrintErr
  169. .end:
  170. pop ix
  171. pop hl
  172. pop de
  173. pop bc
  174. pop af
  175. ret
  176. ; Print the error code set in A (in hex)
  177. shellPrintErr:
  178. push af
  179. push hl
  180. ld hl, .str
  181. call printstr
  182. call printHex
  183. call printcrlf
  184. pop hl
  185. pop af
  186. ret
  187. .str:
  188. .db "ERR ", 0
  189. ; Parse arguments at (HL) with specifiers at (DE) into (SHELL_CMD_ARGS).
  190. ; (HL) should point to the character *just* after the name of the command
  191. ; because we verify, in the case that we have args, that we have a space there.
  192. ;
  193. ; Args specifiers are a series of flag for each arg:
  194. ; Bit 0 - arg present: if unset, we stop parsing there
  195. ; Bit 1 - is word: this arg is a word rather than a byte. Because our
  196. ; destination are bytes anyway, this doesn't change much except
  197. ; for whether we expect a space between the hex pairs. If set,
  198. ; you still need to have a specifier for the second part of
  199. ; the multibyte.
  200. ; Bit 2 - optional: If set and not present during parsing, we don't error out
  201. ; and write zero
  202. ;
  203. ; Sets A to nonzero if there was an error during parsing, zero otherwise.
  204. ; If there was an error during parsing, carry is set.
  205. shellParseArgs:
  206. push bc
  207. push de
  208. push hl
  209. push ix
  210. ld ix, SHELL_CMD_ARGS
  211. ld a, SHELL_CMD_ARGS_MAXSIZE
  212. ld b, a
  213. xor c
  214. .loop:
  215. ; init the arg value to a default 0
  216. xor a
  217. ld (ix), a
  218. ld a, (hl)
  219. ; is this the end of the line?
  220. cp 0
  221. jr z, .endofargs
  222. ; do we have a proper space char?
  223. cp ' '
  224. jr z, .hasspace ; We're fine
  225. ; is our previous arg a multibyte? (argspec still in C)
  226. bit 1, c
  227. jr z, .error ; bit not set? error
  228. dec hl ; offset the "inc hl" below
  229. .hasspace:
  230. ; Get the specs
  231. ld a, (de)
  232. bit 0, a ; do we have an arg?
  233. jr z, .error ; not set? then we have too many args
  234. ld c, a ; save the specs for the next loop
  235. inc hl ; (hl) points to a space, go next
  236. call parseHexPair
  237. jr c, .error
  238. ; we have a good arg and we need to write A in (IX).
  239. ld (ix), a
  240. ; Good! increase counters
  241. inc de
  242. inc ix
  243. inc hl ; get to following char (generally a space)
  244. djnz .loop
  245. ; If we get here, it means that our next char *has* to be a null char
  246. ld a, (hl)
  247. cp 0
  248. jr z, .success ; zero? great!
  249. jr .error
  250. .endofargs:
  251. ; We encountered our null char. Let's verify that we either have no
  252. ; more args or that they are optional
  253. ld a, (de)
  254. cp 0
  255. jr z, .success ; no arg? success
  256. bit 2, a
  257. jr nz, .success ; if set, arg is optional. success
  258. jr .error
  259. .success:
  260. xor a
  261. jr .end
  262. .error:
  263. inc a
  264. .end:
  265. pop ix
  266. pop hl
  267. pop de
  268. pop bc
  269. ret
  270. ; *** COMMANDS ***
  271. ; A command is a 4 char names, followed by a SHELL_CMD_ARGS_MAXSIZE bytes of
  272. ; argument specs, followed by the routine. Then, a simple table of addresses
  273. ; is compiled in a block and this is what is iterated upon when we want all
  274. ; available commands.
  275. ;
  276. ; Format: 4 bytes name followed by SHELL_CMD_ARGS_MAXSIZE bytes specifiers,
  277. ; followed by 3 bytes jump. fill names with zeroes
  278. ;
  279. ; When these commands are called, HL points to the first byte of the
  280. ; parsed command args.
  281. ;
  282. ; If the command is a success, it should set A to zero. If the command results
  283. ; in an error, it should set an error code in A.
  284. ;
  285. ; Extra commands: Other parts might define new commands. You can add these
  286. ; commands to your shell. First, set SHELL_EXTRA_CMD_COUNT to
  287. ; the number of extra commands to add, then add a ".dw"
  288. ; directive *just* after your '#include "shell.asm"'. Voila!
  289. ;
  290. ; Set memory pointer to the specified address (word).
  291. ; Example: mptr 01fe
  292. shellMptrCmd:
  293. .db "mptr", 0b011, 0b001, 0
  294. shellMptr:
  295. push hl
  296. ; reminder: z80 is little-endian
  297. ld a, (hl)
  298. ld (SHELL_MEM_PTR+1), a
  299. inc hl
  300. ld a, (hl)
  301. ld (SHELL_MEM_PTR), a
  302. ld hl, (SHELL_MEM_PTR)
  303. ld a, h
  304. call printHex
  305. ld a, l
  306. call printHex
  307. call printcrlf
  308. pop hl
  309. xor a
  310. ret
  311. ; peek byte where memory pointer points to any display its value. If the
  312. ; optional numerical byte arg is supplied, this number of bytes will be printed
  313. ;
  314. ; Example: peek 2 (will print 2 bytes)
  315. shellPeekCmd:
  316. .db "peek", 0b101, 0, 0
  317. shellPeek:
  318. push bc
  319. push de
  320. push hl
  321. ld a, (hl)
  322. cp 0
  323. jr nz, .arg1isset ; if arg1 is set, no need for a default
  324. ld a, 1 ; default for arg1
  325. .arg1isset:
  326. ld b, a
  327. ld hl, (SHELL_MEM_PTR)
  328. .loop: ld a, (hl)
  329. call printHex
  330. inc hl
  331. djnz .loop
  332. call printcrlf
  333. .end:
  334. pop hl
  335. pop de
  336. pop bc
  337. xor a
  338. ret
  339. ; Load the specified number of bytes (max 0xff) from IO and write them in the
  340. ; current memory pointer (which doesn't change). This gets chars from
  341. ; SHELL_IO_GETC, which can be different from SHELL_GETC. Coupled with the
  342. ; "blockdev" part, this allows you to dynamically select your IO source.
  343. ; Control is returned to the shell only after all bytes are read.
  344. ;
  345. ; Example: load 42
  346. shellLoadCmd:
  347. .db "load", 0b001, 0, 0
  348. shellLoad:
  349. push bc
  350. push hl
  351. ld a, (hl)
  352. ld b, a
  353. ld hl, (SHELL_MEM_PTR)
  354. .loop: SHELL_IO_GETC
  355. ld (hl), a
  356. inc hl
  357. djnz .loop
  358. .end:
  359. pop hl
  360. pop bc
  361. xor a
  362. ret
  363. ; Calls the routine where the memory pointer currently points. This can take two
  364. ; parameters, A and HL. The first one is a byte, the second, a word. These are
  365. ; the values that A and HL are going to be set to just before calling.
  366. ; Example: run 42 cafe
  367. shellCallCmd:
  368. .db "call", 0b101, 0b111, 0b001
  369. shellCall:
  370. push hl
  371. push ix
  372. ; Let's recap here. At this point, we have:
  373. ; 1. The address we want to execute in (SHELL_MEM_PTR)
  374. ; 2. our A arg as the first byte of (HL)
  375. ; 2. our HL arg as (HL+1) and (HL+2)
  376. ; Ready, set, go!
  377. ld a, (SHELL_MEM_PTR)
  378. ld ixl, a
  379. ld a, (SHELL_MEM_PTR+1)
  380. ld ixh, a
  381. ld a, (hl)
  382. ex af, af'
  383. inc hl
  384. ld a, (hl)
  385. exx
  386. ld h, a
  387. exx
  388. inc hl
  389. ld a, (hl)
  390. exx
  391. ld l, a
  392. ex af, af'
  393. call callIX
  394. .end:
  395. pop ix
  396. pop hl
  397. xor a
  398. ret
  399. ; This table is at the very end of the file on purpose. The idea is to be able
  400. ; to graft extra commands easily after an include in the glue file.
  401. shellCmdTbl:
  402. .dw shellMptrCmd, shellPeekCmd, shellLoadCmd, shellCallCmd