XModem.Mos 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. MODULE XModem; (** AUTHOR "Timothée Martiel"; PURPOSE "XModem protocol implementation for bootloader"; *)
  2. (**
  3. Adapted from A2 sources: XModem.Mod
  4. *)
  5. IMPORT SYSTEM, Trace, StreamReaders, StreamWriters, EnetTiming, PrivateWatchdog;
  6. CONST
  7. DebugR = FALSE; (* debug receiver *)
  8. (* result codes *)
  9. Ok* = 0;
  10. Timeout* = 1;
  11. Error* = 2;
  12. (* states *)
  13. RInitCRC = 03X;
  14. RInitChksum = 04X;
  15. RData = 05X;
  16. Abort = 06X;
  17. Exit = 07X;
  18. (* Timeouts ([ms]) *)
  19. ReceiverInitTimeout = 3000;
  20. ReceiverDataTimeout = 1000;
  21. PurgeTimeout = 1000;
  22. MaxRetries = 10;
  23. MaxCRCThreshold = 3;
  24. HeaderSize = 3;
  25. BlockSize = 128;
  26. LargeBlock = 1024 - BlockSize;
  27. ChksumSize = 1;
  28. CRCSize = 2;
  29. PacketSizeChksum = HeaderSize + BlockSize + ChksumSize;
  30. PacketSizeCRC = HeaderSize + BlockSize + CRCSize;
  31. MaxPacketSize = PacketSizeCRC + LargeBlock;
  32. (* symbols *)
  33. SOH = 01X;
  34. STX = 02X;
  35. EOT = 04X;
  36. ACK = 06X;
  37. NAK = 15X;
  38. TYPE
  39. Callback * = PROCEDURE (CONST buffer: ARRAY OF CHAR; ofs, len: LONGINT);
  40. VAR
  41. state: CHAR; (* current state *)
  42. crc: BOOLEAN; (* TRUE iff in CRC mode, FALSE iff in checksum mode *)
  43. packetSize: LONGINT; (* size of a packet, including header & checksum/CRC. Initialized by SendInit/ReceiveInit *)
  44. retries: LONGINT; (* # of retries *)
  45. blockIndex: LONGINT; (* # of block *)
  46. msg: ARRAY 256 OF CHAR; (* error message *)
  47. crcThreshold: LONGINT;
  48. buf: ARRAY MaxPacketSize OF CHAR;
  49. (** Receive a file from XModem in buffer. Returns 0 if error, received length otherwise *)
  50. PROCEDURE Receive * (VAR in: StreamReaders.Reader; VAR out: StreamWriters.Writer; callback: Callback): LONGINT;
  51. BEGIN
  52. state := RInitCRC;
  53. blockIndex := 1; retries := 0; crcThreshold := 0;
  54. Purge(in);
  55. WHILE (state # Abort) & (state # Exit) & (retries < MaxRetries) DO
  56. CASE state OF
  57. | RInitCRC: ReceiveInitCRC(in, out)
  58. | RInitChksum: ReceiveInitChecksum(in, out)
  59. | RData: ReceiveData(in, out, callback)
  60. END
  61. ;PrivateWatchdog.Feed(3000)
  62. END;
  63. IF (state = Exit) THEN RETURN Ok
  64. ELSE RETURN Error
  65. END
  66. END Receive;
  67. PROCEDURE ReceiveInitCRC(VAR in: StreamReaders.Reader; VAR out: StreamWriters.Writer);
  68. VAR res: LONGINT; ch: CHAR;
  69. BEGIN
  70. IF DebugR THEN
  71. Trace.String("ReceiveInitCRC:"); Trace.Ln; Trace.String(" sending 'C'..."); Trace.Ln
  72. END;
  73. packetSize := PacketSizeCRC;
  74. (* send "C" *)
  75. crc := TRUE;
  76. StreamWriters.Char(out, "C"); StreamWriters.Update(out);
  77. (* wait for ACK *)
  78. IF DebugR THEN Trace.String(" waiting for reply...") END;
  79. res := Peek(in, ReceiverInitTimeout, ch);
  80. IF (res = Ok) THEN
  81. IF DebugR THEN Trace.String("got "); Trace.Hex(ORD(ch), -2); Trace.Char("X"); Trace.Ln END;
  82. CASE ch OF
  83. | SOH, STX: IF DebugR THEN Trace.String(" new state = RData"); Trace.Ln END; state := RData
  84. | EOT: IF DebugR THEN Trace.String(" new state = Abort"); Trace.Ln END;
  85. COPY("ReceiveInitCRC: got EOT", msg); state := Abort
  86. ELSE
  87. INC(retries); (* state stays the same (RInitCRC) *)
  88. COPY("ReceiveInitCRC: wrong reply", msg);
  89. IF DebugR THEN Trace.String(" retries = "); Trace.Int(retries, 0); Trace.Ln END
  90. END
  91. ELSE (* timeout *)
  92. INC(crcThreshold);
  93. IF DebugR THEN Trace.String(" timeout, CRC-threshold = "); Trace.Int(crcThreshold, 0); Trace.Ln END;
  94. IF (crcThreshold = MaxCRCThreshold) THEN
  95. IF DebugR THEN Trace.String(" switching to checksum-mode"); Trace.Ln END;
  96. crc := FALSE; retries := 0; state := RInitChksum
  97. END (* ELSE state stays the same (RInitCRC) *)
  98. END
  99. END ReceiveInitCRC;
  100. PROCEDURE ReceiveInitChecksum(VAR in: StreamReaders.Reader; VAR out: StreamWriters.Writer);
  101. VAR res: LONGINT; ch: CHAR;
  102. BEGIN
  103. packetSize := PacketSizeChksum;
  104. (* send NAK *)
  105. StreamWriters.Char(out, NAK); StreamWriters.Update(out);
  106. (* wait for transmission to begin *)
  107. res := Peek(in, ReceiverInitTimeout, ch);
  108. IF (res = Ok) THEN
  109. state := RData
  110. ELSE (* timeout *)
  111. COPY("ReceiveInitChecksum: timeout", msg);
  112. INC(retries) (* state stays the same (RInitChksum) *)
  113. END
  114. END ReceiveInitChecksum;
  115. PROCEDURE ReceiveData (VAR in: StreamReaders.Reader; VAR out: StreamWriters.Writer; callback: Callback);
  116. VAR ch: CHAR; res, i, idx, c, cc, blk, pkt: LONGINT; ok: BOOLEAN;
  117. BEGIN
  118. IF DebugR THEN
  119. Trace.String("ReceiveData:"); Trace.Ln; Trace.String(" waiting for first byte...")
  120. END;
  121. (* get first byte (SOH/EOT) *)
  122. res := Peek(in, ReceiverDataTimeout, ch);
  123. IF (res = 0) & ((ch = SOH) OR (ch = STX) OR (ch = EOT)) THEN
  124. IF DebugR THEN Trace.String("got "); Trace.Hex(ORD(ch), -2); Trace.Char("X"); Trace.Ln END;
  125. IF (ch = SOH) OR (ch = STX) THEN
  126. blk := BlockSize;
  127. pkt := packetSize;
  128. IF ch = STX THEN INC(pkt, LargeBlock); INC(blk, LargeBlock) END;
  129. IF DebugR THEN Trace.String(" receiving "); Trace.Int(pkt, 0); Trace.String(" bytes...") END;
  130. (* receive pkt bytes *)
  131. i := 0; res := 0;
  132. WHILE (i < pkt) & (res = 0) DO
  133. res := Get(in, ReceiverDataTimeout, buf[i]);
  134. INC(i)
  135. END;
  136. IF DebugR THEN Trace.String("done (got "); Trace.Int(i, 0); Trace.String(" bytes)"); Trace.Ln END;
  137. IF (res = 0) THEN
  138. idx := GetHeader(buf);
  139. IF (idx = blockIndex MOD 100H) THEN (* correct block number *)
  140. (* check checksum/CRC *)
  141. IF crc THEN
  142. c := LONG(ORD(buf[HeaderSize+blk]))*100H+ORD(buf[HeaderSize+blk+1]);
  143. cc := CalcCRC(buf, HeaderSize, blk);
  144. IF DebugR & (c # cc) THEN
  145. Trace.String(" wrong checksum: "); Trace.Hex(cc, 8); Trace.String(", expected "); Trace.Hex(c, 8); Trace.Ln
  146. END;
  147. ok := c = c
  148. ELSE
  149. ok := CalcCheckSum(buf, HeaderSize, blk) = buf[HeaderSize+blk]
  150. END;
  151. IF ok THEN
  152. IF DebugR THEN
  153. Trace.String(" received block "); Trace.Int(blockIndex, 0); Trace.Ln
  154. END;
  155. PutData(buf, HeaderSize, blk, callback);
  156. INC(blockIndex);
  157. StreamWriters.Char(out, ACK); StreamWriters.Update(out)
  158. ELSE
  159. COPY("ReceiveData: checksum error", msg);
  160. IF DebugR THEN Trace.String(" checksum error"); Trace.Ln END;
  161. INC(retries);
  162. StreamWriters.Char(out, NAK); StreamWriters.Update(out)
  163. END
  164. ELSIF (idx = (blockIndex-1) MOD 100H) THEN (* maybe the sender lost our ACK *)
  165. COPY("ReceiveData: got block n-1", msg);
  166. IF DebugR THEN Trace.String(" got block n-1"); Trace.Ln END;
  167. INC(retries); StreamWriters.Char(out, ACK); StreamWriters.Update(out)
  168. ELSE
  169. COPY("ReceiveData: wrong block number", msg);
  170. state := Abort;
  171. IF DebugR THEN
  172. Trace.String(" wrong block number"); Trace.Int(idx, 5); Trace.String(", expected ");
  173. Trace.Int(blockIndex, 0); Trace.Ln
  174. END
  175. END
  176. ELSE
  177. COPY("ReceiveData: timeout while receiving block", msg);
  178. state := Abort
  179. END
  180. ELSE (* ch = EOT *)
  181. IF (blockIndex = 1) THEN
  182. COPY("ReceiveData: got EOT instead of first block", msg);
  183. state := Abort
  184. ELSE
  185. StreamWriters.Char(out, ACK); StreamWriters.Update(out); state := Exit
  186. END
  187. END
  188. ELSE (* timeout/wrong character *)
  189. COPY("ReceiveData: timeout/wrong packet", msg);
  190. IF DebugR THEN
  191. Trace.String("timeout/wrong packet; res = "); Trace.Int(res, 0); Trace.String("; ch = ");
  192. Trace.Hex(ORD(ch), -2); Trace.Ln
  193. END;
  194. INC(retries);
  195. Purge(in);
  196. StreamWriters.Char(out, NAK); StreamWriters.Update(out)
  197. END
  198. END ReceiveData;
  199. PROCEDURE Get(VAR in: StreamReaders.Reader; timeout: LONGINT; VAR ch: CHAR): LONGINT;
  200. VAR milliTimer : EnetTiming.Timer;
  201. BEGIN
  202. IF (StreamReaders.Available(in) = 0) THEN
  203. EnetTiming.SetTimerMilli(milliTimer, timeout);
  204. EnetTiming.StartTimer(milliTimer);
  205. REPEAT PrivateWatchdog.Feed(1000)
  206. UNTIL (StreamReaders.Available(in) > 0) OR EnetTiming.IsTimerExpired(milliTimer);
  207. IF (StreamReaders.Available(in) = 0) THEN
  208. ch := 0X; RETURN Timeout
  209. END
  210. END;
  211. ch := StreamReaders.Get(in);
  212. RETURN Ok
  213. END Get;
  214. PROCEDURE Peek(VAR in: StreamReaders.Reader; timeout: LONGINT; VAR ch: CHAR): LONGINT;
  215. VAR milliTimer : EnetTiming.Timer;
  216. BEGIN
  217. IF (StreamReaders.Available(in) = 0) THEN
  218. EnetTiming.SetTimerMilli(milliTimer, timeout);
  219. EnetTiming.StartTimer(milliTimer);
  220. REPEAT PrivateWatchdog.Feed(1000)
  221. UNTIL (StreamReaders.Available(in) > 0) OR EnetTiming.IsTimerExpired(milliTimer);
  222. IF (StreamReaders.Available(in) = 0) THEN
  223. ch := 0X; RETURN Timeout
  224. END
  225. END;
  226. ch := StreamReaders.Peek(in);
  227. RETURN Ok
  228. END Peek;
  229. PROCEDURE Purge(VAR r: StreamReaders.Reader);
  230. VAR ch: CHAR;
  231. BEGIN
  232. REPEAT UNTIL Get(r, PurgeTimeout, ch) = Timeout
  233. END Purge;
  234. PROCEDURE CalcCheckSum(VAR buffer: ARRAY OF CHAR; ofs, len: LONGINT): CHAR;
  235. VAR i, chksum: LONGINT;
  236. BEGIN
  237. chksum := 0;
  238. FOR i := 0 TO len-1 DO chksum := chksum + ORD(buffer[ofs+i]) END;
  239. RETURN CHR(chksum MOD 100H)
  240. END CalcCheckSum;
  241. PROCEDURE CalcCRC(VAR buffer: ARRAY OF CHAR; ofs, len: LONGINT): LONGINT;
  242. VAR i, k, crc: LONGINT;
  243. BEGIN
  244. crc := 0;
  245. FOR i := 0 TO len-1 DO
  246. crc := SYSTEM.VAL(LONGINT, SYSTEM.VAL(SET, crc) / SYSTEM.VAL(SET, ORD(buffer[ofs+i])*100H));
  247. FOR k := 0 TO 7 DO
  248. IF (15 IN SYSTEM.VAL(SET, crc)) THEN
  249. crc := SYSTEM.VAL(LONGINT, SYSTEM.VAL(SET, crc*2) / SYSTEM.VAL(SET, 1021H))
  250. ELSE
  251. crc := crc*2
  252. END
  253. END
  254. END;
  255. RETURN crc MOD 10000H
  256. END CalcCRC;
  257. PROCEDURE GetErrorMessage*(VAR string: ARRAY OF CHAR);
  258. BEGIN
  259. COPY(msg, string)
  260. END GetErrorMessage;
  261. PROCEDURE PutData(VAR tmp: ARRAY OF CHAR; ofs, len: LONGINT; callback: Callback);
  262. BEGIN
  263. callback(tmp, ofs, len)
  264. END PutData;
  265. PROCEDURE GetHeader(VAR buf: ARRAY OF CHAR): LONGINT;
  266. VAR index: LONGINT;
  267. BEGIN
  268. IF (buf[0] = SOH) OR (buf[0] = STX) THEN
  269. index := ORD(buf[1]);
  270. IF (index # 255-ORD(buf[2])) THEN index := -1 END
  271. ELSE
  272. index := -1
  273. END;
  274. RETURN index
  275. END GetHeader;
  276. END XModem.