; ICMP implementation .include "../inc/common.inc" .include "../inc/error.inc" .export icmp_init .export icmp_process .export icmp_add_listener .export icmp_remove_listener .export icmp_callback .export icmp_inp .export icmp_outp .exportzp icmp_type .exportzp icmp_code .exportzp icmp_cksum .exportzp icmp_data .ifdef TCP .export icmp_echo_ip .export icmp_send_echo .export icmp_ping .endif .import ip65_process .import ip65_error .import ip_calc_cksum .import ip_inp .import ip_outp .import ip_broadcast .import ip_send .import ip_create_packet .importzp ip_proto .importzp ip_proto_icmp .importzp ip_cksum_ptr .importzp ip_header_cksum .importzp ip_src .importzp ip_dest .importzp ip_data .importzp ip_len .import eth_tx .import eth_inp .import eth_inp_len .import eth_outp .import eth_outp_len .import timer_read .import timer_timeout .data icmp_cbtmp: jmp $0000 ; temporary vector - address filled in later .bss ; argument for icmp_add_listener icmp_callback: .res 2 ; icmp callbacks icmp_cbmax = 2 icmp_cbveclo: .res icmp_cbmax ; table of listener vectors (lsb) icmp_cbvechi: .res icmp_cbmax ; table of listener vectors (msb) icmp_cbtype: .res icmp_cbmax ; table of listener types icmp_cbcount: .res 1 ; number of active listeners ; icmp packet offsets icmp_inp = ip_inp + ip_data ; pointer to inbound icmp packet icmp_outp = ip_outp + ip_data ; pointer to outbound icmp packet icmp_type = 0 ; offset of 'type' field in icmp packet icmp_code = 1 ; offset of 'code' field in icmp packet icmp_cksum = 2 ; offset of 'checksum' field in icmp packet icmp_data = 4 ; offset of 'data' field in icmp packet ; icmp echo packet offsets icmp_echo_id = 4 ; offset of 'id' field in icmp echo request/echo response icmp_echo_seq = 6 ; offset of 'sequence' field in icmp echo request/echo response icmp_echo_data = 8 ; offset of 'data' field in icmp echo request/echo response ; icmp type codes icmp_msg_type_echo_reply = 0 icmp_msg_type_destination_unreachable = 3 icmp_msg_type_source_quench = 4 icmp_msg_type_redirect = 5 icmp_msg_type_echo_request = 8 icmp_msg_type_time_exceeded = 11 icmp_msg_type_paramater_problem = 12 icmp_msg_type_timestamp = 13 icmp_msg_type_timestamp_reply = 14 icmp_msg_type_information_request = 15 icmp_msg_type_information_reply = 16 ; ping states ping_state_request_sent = 0 ping_state_response_received = 1 .ifdef TCP .bss icmp_echo_ip: .res 4 ; destination IP address for echo request ("ping") icmp_echo_cnt: .res 1 ; ping sequence counter ping_state: .res 1 ping_timer: .res 2 .endif .code ; initialize icmp ; inputs: none ; outputs: none icmp_init: lda #0 sta icmp_cbcount rts ; process incoming icmp packet ; inputs: ; eth_inp points to an ethernet frame containing an icmp packet ; outputs: ; carry flag - set on any error, clear if OK ; if inbound packet is a request (e.g. 'echo request') and an icmp listener ; has been installed, then an appropriate response message will be ; generated and sent out (overwriting the eth_outp buffer) icmp_process: lda icmp_inp + icmp_type cmp #icmp_msg_type_echo_request ; ping beq @echo lda icmp_cbcount ; any installed icmp listeners? beq @drop ldx icmp_cbcount ; check listened types dex : lda icmp_cbtype,x cmp icmp_inp + icmp_type beq @handle ; found a match dex bpl :- @drop: sec rts @handle: lda icmp_cbveclo,x ; copy vector sta icmp_cbtmp + 1 lda icmp_cbvechi,x sta icmp_cbtmp + 2 jsr icmp_cbtmp ; call listener clc rts @echo: lda ip_broadcast ; check if packet is broadcast beq @notbc sec ; don't reply to broadcast pings rts @notbc: ldx #5 : lda eth_inp,x ; swap dest and src mac sta eth_outp + 6,x lda eth_inp + 6,x sta eth_outp,x dex bpl :- ldx #12 ; copy the packet : lda eth_inp,x sta eth_outp,x inx cpx eth_inp_len bne :- ldx #3 : lda ip_inp + ip_src,x ; swap dest and src ip sta ip_outp + ip_dest,x lda ip_inp + ip_dest,x sta ip_outp + ip_src,x dex bpl :- lda #0 ; change type to reply sta icmp_outp + icmp_type lda icmp_inp + icmp_cksum ; recalc checksum clc adc #8 sta icmp_outp + icmp_cksum bcc :+ inc icmp_outp + icmp_cksum + 1 : lda eth_inp_len ; copy length sta eth_outp_len lda eth_inp_len + 1 sta eth_outp_len + 1 lda #0 ; clear checksum sta ip_outp + ip_header_cksum sta ip_outp + ip_header_cksum + 1 ldax #ip_outp ; calculate ip header checksum stax ip_cksum_ptr ldax #20 jsr ip_calc_cksum stax ip_outp + ip_header_cksum jsr eth_tx ; send packet clc rts ; add an icmp listener ; inputs: ; A = icmp type ; icmp_callback: vector to call when an icmp packet of specified type arrives ; outputs: ; carry flag - set if error, clear if no error icmp_add_listener: ldx icmp_cbcount ; any listeners at all? beq @add cpx #icmp_cbmax ; max? beq @full ldx #0 : cmp icmp_cbtype,x ; check if type is already listened beq @busy inx cpx icmp_cbcount bne :- @add: inc icmp_cbcount ; increase counter sta icmp_cbtype,x ; add type lda icmp_callback ; and vector sta icmp_cbveclo,x lda icmp_callback + 1 sta icmp_cbvechi,x clc rts @full: @busy: sec rts ; remove an icmp listener ; inputs: ; A = icmp type ; outputs: ; carry flag - set if error (i.e. no listner for this type exists), ; clear if no error icmp_remove_listener: ldx icmp_cbcount ; any listeners installed? beq @notfound dex : cmp icmp_cbtype,x ; check if type is listened beq @remove dex bpl :- @notfound: sec rts @remove: txa ; number of listeners below eor #$ff clc adc icmp_cbcount beq @done @move: tay ; number of items to move : lda icmp_cbtype + 1,x ; move type sta icmp_cbtype,x lda icmp_cbveclo + 1,x ; move vector lsb sta icmp_cbveclo,x lda icmp_cbvechi + 1,x ; move vector msb sta icmp_cbvechi,x inx dey bne :- @done: dec icmp_cbcount ; decrement counter clc rts .ifdef TCP ; icmp_send_echo was contributed by Glenn Holmer (ShadowM) ; send an ICMP echo ("ping") request ; inputs: ; icmp_echo_ip: destination IP address ; outputs: ; carry flag - set if error, clear if no error icmp_send_echo: ldy #3 : lda icmp_echo_ip,y sta ip_outp + ip_dest,y dey bpl :- lda #icmp_msg_type_echo_request sta icmp_outp + icmp_type lda #0 ; not used for echo packets sta icmp_outp + icmp_code sta icmp_outp + icmp_cksum ; clear checksum sta icmp_outp + icmp_cksum + 1 sta icmp_outp + icmp_echo_id ; set id to 0 sta icmp_outp + icmp_echo_id + 1 inc icmp_echo_cnt + 1 ; big-endian bne :+ inc icmp_echo_cnt : ldax icmp_echo_cnt stax icmp_outp + icmp_echo_seq ldy #0 : lda ip65_msg,y beq @set_ip_len sta icmp_outp + icmp_echo_data,y iny bne :- @set_ip_len: tya clc adc #28 ; IP header + ICMP type, code, cksum, id, seq sta ip_outp + ip_len + 1 ; high byte first lda #0 ; will never be >256 sta ip_outp + ip_len ldax #icmp_outp ; start of ICMP packet stax ip_cksum_ptr tya clc adc #8 ; ICMP type, code, cksum, id, seq ldx #0 ; AX = length of ICMP data jsr ip_calc_cksum stax icmp_outp + icmp_cksum lda #ip_proto_icmp sta ip_outp + ip_proto jsr ip_create_packet jmp ip_send ; send a ping (ICMP echo request) to a remote host, and wait for a response ; inputs: ; icmp_echo_ip: destination IP address ; outputs: ; carry flag - set if no response, otherwise AX is time (in miliseconds) for host to respond icmp_ping: lda #0 ; reset the "packet sent" counter sta icmp_echo_cnt @send_one_message: jsr icmp_send_echo bcc @message_sent_ok ; we couldn't send the message - most likely we needed to do an ARP lookup. ; so wait a bit, and retry jsr timer_read ; read current timer value stax ping_timer @loop_during_arp_lookup: jsr ip65_process ldax ping_timer adc #50 ; set timeout to now + 50 ms bcc :+ inx : jsr timer_timeout bcs @loop_during_arp_lookup jsr icmp_send_echo bcc @message_sent_ok ; still can't send? then give up lda #IP65_ERROR_TRANSMIT_FAILED sta ip65_error rts @message_sent_ok: jsr timer_read ; read current timer value stax ping_timer ldax #icmp_ping_callback stax icmp_callback lda #icmp_msg_type_echo_reply jsr icmp_add_listener lda #ping_state_request_sent sta ping_state @loop_till_get_ping_response: jsr ip65_process lda ping_state cmp #ping_state_response_received beq @got_reply ldax ping_timer inx ; x rolls over about 4 times per second inx ; so we will timeout after about 2 seconds inx inx inx inx inx inx jsr timer_timeout bcs @loop_till_get_ping_response lda #IP65_ERROR_TIMEOUT_ON_RECEIVE sta ip65_error lda #icmp_msg_type_echo_reply jsr icmp_remove_listener sec rts @got_reply: lda #icmp_msg_type_echo_reply jsr icmp_remove_listener jsr timer_read sec sbc ping_timer pha txa sbc ping_timer+1 tax pla clc rts icmp_ping_callback: lda icmp_inp + icmp_echo_seq cmp icmp_echo_cnt bne @not_what_we_were_waiting_for lda #ping_state_response_received sta ping_state @not_what_we_were_waiting_for: rts ip65_msg: .byte "ip65 - the 6502 IP stack",0 .endif ; -- LICENSE FOR icmp.s -- ; The contents of this file are subject to the Mozilla Public License ; Version 1.1 (the "License"); you may not use this file except in ; compliance with the License. You may obtain a copy of the License at ; http://www.mozilla.org/MPL/ ; ; Software distributed under the License is distributed on an "AS IS" ; basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the ; License for the specific language governing rights and limitations ; under the License. ; ; The Original Code is ip65. ; ; The Initial Developer of the Original Code is Per Olofsson, ; MagerValp@gmail.com. ; Portions created by the Initial Developer are Copyright (C) 2009 ; Per Olofsson. All Rights Reserved. ; ; Contributor(s): Jonno Downes, Glenn Holmer ; -- LICENSE END --