Up one Vic 20 network - software by Lee Davison Up to top

Talking to the 3Com card.

If you're going to write code for any network card you need to get the technical reference documents for that card, don't rely on other people's code, even mine, as you will end up repeating any mistakes they made and avoid the discovery of your own spectacular errors. For the 3Com 3C509B-TPO you need 3c5x9b.pdf from the 3Com site.

Initialisation.

To initialise the card after a reset you need to do quite a bit of work. This is reflected in the aparent complexity of the startup code, fortunately this only has to be called once.


; *********************************************************************		
; initialize the 3C509B

; jump through 3Com hoops - don't ask, just jump.

LAB_10500
	LDY	#$00			; clear Y
	STY	id_port			; initialise the id sequence state ..
	STY	id_port			; .. machine by writing $00,$00

					; send id sequence to NIC id port
	DEY				; set for 255 bytes
	TYA				; set initial byte
LAB_10520
	STA	id_port			; send id sequence state machine byte
	ASL				; shift byte
	BCC	LAB_10535		; branch if no carry

	EOR	#$CF			; else EOR with $CF
LAB_10535
	DEY				; increment count
	BNE	LAB_10520		; loop if not all done
	
					; no contention check, only 1 card!
	LDA	#$D1			; set tag to 1
	STA	id_port			; send id port byte
	LDA	#$F0			; set I/O base to $0300
	STA	id_port			; send id port byte

	LDA	#$08			;
	STY	com_reg_l		; $00 page 0
	STA	com_reg_h		; $08 set window command

					; read our MAC address from the card
	LDX	#$00			; clear index
LAB_10560
	TXA				; copy index
	LSR				; /2 (EEPROM reads as word wide)
	ORA	#$80			; OR with EEPROM read command
	STA	epr_ctr_l		; set EEPROM read address low byte
	STY	epr_ctr_h		; set EEPROM read address high byte

					; wait for at least 162 uS
	LDY	#$24			; [2] cycles plus .. [PAL]
	LDY	#$21			; [2] cycles plus .. [NTSC]
LAB_10565
	DEY				; [2] cycles plus ..
	BNE	LAB_10565		; [3/2] cycles = 5 * Y plus 1 cycles
					; = 181 cycles = 163uS [PAL]
					; = 166 cycles = 162uS [NTSC]

	LDY	epr_dat_l		; get MAC address low byte
	LDA	epr_dat_h		; get MAC address high byte
	STA	mac_ours,X		; save MAC address high byte
	INX				; increment index
	TYA				; copy MAC address low byte
	STA	mac_ours,X		; save MAC address low byte
	LDY	#$00			; clear Y
	INX				; increment index
	CPX	#$06			; are we done yet
	BNE	LAB_10560		; loop if not

					; enable the adaptor
	LDA	#$01			;
 	STA	config_c_l		; $01
 	STY	config_c_h		; $00 set configuration control

					; reset the Tx ..
	LDA	#$58			;
	STY	com_reg_l		; $00
	STA	com_reg_h		; $58 Tx reset
	JSR	wait_cmd		; wait for Tx reset to complete

					; .. and reset the Rx ..
	LDA	#$28			;
	STY	com_reg_l		; $00
	STA	com_reg_h		; $28 Rx reset 
	JSR	wait_cmd		; wait for Rx reset to complete

					; .. and suspend the status
	LDA	#$78			;
	STY	com_reg_l		; $00
	STA	com_reg_h		; $78 mask all status bits

					; set IRQ3
	LDA	#$3F			;
 	STY	r_config_l		; $00
 	STA	r_config_h		; $3F resource configuration
	
					; set our MAC address & copy MAC low
					; word as our IP address low word
	LDA	#$02			;
	STA	com_reg_l		;
	LDA	#$08			;
	STA	com_reg_h		; set page 2

	LDA	mac_ours_00		; get MAC address byte 0
	STA	MAC_adr_00		; save to card
	LDA	mac_ours_01		; get MAC address byte 1
	STA	MAC_adr_01		; save to card
	LDA	mac_ours_02		; get MAC address byte 2
	STA	MAC_adr_02		; save to card
	LDA	mac_ours_03		; get MAC address byte 3
	STA	MAC_adr_03		; save to card
	LDA	mac_ours_04		; get MAC address byte 4
	STA	MAC_adr_04		; save to card
	STA	ip_ours+$02		; save IP byte
	LDA	mac_ours_05		; get MAC address byte 5
	STA	MAC_adr_05		; save to card
	STA	ip_ours+$03		; save IP byte

					; enable link beat & jabber for TP
	LDA	#$04			;
	STA	com_reg_l		;
	LDA	#$08			;
	STA	com_reg_h		; set page 4

	LDA	#$C0			;
	STA	media_t_l		; set media type & status
					; only the low byte needed

	LDA	#$B0			;
	STY	com_reg_l		; $00
	STA	com_reg_h		; $B0 stats disable

	LDA	#$06			;
	STA	com_reg_l		;
	LDA	#$08			;
	STA	com_reg_h		; set page 6

					; clear stats by reading them
	LDX	#$00			; clear index
