51 #define ETH_FRAME_MAX_SIZE 1536
55 #define ETH_FRAME_MIN_SIZE 60
58 #define SELECT_WAIT_USEC_MAX 10000
61 #define SELECT_WAIT_USEC_MIN 5
62 #define SELECT_WAIT_INC_VAL 5
65 #define ETH_II_FRAME_ARP 0x0806
66 #define ETH_II_FRAME_IPV4 0x0800
70 static volatile struct {
100 static Uint8 mac_address[6] = {0x02,0x47,0x53,0x65,0x65,0x65};
102 static const Uint8 mac_bcast[6] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
105 static Uint16 miimlo8[0x20], miimhi8[0x20];
107 #ifndef ETH65_NO_DEBUG
108 #define ETHDEBUG(...) do { \
109 if (XEMU_UNLIKELY(eth_debug)) { \
110 DEBUGPRINT(__VA_ARGS__); \
114 #define ETHDEBUG(...)
119 static void __eth65_manage_irq_for_cpu (
void )
121 int status = ((eth65._rx_irq && eth65.rx_irq_enabled) || (eth65._tx_irq && eth65.tx_irq_enabled));
122 if (
status != eth65._irq_to_cpu_routed) {
123 ETHDEBUG(
"ETH: IRQ change should be propogated: %d -> %d" NL, eth65._irq_to_cpu_routed,
status);
124 eth65._irq_to_cpu_routed =
status;
128 #define RX_IRQ_ON() do { eth65._rx_irq = 0x20; __eth65_manage_irq_for_cpu(); } while (0)
129 #define RX_IRQ_OFF() do { eth65._rx_irq = 0; __eth65_manage_irq_for_cpu(); } while (0)
130 #define TX_IRQ_ON() do { eth65._tx_irq = 0x10; __eth65_manage_irq_for_cpu(); } while (0)
131 #define TX_IRQ_OFF() do { eth65._tx_irq = 0; __eth65_manage_irq_for_cpu(); } while (0)
150 static const char init_error_prefix[] =
"ETH: disabled: ";
152 static SDL_Thread *thread_id = NULL;
154 static XEMU_INLINE int ethernet_rx_processor (
void )
156 Uint8 rx_temp_buffer[0x800];
158 ret = xemu_tuntap_read(rx_temp_buffer, 6 + 6 + 2,
sizeof rx_temp_buffer);
160 eth65.select_wait_usec = 1000000;
161 ETHDEBUG(
"ETH-THREAD: read with EAGAIN, continue ..." NL);
165 ETHDEBUG(
"ETH-THREAD: read error: %s" NL, strerror(errno));
169 eth65.select_wait_usec = 1000000;
170 ETHDEBUG(
"ETH-THREAD: read() returned with zero! continue ..." NL);
175 eth65.select_wait_usec = 1000000;
176 ETHDEBUG(
"ETH-THREAD: read() had %d bytes, too long! continue ..." NL, ret);
181 ETHDEBUG(
"ETH-THREAD: read() returned with %d bytes read, OK!" NL, ret);
182 eth65.sense_activity = 1;
184 ETHDEBUG(
"ETH-THREAD: ... however, insane sized frame, skipping" NL);
189 ethertype = (rx_temp_buffer[12] << 8) | rx_temp_buffer[13];
190 if (ethertype < 1536) {
191 ETHDEBUG(
"ETH-THREAD: ... however, skipped by XEMU: not an Ethernet-II frame ($%04X)" NL, ethertype);
195 ETHDEBUG(
"ETH-THREAD: ... however, skipped by XEMU: not ARP or IPv4 Ethernet-II ethertype ($%04X)" NL, ethertype);
200 (eth65.mac_filter || eth65.xemu_always_filters) &&
201 memcmp(rx_temp_buffer, mac_bcast, 6) &&
202 memcmp(rx_temp_buffer, mac_address, 6)
204 ETHDEBUG(
"ETH-THREAD: ... however, MAC filter ruled it out %02X:%02X:%02X:%02X:%02X:%02X" NL,
205 rx_temp_buffer[0], rx_temp_buffer[1], rx_temp_buffer[2], rx_temp_buffer[3], rx_temp_buffer[4],rx_temp_buffer[5]
209 ETHDEBUG(
"ETH-THREAD: ... cool, target MAC seems to survive MAC filter %02X:%02X:%02X:%02X:%02X:%02X" NL,
210 rx_temp_buffer[0], rx_temp_buffer[1], rx_temp_buffer[2], rx_temp_buffer[3], rx_temp_buffer[4],rx_temp_buffer[5]
214 ret >= 20 + 8 + 14 &&
216 (rx_temp_buffer[15] & 0xF0) == 0x40
218 if ((!eth65.accept_broadcast || eth65.xemu_always_filters) && !memcmp(rx_temp_buffer + 30, mac_bcast, 4)) {
219 ETHDEBUG(
"ETH-THREAD: ... however, IP filter ruled it out: broadcast (%d.%d.%d.%d)" NL, rx_temp_buffer[30], rx_temp_buffer[31], rx_temp_buffer[32], rx_temp_buffer[33]);
223 if ((!eth65.accept_multicast || eth65.xemu_always_filters) && (rx_temp_buffer[30] & 0xF0) == 0xE0) {
224 ETHDEBUG(
"ETH-THREAD: ... however, IP filter ruled it out: multicast (%d.%d.%d.%d)" NL, rx_temp_buffer[30], rx_temp_buffer[31], rx_temp_buffer[32], rx_temp_buffer[33]);
227 ETHDEBUG(
"ETH-THREAD: ... cool, target IP seems to survive IP filter %d.%d.%d.%d" NL, rx_temp_buffer[30], rx_temp_buffer[31], rx_temp_buffer[32], rx_temp_buffer[33]);
229 ETHDEBUG(
"ETH-THREAD: ... MEGA[65]-COOL: we are ready to propogate packet" NL);
232 eth65.rx_buffer[eth65.rx_buffer_using] = ret & 0xFF;
233 eth65.rx_buffer[eth65.rx_buffer_using + 1] = ret >> 8;
234 for (
int i = 0; i < ret; i++)
235 eth65.rx_buffer[eth65.rx_buffer_using + 2 + i] = rx_temp_buffer[i];
237 eth65.rx_enabled = 0;
243 static XEMU_INLINE int ethernet_tx_processor (
void )
247 ETHDEBUG(
"ETH-THREAD: skipping TX, because invalid frame size: %d" NL, eth65.tx_size);
250 eth65.tx_trigger = 0;
256 memset((
void*)eth65.tx_buffer + eth65.tx_size, 0,
ETH_FRAME_MIN_SIZE - eth65.tx_size);
260 size = eth65.tx_size;
261 ret = xemu_tuntap_write((
void*)eth65.tx_buffer,
size);
263 eth65.select_wait_usec = 1000000;
264 ETHDEBUG(
"ETH-THREAD: write with EAGAIN, continue ..." NL);
268 ETHDEBUG(
"ETH-THREAD: write error: %s" NL, strerror(errno));
272 eth65.select_wait_usec = 1000000;
273 ETHDEBUG(
"ETH-THREAD: write() returned with zero! continue ..." NL);
277 ETHDEBUG(
"ETH-THREAD: partial write only?! wanted = %d, written = %d" NL,
size, ret);
280 ETHDEBUG(
"ETH-THREAD: write() returned with %d bytes read, OK!" NL, ret);
281 eth65.sense_activity = 1;
282 eth65.tx_trigger = 0;
289 static int ethernet_thread (
void *unused )
291 ETHDEBUG(
"ETH-THREAD: hello from the thread." NL);
292 while (eth65.enabled) {
296 (!eth65.rx_enabled) && eth65.no_reset &&
297 (eth65.rx_buffer_mapped == eth65.rx_buffer_using)
299 eth65.rx_enabled = 1;
300 eth65.rx_buffer_using = eth65.rx_buffer_mapped ^ 0x800;
301 eth65.sense_activity = 1;
302 ETHDEBUG(
"ETH-THREAD: rx enabled flipped to OK!" NL);
305 if (eth65.sense_activity) {
307 eth65.sense_activity = 0;
313 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);
318 ETHDEBUG(
"ETH-THREAD: EMERGENCY STOP: select error: %s" NL, strerror(errno));
322 if (eth65.rx_enabled && eth65.no_reset && (ret & XEMU_TUNTAP_SELECT_R)) {
323 if (ethernet_rx_processor()) {
324 ETHDEBUG(
"ETH-THREAD: EMERGENCY STOP: requested by RX processor." NL);
330 if (eth65.tx_trigger && eth65.no_reset && (ret & XEMU_TUNTAP_SELECT_W)) {
331 if (ethernet_tx_processor()) {
332 ETHDEBUG(
"ETH-THREAD: EMERGENCY STOP: requested by TX processor." NL);
355 DEBUG(
"ETH: reading register $%02X" NL,
addr & 0xF);
356 switch (
addr & 0xF) {
359 return eth65.no_reset;
366 (eth65.rx_buffer_mapped ? 0x02 : 0 ) |
368 (eth65.rx_buffer_using ? 0x04 : 0 ) |
370 eth65.video_streaming |
376 eth65.tx_irq_enabled |
381 case 0x02:
return eth65.tx_size & 0xFF;
383 case 0x03:
return (eth65.tx_size >> 8) & 0xFF;
388 eth65.disable_crc_chk |
389 ( eth65.adjust_txd_phase << 2) |
390 eth65.accept_broadcast |
391 eth65.accept_multicast
397 eth65.miim_register |
398 (eth65.phy_number << 5)
401 case 0x07:
return miimlo8[eth65.miim_register];
403 case 0x08:
return miimhi8[eth65.miim_register];
405 case 0x09:
return mac_address[0];
406 case 0x0A:
return mac_address[1];
407 case 0x0B:
return mac_address[2];
408 case 0x0C:
return mac_address[3];
409 case 0x0D:
return mac_address[4];
410 case 0x0E:
return mac_address[5];
421 DEBUG(
"ETH: writing register $%02X with data $%02X" NL,
addr & 0xF,
data);
422 switch (
addr & 0xF) {
425 eth65.no_reset = (
data & 0x01);
432 eth65.no_reset = (
data & 0x01);
435 eth65.rx_buffer_mapped = (
data & 0x02) ? 0x800 : 0;
438 eth65.video_streaming = (
data & 0x08);
442 eth65.tx_irq_enabled = (
data & 0x40);
444 eth65.rx_irq_enabled = (
data & 0x80);
452 eth65.tx_size = (eth65.tx_size & 0xFF00) |
data;
456 eth65.tx_size = ((
data & 0x0F) << 8) | (eth65.tx_size & 0xFF);
463 eth65.tx_trigger = 0;
464 }
else if (
data == 1) {
466 eth65.sense_activity = 1;
468 eth65.tx_trigger = 1;
471 DEBUGPRINT(
"ETH: warning, invalid sized (%d) frame tried to be TX'ed" NL, eth65.tx_size);
477 eth65.mac_filter = (
data & 0x01);
478 eth65.disable_crc_chk = (
data & 0x02);
479 eth65.adjust_txd_phase = (
data >> 2) & 3;
480 eth65.accept_broadcast = (
data & 0x10);
481 eth65.accept_multicast = (
data & 0x20);
485 eth65.miim_register = (
data & 0x1F);
486 eth65.phy_number = (
data & 0xE0) >> 5;
490 miimlo8[eth65.miim_register] =
data;
494 miimhi8[eth65.miim_register] =
data;
497 case 0x09: mac_address[0] =
data;
break;
498 case 0x0A: mac_address[1] =
data;
break;
499 case 0x0B: mac_address[2] =
data;
break;
500 case 0x0C: mac_address[3] =
data;
break;
501 case 0x0D: mac_address[4] =
data;
break;
502 case 0x0E: mac_address[5] =
data;
break;
511 return eth65.rx_buffer[eth65.rx_buffer_mapped + (offset & 0x7FF)];
516 eth65.tx_buffer[offset & 0x7FF] =
data;
524 DEBUGPRINT(
"ETH: shutting down: handler thread has already exited" NL);
525 else if (eth65.enabled) {
528 eth65.rx_enabled = 0;
529 eth65.tx_trigger = 0;
530 eth65.sense_activity = 1;
531 eth65.select_wait_usec = 0;
532 DEBUGPRINT(
"ETH: shutting down: handler thread seems to be running" NL);
534 DEBUGPRINT(
"ETH: shutting down: handler thread seems hasn't been even running (not enabled?)" NL);
544 memset(&miimlo8, 0,
sizeof miimlo8);
545 memset(&miimhi8, 0,
sizeof miimhi8);
546 memset((
void*)ð65, 0,
sizeof eth65);
549 memset((
void*)eth65.rx_buffer, 0xFF,
sizeof eth65.rx_buffer);
550 memset((
void*)eth65.tx_buffer, 0xFF,
sizeof eth65.tx_buffer);
552 eth65.rx_buffer_using = 0x800;
553 eth65.rx_buffer_mapped = 0x000;
554 eth65.accept_broadcast = 16;
555 eth65.accept_multicast = 32;
556 eth65.mac_filter = 1;
564 eth65._irq_to_cpu_routed = 1;
569 char device_name[64];
574 if (!len || len >=
sizeof device_name) {
575 ERROR_WINDOW(
"%sinvalid CLI switch/config: empty or too long parameter", init_error_prefix);
579 strncpy(device_name,
options, len);
580 device_name[len] = 0;
581 }
else if (len == 5 && !strncmp(
options,
"debug", len)) {
583 }
else if (len == 13 && !strncmp(
options,
"alwaysfilters", len)) {
584 eth65.xemu_always_filters = 1;
585 }
else if (!strncmp(
options,
"mac=", 4)) {
586 if (len != 16 || sscanf(
588 "%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX",
589 mac_address + 0, mac_address + 1, mac_address + 2,
590 mac_address + 3, mac_address + 4, mac_address + 5
592 ERROR_WINDOW(
"%sbad mac= option, invalid MAC", init_error_prefix);
596 strncpy(device_name,
options, len);
597 device_name[len] = 0;
598 ERROR_WINDOW(
"%sunknown/bad sub-option given in CLI/config: %s", init_error_prefix, device_name);
606 if (xemu_tuntap_alloc(device_name, NULL, 0, XEMU_TUNTAP_IS_TAP | XEMU_TUNTAP_NO_PI | XEMU_TUNTAP_NONBLOCKING_IO) < 0) {
607 ERROR_WINDOW(
"%sTAP device \"%s\" opening error: %s", init_error_prefix, device_name, strerror(errno));
612 thread_id = SDL_CreateThread(ethernet_thread,
"Xemu-EtherTAP", NULL);
614 DEBUGPRINT(
"ETH: enabled - thread %p started, device attached: \"%s\", initial MAC: %02X:%02X:%02X:%02X:%02X:%02X" NL,
615 thread_id, device_name,
616 mac_address[0], mac_address[1], mac_address[2],
617 mac_address[3], mac_address[4], mac_address[5]
620 ERROR_WINDOW(
"%serror creating thread for Ethernet emulation: %s", init_error_prefix, SDL_GetError());
627 ERROR_WINDOW(
"Ethernet emulation is not supported/was compiled into this Xemu");
632 DEBUGPRINT(
"ETH: not enabled by config/command line" NL);
634 DEBUG(
"ETH: Ethernet emulation is not supported/was compiled into this Xemu");