11/* A work-in-progess MEGA65 (Commodore 65 clone origins) emulator
22 Part of the Xemu project, please visit: https://github.com/lgblgblgb/xemu
3- Copyright (C)2018 LGB (Gábor Lénárt) <[email protected] > 3+ Copyright (C)2018,2025 LGB (Gábor Lénárt) <[email protected] > 44
55This program is free software; you can redistribute it and/or modify
66it under the terms of the GNU General Public License as published by
@@ -60,10 +60,12 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
6060#define SELECT_WAIT_USEC_MIN 5
6161#define SELECT_WAIT_INC_VAL 5
6262
63-
6463#define ETH_II_FRAME_ARP 0x0806
6564#define ETH_II_FRAME_IPV4 0x0800
6665
66+ #define RX_BUFFERS 4
67+
68+
6769static int eth_debug ;
6870
6971static volatile struct {
@@ -91,17 +93,18 @@ static volatile struct {
9193 int adjust_txd_phase ;
9294 int miim_register ;
9395 int phy_number ;
94- Uint8 rx_buffer [0x1000 ]; // actually, two 2048 bytes long buffers in one array
95- Uint8 tx_buffer [0x0800 ]; // a 2048 bytes long buffer to TX
9696} eth65 ;
9797
98+ static Uint8 rx_buffer [0x1000 ]; // actually, two 2048 bytes long buffers in one array
99+ static Uint8 tx_buffer [0x0800 ]; // a 2048 bytes long buffer to TX
98100
99101static Uint8 mac_address [6 ] = {0x02 ,0x47 ,0x53 ,0x65 ,0x65 ,0x65 }; // 00:80:10:... would be nicer, though a bit of cheating, it belongs to Commodore International(TM).
100102#ifdef HAVE_ETHERTAP
101103static const Uint8 mac_bcast [6 ] = {0xFF ,0xFF ,0xFF ,0xFF ,0xFF ,0xFF }; // also used as IPv4 bcast detection with filters, if only the first 4 bytes are checked
102104#endif
103105
104106static Uint16 miimlo8 [0x20 ], miimhi8 [0x20 ];
107+ static Uint8 eth_regs [0x10 ];
105108
106109#ifndef ETH65_NO_DEBUG
107110#define ETHDEBUG (...) do { \
@@ -150,11 +153,10 @@ static const char init_error_prefix[] = "ETH: disabled: ";
150153
151154static SDL_Thread * thread_id = NULL ;
152155
153- static XEMU_INLINE int ethernet_rx_processor ( void )
156+ static inline int ethernet_rx_processor ( void )
154157{
155158 Uint8 rx_temp_buffer [0x800 ];
156- int ethertype , ret ;
157- ret = xemu_tuntap_read (rx_temp_buffer , 6 + 6 + 2 , sizeof rx_temp_buffer );
159+ const int ret = xemu_tuntap_read (rx_temp_buffer , 6 + 6 + 2 , sizeof rx_temp_buffer );
158160 if (ret == -2 ) {
159161 eth65 .select_wait_usec = 1000000 ; // serious problem, do 1sec wait ...
160162 ETHDEBUG ("ETH-THREAD: read with EAGAIN, continue ..." NL );
@@ -185,7 +187,7 @@ static XEMU_INLINE int ethernet_rx_processor ( void )
185187 }
186188 // XEMU stuff, just to test TAP
187189 // it only recoginizes Ethernet-II frames, for frame types IPv4 or ARP
188- ethertype = (rx_temp_buffer [12 ] << 8 ) | rx_temp_buffer [13 ];
190+ const int ethertype = (rx_temp_buffer [12 ] << 8 ) | rx_temp_buffer [13 ];
189191 if (ethertype < 1536 ) {
190192 ETHDEBUG ("ETH-THREAD: ... however, skipped by XEMU: not an Ethernet-II frame ($%04X)" NL , ethertype );
191193 return 0 ;
@@ -228,11 +230,11 @@ static XEMU_INLINE int ethernet_rx_processor ( void )
228230 ETHDEBUG ("ETH-THREAD: ... MEGA[65]-COOL: we are ready to propogate packet" NL );
229231 // M65 stores the received frame size in "6502 byte order" as the first two bytes in the RX
230232 // (this makes things somewhat non-symmetric, as for TX, the size are in a pair of registers)
231- eth65 . rx_buffer [eth65 .rx_buffer_using ] = ret & 0xFF ;
232- eth65 . rx_buffer [eth65 .rx_buffer_using + 1 ] = ret >> 8 ;
233+ rx_buffer [eth65 .rx_buffer_using ] = ret & 0xFF ;
234+ rx_buffer [eth65 .rx_buffer_using + 1 ] = ret >> 8 ;
233235 for (int i = 0 ; i < ret ; i ++ )
234- eth65 . rx_buffer [eth65 .rx_buffer_using + 2 + i ] = rx_temp_buffer [i ];
235- //memcpy(eth65. rx_buffer + eth65.rx_buffer_using + 2, rx_temp_buffer, r);
236+ rx_buffer [eth65 .rx_buffer_using + 2 + i ] = rx_temp_buffer [i ];
237+ //memcpy(rx_buffer + eth65.rx_buffer_using + 2, rx_temp_buffer, r);
236238 eth65 .rx_enabled = 0 ; // disable RX till user not ACK'ed by swapping RX buffers
237239 RX_IRQ_ON (); // signal, that we have something! we don't support IRQs yet, but it's also used for polling!
238240 return 0 ;
@@ -241,7 +243,6 @@ static XEMU_INLINE int ethernet_rx_processor ( void )
241243
242244static XEMU_INLINE int ethernet_tx_processor ( void )
243245{
244- int ret , size ;
245246 if (eth65 .tx_size < 14 || eth65 .tx_size > ETH_FRAME_MAX_SIZE ) {
246247 ETHDEBUG ("ETH-THREAD: skipping TX, because invalid frame size: %d" NL , eth65 .tx_size );
247248 // still fake an OK answer FIXME ?
@@ -250,14 +251,15 @@ static XEMU_INLINE int ethernet_tx_processor ( void )
250251 return 0 ;
251252 }
252253#if 0
254+ int size ;
253255 // maybe this is not needed, and TAP device can handle autmatically, but anyway
254256 if (eth65 .tx_size < ETH_FRAME_MIN_SIZE ) {
255- memset ((void * )eth65 . tx_buffer + eth65 .tx_size , 0 , ETH_FRAME_MIN_SIZE - eth65 .tx_size );
257+ memset ((void * )tx_buffer + eth65 .tx_size , 0 , ETH_FRAME_MIN_SIZE - eth65 .tx_size );
256258 size = ETH_FRAME_MIN_SIZE ;
257259 } else
258260#endif
259- size = eth65 .tx_size ;
260- ret = xemu_tuntap_write ((void * )eth65 . tx_buffer , size );
261+ const int size = eth65 .tx_size ;
262+ const int ret = xemu_tuntap_write ((void * )tx_buffer , size );
261263 if (ret == -2 ) { // FIXME: with read OK, but for write????
262264 eth65 .select_wait_usec = 1000000 ; // serious problem, do 1sec wait ...
263265 ETHDEBUG ("ETH-THREAD: write with EAGAIN, continue ..." NL );
@@ -284,12 +286,10 @@ static XEMU_INLINE int ethernet_tx_processor ( void )
284286}
285287
286288
287-
288289static int ethernet_thread ( void * unused )
289290{
290291 ETHDEBUG ("ETH-THREAD: hello from the thread." NL );
291292 while (eth65 .enabled ) {
292- int ret ;
293293 /* ------------------- check for RX condition ------------------- */
294294 if (
295295 (!eth65 .rx_enabled ) && eth65 .no_reset &&
@@ -309,7 +309,7 @@ static int ethernet_thread ( void *unused )
309309 eth65 .select_wait_usec += SELECT_WAIT_INC_VAL ;
310310 }
311311 /* ------------------- The select() stuff ------------------- */
312- ret = xemu_tuntap_select (((eth65 .rx_enabled && eth65 .no_reset ) ? XEMU_TUNTAP_SELECT_R : 0 ) | ((eth65 .tx_trigger && eth65 .no_reset ) ? XEMU_TUNTAP_SELECT_W : 0 ), eth65 .select_wait_usec );
312+ const int ret = xemu_tuntap_select (((eth65 .rx_enabled && eth65 .no_reset ) ? XEMU_TUNTAP_SELECT_R : 0 ) | ((eth65 .tx_trigger && eth65 .no_reset ) ? XEMU_TUNTAP_SELECT_W : 0 ), eth65 .select_wait_usec );
313313 //ETHDEBUG("ETH-THREAD: after select with %d usecs, retval = %d, rx_enabled = %d, tx_trigger = %d" NL,
314314 // eth65.select_wait_usec, ret, eth65.rx_enabled, eth65.tx_trigger
315315 //);
@@ -349,10 +349,11 @@ static int ethernet_thread ( void *unused )
349349 however we want to keep them, so a network-aware software won't go crazy on totally missing register-level emulation */
350350
351351
352- Uint8 eth65_read_reg ( int addr )
352+ // "addr" must be between $0 and $F !!!
353+ Uint8 eth65_read_reg ( const unsigned int addr )
353354{
354- DEBUG ("ETH: reading register $%02X" NL , addr & 0xF );
355- switch (addr & 0xF ) {
355+ DEBUG ("ETH: reading register $%02X" NL , addr );
356+ switch (addr ) {
356357 /* **** $D6E0 register **** */
357358 case 0x00 :
358359 return eth65 .no_reset ; // FIXME: not other bits emulated yet here
@@ -380,6 +381,8 @@ Uint8 eth65_read_reg ( int addr )
380381 case 0x02 : return eth65 .tx_size & 0xFF ;
381382 /* **** $D6E3 register: TX size register high (4 bits only) **** */
382383 case 0x03 : return (eth65 .tx_size >> 8 ) & 0xFF ; // 4 bits, but tx_size var cannot contain more anyway
384+ /* **** $D6E4 register: on _reading_ it seems to gives back the total number of RX buffers what system has *** */
385+ case 0x04 : return RX_BUFFERS ;
383386 /* **** $D6E5 register **** */
384387 case 0x05 :
385388 return
@@ -401,24 +404,20 @@ Uint8 eth65_read_reg ( int addr )
401404 /* **** $D6E8 register **** */
402405 case 0x08 : return miimhi8 [eth65 .miim_register ];
403406 /* **** $D6E9 - $D6EE registers: MAC address **** */
404- case 0x09 : return mac_address [0 ];
405- case 0x0A : return mac_address [1 ];
406- case 0x0B : return mac_address [2 ];
407- case 0x0C : return mac_address [3 ];
408- case 0x0D : return mac_address [4 ];
409- case 0x0E : return mac_address [5 ];
407+ case 0x09 : case 0x0A : case 0x0B : case 0x0C : case 0x0D : case 0x0E :
408+ return mac_address [addr - 9 ];
410409 default :
411410 return 0xFF ;
412411 }
413412}
414413
415414
416-
417-
418- void eth65_write_reg ( int addr , Uint8 data )
415+ // "addr" must be between $0 and $F !!!
416+ void eth65_write_reg ( const unsigned int addr , const Uint8 data )
419417{
420- DEBUG ("ETH: writing register $%02X with data $%02X" NL , addr & 0xF , data );
421- switch (addr & 0xF ) {
418+ eth_regs [addr ] = data ;
419+ DEBUG ("ETH: writing register $%02X with data $%02X" NL , addr , data );
420+ switch (addr ) {
422421 /* **** $D6E0 register **** */
423422 case 0x00 :
424423 eth65 .no_reset = (data & 0x01 ); // FIXME: it seems to be the same as $D6E1 bit0???
@@ -493,30 +492,29 @@ void eth65_write_reg ( int addr, Uint8 data )
493492 miimhi8 [eth65 .miim_register ] = data ;
494493 break ;
495494 /* **** $D6E9 - $D6EE registers: MAC address **** */
496- case 0x09 : mac_address [0 ] = data ; break ;
497- case 0x0A : mac_address [1 ] = data ; break ;
498- case 0x0B : mac_address [2 ] = data ; break ;
499- case 0x0C : mac_address [3 ] = data ; break ;
500- case 0x0D : mac_address [4 ] = data ; break ;
501- case 0x0E : mac_address [5 ] = data ; break ;
495+ case 0x09 : case 0x0A : case 0x0B : case 0x0C : case 0x0D : case 0x0E :
496+ mac_address [addr - 9 ] = data ;
497+ break ;
502498 }
503499}
504500
505501
506-
507- Uint8 eth65_read_rx_buffer ( int offset )
502+ // Note: "offset" shouldn't be trusted fully *EVER*, only its lower 11 bits (0-$7FF)
503+ // Though, this is used by memory_mapper.c where the parameter is "addr32" (physical address),
504+ // here we re-use this functionality with io_mapper.c
505+ Uint8 eth65_buffer_reader ( const Uint32 offset )
508506{
509507 // FIXME what happens if M65 receives frame but user switches buffer meanwhile. Not so nice :-O
510- return eth65 . rx_buffer [eth65 .rx_buffer_mapped + (offset & 0x7FF )];
508+ return rx_buffer [eth65 .rx_buffer_mapped + (offset & 0x7FFU )];
511509}
512510
513- void eth65_write_tx_buffer ( int offset , Uint8 data )
511+ // See the note before eth65_buffer_reader()
512+ void eth65_buffer_writer ( const Uint32 offset , const Uint8 data )
514513{
515- eth65 . tx_buffer [offset & 0x7FF ] = data ;
514+ tx_buffer [offset & 0x7FFU ] = data ;
516515}
517516
518517
519-
520518void eth65_shutdown ( void )
521519{
522520 if (eth65 .exited )
@@ -540,13 +538,14 @@ void eth65_shutdown ( void )
540538// This is not the reset what eth65.no_reset stuff does, but reset of M65 (also called by eth65_init)
541539void eth65_reset ( void )
542540{
541+ memset (& eth_regs , 0 , sizeof eth_regs );
543542 memset (& miimlo8 , 0 , sizeof miimlo8 );
544543 memset (& miimhi8 , 0 , sizeof miimhi8 );
545544 memset ((void * )& eth65 , 0 , sizeof eth65 );
546545 RX_IRQ_OFF ();
547546 TX_IRQ_OFF ();
548- memset ((void * )eth65 . rx_buffer , 0xFF , sizeof eth65 . rx_buffer );
549- memset ((void * )eth65 . tx_buffer , 0xFF , sizeof eth65 . tx_buffer );
547+ memset ((void * )rx_buffer , 0xFF , sizeof rx_buffer );
548+ memset ((void * )tx_buffer , 0xFF , sizeof tx_buffer );
550549 eth65 .select_wait_usec = SELECT_WAIT_USEC_MAX ;
551550 eth65 .rx_buffer_using = 0x800 ; // RX buffer is used to RX @ ofs 0
552551 eth65 .rx_buffer_mapped = 0x000 ; // RX buffer mapped @ ofs 0
0 commit comments