LAB_10655
	LDA	data_reg,X		; read stat byte
	INX				; increment index
	CPX	#$0E			; are we done yet
	BNE	LAB_10655		; loop if not

	LDA	#$A8			;
	STY	com_reg_l		; $00
	STA	com_reg_h		; $A8 stats enable

					; receive host address and broadcasts
	LDA	#$01			;
	STA	com_reg_l		;
	LDA	#$08			;
	STA	com_reg_h		; set page 1

	LDA	#$05			;
	STA	com_reg_l		;
	LDA	#$80			;
	STA	com_reg_h		; set Rx filter

					; enable Rx and Tx
	LDA	#$20			;
	STY	com_reg_l		; $00
	STA	com_reg_h		; $20 Rx enable

	LDA	#$48			;
	STY	com_reg_l		; $00
	STA	com_reg_h		; $48 Tx enable

					; enable those status bits we want
	LDA	#$16			; bit 4 Rx complete
					; bit 2 Tx complete
					; bit 1 adapter failure
					; bit 0 interrupt latch
	STA	com_reg_l		;
	LDA	#$78			;
	STA	com_reg_h		; set read zero mask

					; ack any pending interrupts
	LDA	#$69			; bit 6 interrupt requested
					; bit 5 rx early
					; bit 3 tx available
					; bit 0 interrupt latch
	STA	com_reg_l		;
	LDA	#$68			;
	STA	com_reg_h		; ack interrupts

					; enable those interrupts we want
	LDA	#$16			; bit 4 Rx complete
					; bit 2 Tx complete
					; bit 1 adapter failure
					; bit 0 interrupt latch
	STA	com_reg_l		;
	LDA	#$70			;
	STA	com_reg_h		; set interrupt mask

	RTS

; *********************************************************************

Command waiting.

When this was all done in EhBASIC it was easy to blindly issue commands to the network card and assume they would be finished before the next command was issued. However, when running assembled code it is easy to issue a new command before some commands have completed causing things to go horribly wrong. To fix this the status register can be polled repeatedly until the command in progress bit clears.

This sounds like a good plan but to do this you must disable interrupts while polling the status bit, if an interrupt condition is met while interrupts are disabled it won't cause an interrupt and will be missed, unless you poll the interrupt bits as well. This adds some complexity to the code so for now the code just waits longer than the longest command takes, about half a milisecond.


; *********************************************************************		
; wait for an NIC command to complete

wait_cmd
	CLC				; clear carry for add
	LDA	#$80			; count $80
wait_loop
	ADC	#$01			; waste cycles
	BCC	wait_loop		; loop for more

	RTS
; *********************************************************************

Receiver interrupt handling.

When a receive interrupt is flagged this code gets called to handle it. The receive status is read and if there is a valid packet of the correct size waiting it is copied from the network card and into the packet buffer. The valid packet flag is then set.

Packets that are too large or that are flagged as having errors (though this should never happen, the card should discard these itself) are not copied. All packets are discarded once dealt with to clear the space in the network card buffer. Once this is done the card is also free to generate an interrupt for any other packets received.


; *********************************************************************		
; handle Rx interrupt flagged, get the ethernet packet from the NIC
; exit with length in rx_len, payload in pk_buff(). valid_pkt flags the
; packet status

LAB_20100
	LDX	#$00			;
	STX	valid_pkt		; clear packet valid flag

	LDA	#$EF			; xxx0 xxxx, Rx interrupt bit
	AND	IB			; clear IB bit(s)
	STA	IB			; save IB

	LDX	#$01			;
	LDA	#$08			;
	STX	com_reg_l		; $01
	STA	com_reg_h		; $08 set page 1

	LDA	rx_stat_l		; get Rx status low byte
	STA	tc_l			; save length to temp count low byte

	LDA	rx_stat_h		; get Rx status high byte
	CMP	#$40			; test error bit
	BCS	LAB_20145		; discard packet & exit if Rx error

	AND	#$07			; else mask length bits
	STA	tc_h			; save length to temp count high byte
	CMP	#$05			; compare with max length high byte
	BCC	LAB_20135		; branch if ok (less)

	BNE	LAB_20145		; discard packet & exit if too big

	LDA	tc_l			; else get length low byte
	CMP	#$DD			; compare with max+1
	BCS	LAB_20145		; discard packet & exit if too big

