Mirror of CollapseOS
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

506 řádky
11KB

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