; get packet from NIC

LAB_20135
	LDA	#>pk_buff		; get IP buffer pointer high byte
	STA	tp_h			; save to temp pointer high byte
	LDA	#<pk_buff		; get IP buffer pointer low byte
	STA	tp_l			; save to temp pointer low byte
	LDY	#$00			; clear index
LAB_20136
	LDA	data_reg		; get byte from NIC
	STA	(tp_l),Y		; save to IP buffer
	INY				; increment index (pointer low byte)
	BNE	LAB_20137		; branch if no overflow

	INC	tp_h			; else increment pointer high byte
LAB_20137
	DEC	tc_l			; decrement count low byte
	BNE	LAB_20136		; loop if not all done

	DEC	tc_h			; else decrement count high byte
	BPL	LAB_20136		; loop if not all done

	DEC	valid_pkt		; flag this packet good
LAB_20145
	LDX	#$00			;
	LDA	#$40			;
	STX	com_reg_l		; $00
	STA	com_reg_h		; $40 discard top packet
	JSR	wait_cmd		; wait for discard to complete
	RTS


; *********************************************************************

Transmitter interrupt handling.

This is really not necessary and, if the card is set up not to generate transmit interrupts, could be dispensed with. Under normal conditions the interrupt only flags that a packet has been successfully sent and the only action that needs to be taken is to acknowledge it by writing to the Tx status register. However this code was used for status tracking and so has been left in as example code.


; *********************************************************************		
; handle Tx interrupt flagged

LAB_20200
	LDA	#$FB			; xxxx x0xx, Tx interrupt bit
	AND	IB			; clear IB bit
	STA	IB			; save IB

	LDX	#$01			;
	LDA	#$08			;
	STX	com_reg_l		; $01
	STA	com_reg_h		; $08 set page 1

	LDA	tx_stat_h		; read Tx status (high byte only)
	AND	#$3C			; mask all Tx errors
	BEQ	LAB_20220		; branch if no errors

	DEX				; clear X
	AND	#$30			; mask jabber or underrun
	BEQ	LAB_20215		; branch if no jabber or underrun

					; else reset Tx
	LDA	#$58			;
 	STX	com_reg_l		; $00
	STA	com_reg_h		; $58 Tx reset
	JSR	wait_cmd		; wait for Tx reset to complete

LAB_20215				; enable Tx if any errors indicated
	LDA	#$48			;
 	STX	com_reg_l		; $00
	STA	com_reg_h		; $48 Tx enable

LAB_20220
 	STX	tx_stat_h		; ack Tx interrupts by writing any
					; value to the Tx status word
	RTS


; *********************************************************************

Adapter fail interrupt handling.

Again some code that is not essential to normal operation. The usual reason for an adapter fail to be flagged is that too many bytes have been written to or read from the card's FIFO buffer. This code was essential during developement and is retained as an example.


; *********************************************************************		
; handle adapter fail interrupt flagged

LAB_20300
	LDA	#$FD			; xxxx xx0x, adapter fail interrupt bit
	AND	IB			; clear IB bit(s)
	STA	IB			; save IB

	LDX	#$04			;
	LDA	#$08			;
	STX	com_reg_l		; $04
	STA	com_reg_h		; $08 page 4

	LDX	FIFO_dag_h		; get fail reason

	LDY	#$01			;
	STY	com_reg_l		; $01
	STA	com_reg_h		; $08 page 1

	TXA				; get fail reason back
	AND	#$20			; test Rx underflow
	BEQ	LAB_20330		; branch if no Rx underflow

					; else clear flags ..
	LDA	#$EF			; xxx0 xxxx, Rx interrupt bit
	AND	IB			; clear IB bit(s)
	STA	IB			; save IB

					; .. reset the Rx ..
	DEY				; Y is now $00
	LDA	#$28			;
	STY	com_reg_l		; $00
	STA	com_reg_h		; $28 Rx reset
	JSR	wait_cmd		; wait for Rx reset to complete

					; .. and restart the receiver
	LDA	#$20			;
	STY	com_reg_l		; $00
	STA	com_reg_h		; $20 Rx enable

LAB_20330
	TXA				; get fail reason back
	AND	#$04			; test Tx overflow
	BEQ	LAB_20350		; branch if no Tx overflow

					; else reset the Tx ..
	LDA	#$58			;
	STY	com_reg_l		; $00
	STA	com_reg_h		; $58 Tx reset
	JSR	wait_cmd		; wait for Tx reset to complete

					; .. and restart it
	LDA	#$48
	STY	com_reg_l		; $00
	STA	com_reg_h		; $48 Tx enable
LAB_20350
	RTS

; *********************************************************************


Last page update: 29th February, 2004. e-mail me e-mail