diff --git a/.ci/build.sh b/.ci/build.sh index 38cd36c0c..f0d91fe18 100644 --- a/.ci/build.sh +++ b/.ci/build.sh @@ -1158,10 +1158,14 @@ EOF # Run appimage-builder in extract-and-run mode for Docker compatibility. # --appdir is a workaround for https://github.com/AppImageCrafters/appimage-builder/issues/270 - project="$project" project_id="$project_id" project_version="$project_version" project_icon="$project_icon" arch_deb="$arch_deb" \ - arch_appimage="$arch_appimage" appimage_path="$cwd/$package_name.AppImage" APPIMAGE_EXTRACT_AND_RUN=1 ./appimage-builder.AppImage \ - --recipe AppImageBuilder-generated.yml --appdir "$(grep -oP '^\s+path: \K(.+)' AppImageBuilder-generated.yml)" - status=$? + for retry in 1 2 3 4 5 + do + project="$project" project_id="$project_id" project_version="$project_version" project_icon="$project_icon" arch_deb="$arch_deb" \ + arch_appimage="$arch_appimage" appimage_path="$cwd/$package_name.AppImage" APPIMAGE_EXTRACT_AND_RUN=1 ./appimage-builder.AppImage \ + --recipe AppImageBuilder-generated.yml --appdir "$(grep -oP '^\s+path: \K(.+)' AppImageBuilder-generated.yml)" + status=$? + [ $status -eq 0 ] && break + done # Remove appimage-builder binary on failure, just in case it's corrupted. [ $status -ne 0 ] && rm -f "$appimage_builder_binary" diff --git a/src/cdrom/cdrom_image_viso.c b/src/cdrom/cdrom_image_viso.c index f420bf2fb..d4fc2b2a9 100644 --- a/src/cdrom/cdrom_image_viso.c +++ b/src/cdrom/cdrom_image_viso.c @@ -89,9 +89,9 @@ enum { }; enum { - VISO_FORMAT_HSF = 0, /* High Sierra */ - VISO_FORMAT_ISO, /* ISO 9660 */ - VISO_FORMAT_ISO_LFN /* ISO 9660 with Joliet and Rock Ridge */ + VISO_FORMAT_ISO = 1, /* ISO 9660 (High Sierra if not set) */ + VISO_FORMAT_JOLIET = 2, /* Joliet extensions (Microsoft) */ + VISO_FORMAT_RR = 4 /* Rock Ridge extensions (*nix) */ }; typedef struct _viso_entry_ { @@ -115,7 +115,7 @@ typedef struct _viso_entry_ { typedef struct { uint64_t vol_size_offsets[2], pt_meta_offsets[2]; - int format; + int format, use_version_suffix : 1; size_t metadata_sectors, all_sectors, entry_map_size, sector_size, file_fifo_pos; uint8_t *metadata; @@ -300,7 +300,7 @@ static int viso_fill_fn_short(char *data, const viso_entry_t *entry, viso_entry_t **entries) { /* Get name and extension length. */ - const char *ext_pos = S_ISDIR(entry->stats.st_mode) ? NULL : strrchr(entry->basename, '.'); + const char *ext_pos = strrchr(entry->basename, '.'); int name_len, ext_len; if (ext_pos) { name_len = ext_pos - entry->basename; @@ -444,7 +444,7 @@ viso_fill_time(uint8_t *data, time_t time, int format, int longform) time_s->tm_mday = 1; } else if (time_s->tm_year > (longform ? 8099 : 255)) { time_s->tm_year = longform ? 8099 : 255; - time_s->tm_mon = 12; + time_s->tm_mon = 11; time_s->tm_mday = 31; time_s->tm_hour = 23; time_s->tm_min = time_s->tm_sec = 59; @@ -463,14 +463,14 @@ viso_fill_time(uint8_t *data, time_t time, int format, int longform) *p++ = time_s->tm_min; /* minute */ *p++ = time_s->tm_sec; /* second */ } - if (format >= VISO_FORMAT_ISO) + if (format & VISO_FORMAT_ISO) *p++ = tz_offset; /* timezone (ISO only) */ return p - data; } static int -viso_fill_dir_record(uint8_t *data, viso_entry_t *entry, int format, int type) +viso_fill_dir_record(uint8_t *data, viso_entry_t *entry, viso_t *viso, int type) { uint8_t *p = data, *q, *r; @@ -482,11 +482,11 @@ viso_fill_dir_record(uint8_t *data, viso_entry_t *entry, int format, int type) if (entry->stats.st_mtime < 0) pclog("VISO: Warning: Windows returned st_mtime %lld on file [%s]\n", (long long) entry->stats.st_mtime, entry->path); #endif - p += viso_fill_time(p, entry->stats.st_mtime, format, 0); /* time */ - *p++ = S_ISDIR(entry->stats.st_mode) ? 0x02 : 0x00; /* flags */ + p += viso_fill_time(p, entry->stats.st_mtime, viso->format, 0); /* time */ + *p++ = S_ISDIR(entry->stats.st_mode) ? 0x02 : 0x00; /* flags */ - VISO_SKIP(p, 2 + (format <= VISO_FORMAT_HSF)); /* file unit size (reserved on HSF), interleave gap size (HSF/ISO) and skip factor (HSF only) */ - VISO_LBE_16(p, 1); /* volume sequence number */ + VISO_SKIP(p, 2 + !(viso->format & VISO_FORMAT_ISO)); /* file unit size (reserved on HSF), interleave gap size (HSF/ISO) and skip factor (HSF only) */ + VISO_LBE_16(p, 1); /* volume sequence number */ switch (type) { case VISO_DIR_CURRENT: @@ -496,7 +496,7 @@ viso_fill_dir_record(uint8_t *data, viso_entry_t *entry, int format, int type) *p++ = (type == VISO_DIR_PARENT) ? 1 : 0; /* magic value corresponding to . or .. */ /* Fill Rock Ridge Extension Record for the root directory's . entry. */ - if ((type == VISO_DIR_CURRENT_ROOT) && (format >= VISO_FORMAT_ISO_LFN)) { + if ((type == VISO_DIR_CURRENT_ROOT) && (viso->format & VISO_FORMAT_RR)) { *p++ = 'E'; *p++ = 'R'; *p++ = 8 + (sizeof(rr_eid) - 1) + (sizeof(rr_edesc) - 1); /* length */ @@ -522,8 +522,8 @@ viso_fill_dir_record(uint8_t *data, viso_entry_t *entry, int format, int type) *q = strlen(entry->name_short); memcpy(p, entry->name_short, *q); /* file ID */ p += *q; - if ((format >= VISO_FORMAT_ISO) && !S_ISDIR(entry->stats.st_mode)) { - *p++ = ';'; /* version suffix for files (ISO only?) */ + if (viso->use_version_suffix && !S_ISDIR(entry->stats.st_mode)) { + *p++ = ';'; /* version suffix for files (ISO only, except for Windows NT SETUPLDR.BIN El Torito hack) */ *p++ = '1'; *q += 2; } @@ -532,7 +532,7 @@ viso_fill_dir_record(uint8_t *data, viso_entry_t *entry, int format, int type) *p++ = 0; /* Fill Rock Ridge data. */ - if (format >= VISO_FORMAT_ISO_LFN) { + if (viso->format & VISO_FORMAT_RR) { *p++ = 'R'; /* RR = present Rock Ridge entries (only documented by RRIP revision 1.09!) */ *p++ = 'R'; *p++ = 5; /* length */ @@ -588,14 +588,14 @@ viso_fill_dir_record(uint8_t *data, viso_entry_t *entry, int format, int type) *p++ = times; /* flags */ #ifdef st_birthtime if (times & (1 << 0)) - p += viso_fill_time(p, entry->stats.st_birthtime, format, 0); /* creation */ + p += viso_fill_time(p, entry->stats.st_birthtime, viso->format, 0); /* creation */ #endif if (times & (1 << 1)) - p += viso_fill_time(p, entry->stats.st_mtime, format, 0); /* modify */ + p += viso_fill_time(p, entry->stats.st_mtime, viso->format, 0); /* modify */ if (times & (1 << 2)) - p += viso_fill_time(p, entry->stats.st_atime, format, 0); /* access */ + p += viso_fill_time(p, entry->stats.st_atime, viso->format, 0); /* access */ if (times & (1 << 3)) - p += viso_fill_time(p, entry->stats.st_ctime, format, 0); /* attributes */ + p += viso_fill_time(p, entry->stats.st_ctime, viso->format, 0); /* attributes */ *r += p - r; /* add to length */ } @@ -764,8 +764,9 @@ viso_init(const char *dirname, int *error) *error = 1; if (viso == NULL) goto end; - viso->sector_size = VISO_SECTOR_SIZE; - viso->format = VISO_FORMAT_ISO_LFN; + viso->sector_size = VISO_SECTOR_SIZE; + viso->format = VISO_FORMAT_ISO | VISO_FORMAT_JOLIET | VISO_FORMAT_RR; + viso->use_version_suffix = (viso->format & VISO_FORMAT_ISO); /* cleared later if required */ /* Prepare temporary data buffers. */ data = calloc(2, viso->sector_size); @@ -797,7 +798,6 @@ viso_init(const char *dirname, int *error) if (!dir) goto end; strcpy(dir->path, dirname); - strcpy(dir->name_short, "[root]"); if (stat(dirname, &dir->stats) != 0) { /* Use a blank structure if stat failed. */ memset(&dir->stats, 0x00, sizeof(struct stat)); @@ -805,7 +805,7 @@ viso_init(const char *dirname, int *error) if (!S_ISDIR(dir->stats.st_mode)) /* root is not a directory */ goto end; dir->parent = dir; /* for the root's path table and .. entries */ - cdrom_image_viso_log("[%08X] %s => %s\n", dir, dir->path, dir->name_short); + cdrom_image_viso_log("[%08X] %s => [root]\n", dir, dir->path); /* Traverse directories, starting with the root. */ viso_entry_t **dir_entries = NULL; @@ -813,16 +813,16 @@ viso_init(const char *dirname, int *error) while (dir) { /* Open directory for listing. */ DIR *dirp = opendir(dir->path); - if (!dirp) - goto next_dir; /* Iterate through this directory's children to determine the entry array size. */ size_t children_count = 3; /* include terminator, . and .. */ - while ((readdir_entry = readdir(dirp))) { - /* Ignore . and .. pseudo-directories. */ - if ((readdir_entry->d_name[0] == '.') && ((readdir_entry->d_name[1] == '\0') || ((readdir_entry->d_name[1] == '.') && (readdir_entry->d_name[2] == '\0')))) - continue; - children_count++; + if (dirp) { /* create empty directory if opendir failed */ + while ((readdir_entry = readdir(dirp))) { + /* Ignore . and .. pseudo-directories. */ + if ((readdir_entry->d_name[0] == '.') && ((readdir_entry->d_name[1] == '\0') || (*((uint16_t *) &readdir_entry->d_name[1]) == '.'))) + continue; + children_count++; + } } /* Grow array if needed. */ @@ -859,77 +859,89 @@ viso_init(const char *dirname, int *error) } /* Iterate through this directory's children again, making the entries. */ - rewinddir(dirp); - while ((readdir_entry = readdir(dirp))) { - /* Ignore . and .. pseudo-directories. */ - if ((readdir_entry->d_name[0] == '.') && ((readdir_entry->d_name[1] == '\0') || ((readdir_entry->d_name[1] == '.') && (readdir_entry->d_name[2] == '\0')))) - continue; + if (dirp) { + rewinddir(dirp); + while ((readdir_entry = readdir(dirp))) { + /* Ignore . and .. pseudo-directories. */ + if ((readdir_entry->d_name[0] == '.') && ((readdir_entry->d_name[1] == '\0') || (*((uint16_t *) &readdir_entry->d_name[1]) == '.'))) + continue; - /* Add and fill entry. */ - entry = dir_entries[children_count++] = (viso_entry_t *) calloc(1, sizeof(viso_entry_t) + dir_path_len + strlen(readdir_entry->d_name) + 2); - if (!entry) - break; - entry->parent = dir; - strcpy(entry->path, dir->path); - path_slash(&entry->path[dir_path_len]); - entry->basename = &entry->path[dir_path_len + 1]; - strcpy(entry->basename, readdir_entry->d_name); + /* Add and fill entry. */ + entry = dir_entries[children_count++] = (viso_entry_t *) calloc(1, sizeof(viso_entry_t) + dir_path_len + strlen(readdir_entry->d_name) + 2); + if (!entry) + break; + entry->parent = dir; + strcpy(entry->path, dir->path); + path_slash(&entry->path[dir_path_len]); + entry->basename = &entry->path[dir_path_len + 1]; + strcpy(entry->basename, readdir_entry->d_name); - /* Stat this child. */ - if (stat(entry->path, &entry->stats) != 0) { - /* Use a blank structure if stat failed. */ - memset(&entry->stats, 0x00, sizeof(struct stat)); - } - - /* Handle file size and El Torito boot code. */ - if (!S_ISDIR(entry->stats.st_mode)) { - /* Clamp to 4 GB - 1 byte. */ - if (entry->stats.st_size > ((uint32_t) -1)) - entry->stats.st_size = (uint32_t) -1; - - /* Increase entry map size. */ - viso->entry_map_size += entry->stats.st_size / viso->sector_size; - if (entry->stats.st_size % viso->sector_size) - viso->entry_map_size++; /* round up to the next sector */ - - /* Detect El Torito boot code file and set it accordingly. */ - if (dir == eltorito_dir) { - if (!stricmp(readdir_entry->d_name, "Boot-NoEmul.img")) { - eltorito_type = 0x00; -have_eltorito_entry: - if (eltorito_entry) - eltorito_others_present = 1; /* flag that the boot code directory contains other files */ - eltorito_entry = entry; - } else if (!stricmp(readdir_entry->d_name, "Boot-1.2M.img")) { - eltorito_type = 0x01; - goto have_eltorito_entry; - } else if (!stricmp(readdir_entry->d_name, "Boot-1.44M.img")) { - eltorito_type = 0x02; - goto have_eltorito_entry; - } else if (!stricmp(readdir_entry->d_name, "Boot-2.88M.img")) { - eltorito_type = 0x03; - goto have_eltorito_entry; - } else if (!stricmp(readdir_entry->d_name, "Boot-HardDisk.img")) { - eltorito_type = 0x04; - goto have_eltorito_entry; - } else { - eltorito_others_present = 1; /* flag that the boot code directory contains other files */ - } + /* Stat this child. */ + if (stat(entry->path, &entry->stats) != 0) { + /* Use a blank structure if stat failed. */ + memset(&entry->stats, 0x00, sizeof(struct stat)); } - } else if ((dir == viso->root_dir) && !stricmp(readdir_entry->d_name, "[BOOT]")) { - /* Set this as the directory containing El Torito boot code. */ - eltorito_dir = entry; - eltorito_others_present = 0; - } - /* Set short filename. */ - if (viso_fill_fn_short(entry->name_short, entry, dir_entries)) { - free(entry); - children_count--; - continue; - } + /* Handle file size and El Torito boot code. */ + if (!S_ISDIR(entry->stats.st_mode)) { + /* Clamp file size to 4 GB - 1 byte. */ + if (entry->stats.st_size > ((uint32_t) -1)) + entry->stats.st_size = (uint32_t) -1; - cdrom_image_viso_log("[%08X] %s => [%-12s] %s\n", entry, dir->path, entry->name_short, entry->basename); + /* Increase entry map size. */ + viso->entry_map_size += entry->stats.st_size / viso->sector_size; + if (entry->stats.st_size % viso->sector_size) + viso->entry_map_size++; /* round up to the next sector */ + + /* Detect El Torito boot code file and set it accordingly. */ + if (dir == eltorito_dir) { + if (!stricmp(readdir_entry->d_name, "Boot-NoEmul.img")) { + eltorito_type = 0x00; +have_eltorito_entry: + if (eltorito_entry) + eltorito_others_present = 1; /* flag that the boot code directory contains other files */ + eltorito_entry = entry; + } else if (!stricmp(readdir_entry->d_name, "Boot-1.2M.img")) { + eltorito_type = 0x01; + goto have_eltorito_entry; + } else if (!stricmp(readdir_entry->d_name, "Boot-1.44M.img")) { + eltorito_type = 0x02; + goto have_eltorito_entry; + } else if (!stricmp(readdir_entry->d_name, "Boot-2.88M.img")) { + eltorito_type = 0x03; + goto have_eltorito_entry; + } else if (!stricmp(readdir_entry->d_name, "Boot-HardDisk.img")) { + eltorito_type = 0x04; + goto have_eltorito_entry; + } else { + eltorito_others_present = 1; /* flag that the boot code directory contains other files */ + } + } else { + /* Disable version suffixes if this structure appears to contain the Windows NT + El Torito boot code, which is known not to tolerate suffixed file names. */ + if (eltorito_dir && /* El Torito directory present? */ + (eltorito_type == 0x00) && /* El Torito directory not checked yet, or confirmed to contain non-emulation boot code? */ + (dir->parent == viso->root_dir) && /* one subdirectory deep? (I386 for instance) */ + !stricmp(readdir_entry->d_name, "SETUPLDR.BIN")) /* SETUPLDR.BIN present? */ + viso->use_version_suffix = 0; + } + } else if ((dir == viso->root_dir) && !stricmp(readdir_entry->d_name, "[BOOT]")) { + /* Set this as the directory containing El Torito boot code. */ + eltorito_dir = entry; + eltorito_others_present = 0; + } + + /* Set short filename. */ + if (viso_fill_fn_short(entry->name_short, entry, dir_entries)) { + free(entry); + children_count--; + continue; + } + + cdrom_image_viso_log("[%08X] %s => [%-12s] %s\n", entry, dir->path, entry->name_short, entry->basename); + } + } else { + cdrom_image_viso_log("VISO: Failed to enumerate [%s], will be empty\n", dir->path); } /* Add terminator. */ @@ -951,6 +963,8 @@ have_eltorito_entry: next_dir: /* Move on to the next directory. */ + if (dirp) + closedir(dirp); dir = dir->next_dir; } if (dir_entries) @@ -964,21 +978,26 @@ next_dir: the timezone offset for descriptors and file times to use. */ tzset(); time_t now = time(NULL); - tz_offset = (now - mktime(gmtime(&now))) / (3600 / 4); + if (viso->format & VISO_FORMAT_ISO) /* timezones are ISO only */ + tz_offset = (now - mktime(gmtime(&now))) / (3600 / 4); /* Get root directory basename for the volume ID. */ char *basename = path_get_filename(viso->root_dir->path); if (!basename || (basename[0] == '\0')) basename = EMU_NAME; + /* Determine whether or not we're working with 2 volume descriptors + (as well as 2 directory trees and 4 path tables) for Joliet. */ + int max_vd = (viso->format & VISO_FORMAT_JOLIET) ? 1 : 0; + /* Write volume descriptors. */ - for (int i = 0; i <= (viso->format >= VISO_FORMAT_ISO_LFN); i++) { + for (int i = 0; i <= max_vd; i++) { /* Fill volume descriptor. */ p = data; - if (viso->format <= VISO_FORMAT_HSF) - VISO_LBE_32(p, ftello64(viso->tf.file) / viso->sector_size); /* sector offset (HSF only) */ - *p++ = 1 + i; /* type */ - memcpy(p, (viso->format <= VISO_FORMAT_HSF) ? "CDROM" : "CD001", 5); /* standard ID */ + if (!(viso->format & VISO_FORMAT_ISO)) + VISO_LBE_32(p, ftello64(viso->tf.file) / viso->sector_size); /* sector offset (HSF only) */ + *p++ = 1 + i; /* type */ + memcpy(p, (viso->format & VISO_FORMAT_ISO) ? "CD001" : "CDROM", 5); /* standard ID */ p += 5; *p++ = 1; /* version */ *p++ = 0; /* unused */ @@ -1017,11 +1036,12 @@ next_dir: /* Path table metadata is filled in later. */ viso->pt_meta_offsets[i] = ftello64(viso->tf.file) + (p - data); - VISO_SKIP(p, 24 + (16 * (viso->format <= VISO_FORMAT_HSF))); /* PT size, LE PT offset, optional LE PT offset (three on HSF), BE PT offset, optional BE PT offset (three on HSF) */ + VISO_SKIP(p, 24 + (16 * !(viso->format & VISO_FORMAT_ISO))); /* PT size, LE PT offset, optional LE PT offset (three on HSF), BE PT offset, optional BE PT offset (three on HSF) */ viso->root_dir->dr_offsets[i] = ftello64(viso->tf.file) + (p - data); - p += viso_fill_dir_record(p, viso->root_dir, viso->format, VISO_DIR_CURRENT); /* root directory */ + p += viso_fill_dir_record(p, viso->root_dir, viso, VISO_DIR_CURRENT); /* root directory */ + int copyright_abstract_len = (viso->format & VISO_FORMAT_ISO) ? 37 : 32; if (i) { viso_write_wstring((uint16_t *) p, L"", 64, VISO_CHARSET_D); /* volume set ID */ p += 128; @@ -1031,11 +1051,11 @@ next_dir: p += 128; viso_write_wstring((uint16_t *) p, EMU_NAME_W L" " EMU_VERSION_W L" VIRTUAL ISO", 64, VISO_CHARSET_A); /* application ID */ p += 128; - viso_write_wstring((uint16_t *) p, L"", (viso->format <= VISO_FORMAT_HSF) ? 16 : 18, VISO_CHARSET_D); /* copyright file ID */ - p += (viso->format <= VISO_FORMAT_HSF) ? 32 : 37; - viso_write_wstring((uint16_t *) p, L"", (viso->format <= VISO_FORMAT_HSF) ? 16 : 18, VISO_CHARSET_D); /* abstract file ID */ - p += (viso->format <= VISO_FORMAT_HSF) ? 32 : 37; - if (viso->format >= VISO_FORMAT_ISO) { + viso_write_wstring((uint16_t *) p, L"", copyright_abstract_len >> 1, VISO_CHARSET_D); /* copyright file ID */ + p += copyright_abstract_len; + viso_write_wstring((uint16_t *) p, L"", copyright_abstract_len >> 1, VISO_CHARSET_D); /* abstract file ID */ + p += copyright_abstract_len; + if (viso->format & VISO_FORMAT_ISO) { viso_write_wstring((uint16_t *) p, L"", 18, VISO_CHARSET_D); /* bibliography file ID (ISO only) */ p += 37; } @@ -1048,11 +1068,11 @@ next_dir: p += 128; viso_write_string(p, EMU_NAME " " EMU_VERSION " VIRTUAL ISO", 128, VISO_CHARSET_A); /* application ID */ p += 128; - viso_write_string(p, "", (viso->format <= VISO_FORMAT_HSF) ? 32 : 37, VISO_CHARSET_D); /* copyright file ID */ - p += (viso->format <= VISO_FORMAT_HSF) ? 32 : 37; - viso_write_string(p, "", (viso->format <= VISO_FORMAT_HSF) ? 32 : 37, VISO_CHARSET_D); /* abstract file ID */ - p += (viso->format <= VISO_FORMAT_HSF) ? 32 : 37; - if (viso->format >= VISO_FORMAT_ISO) { + viso_write_string(p, "", copyright_abstract_len, VISO_CHARSET_D); /* copyright file ID */ + p += copyright_abstract_len; + viso_write_string(p, "", copyright_abstract_len, VISO_CHARSET_D); /* abstract file ID */ + p += copyright_abstract_len; + if (viso->format & VISO_FORMAT_ISO) { viso_write_string(p, "", 37, VISO_CHARSET_D); /* bibliography file ID (ISO only) */ p += 37; } @@ -1078,10 +1098,10 @@ next_dir: cdrom_image_viso_log("VISO: Writing El Torito boot descriptor for entry [%08X]\n", eltorito_entry); p = data; - if (viso->format <= VISO_FORMAT_HSF) - VISO_LBE_32(p, ftello64(viso->tf.file) / viso->sector_size); /* sector offset (HSF only) */ - *p++ = 0; /* type */ - memcpy(p, (viso->format <= VISO_FORMAT_HSF) ? "CDROM" : "CD001", 5); /* standard ID */ + if (!(viso->format & VISO_FORMAT_ISO)) + VISO_LBE_32(p, ftello64(viso->tf.file) / viso->sector_size); /* sector offset (HSF only) */ + *p++ = 0; /* type */ + memcpy(p, (viso->format & VISO_FORMAT_ISO) ? "CD001" : "CDROM", 5); /* standard ID */ p += 5; *p++ = 1; /* version */ @@ -1102,10 +1122,10 @@ next_dir: /* Fill terminator. */ p = data; - if (viso->format <= VISO_FORMAT_HSF) - VISO_LBE_32(p, ftello64(viso->tf.file) / viso->sector_size); /* sector offset (HSF only) */ - *p++ = 0xff; /* type */ - memcpy(p, (viso->format <= VISO_FORMAT_HSF) ? "CDROM" : "CD001", 5); /* standard ID */ + if (!(viso->format & VISO_FORMAT_ISO)) + VISO_LBE_32(p, ftello64(viso->tf.file) / viso->sector_size); /* sector offset (HSF only) */ + *p++ = 0xff; /* type */ + memcpy(p, (viso->format & VISO_FORMAT_ISO) ? "CD001" : "CDROM", 5); /* standard ID */ p += 5; *p++ = 1; /* version */ @@ -1181,7 +1201,7 @@ next_dir: } /* Write each path table. */ - for (int i = 0; i <= ((viso->format >= VISO_FORMAT_ISO_LFN) ? 3 : 1); i++) { + for (int i = 0; i <= ((max_vd << 1) | 1); i++) { cdrom_image_viso_log("VISO: Generating path table #%d:\n", i); /* Save this path table's start offset. */ @@ -1211,7 +1231,7 @@ next_dir: /* Fill path table entry. */ p = data; - if (viso->format <= VISO_FORMAT_HSF) { + if (!(viso->format & VISO_FORMAT_ISO)) { *((uint32_t *) p) = 0; /* extent location (filled in later) */ p += 4; *p++ = 0; /* extended attribute length */ @@ -1227,16 +1247,17 @@ next_dir: *((uint16_t *) p) = (i & 1) ? cpu_to_be16(dir->parent->pt_idx) : cpu_to_le16(dir->parent->pt_idx); /* parent directory number */ p += 2; - if (dir == viso->root_dir) { /* directory ID and length */ - data[5 * (viso->format <= VISO_FORMAT_HSF)] = 1; - *p = 0x00; - } else if (i & 2) { - data[5 * (viso->format <= VISO_FORMAT_HSF)] = viso_fill_fn_joliet(p, dir, 255); - } else { - data[5 * (viso->format <= VISO_FORMAT_HSF)] = strlen(dir->name_short); - memcpy(p, dir->name_short, data[5 * (viso->format <= VISO_FORMAT_HSF)]); + pt_temp = 5 * !(viso->format & VISO_FORMAT_ISO); /* directory ID length at offset 0 for ISO, 5 for HSF */ + if (dir == viso->root_dir) { /* directory ID length then ID for root... */ + data[pt_temp] = 1; + *p = 0x00; + } else if (i & 2) { /* ...or Joliet... */ + data[pt_temp] = viso_fill_fn_joliet(p, dir, 255); + } else { /* ...or short name */ + data[pt_temp] = strlen(dir->name_short); + memcpy(p, dir->name_short, data[pt_temp]); } - p += data[5 * (viso->format <= VISO_FORMAT_HSF)]; + p += data[pt_temp]; if ((p - data) & 1) /* padding for odd directory ID lengths */ *p++ = 0x00; @@ -1269,7 +1290,7 @@ next_dir: /* Write directory records for each type. */ int dir_type = VISO_DIR_CURRENT_ROOT; - for (int i = 0; i <= (viso->format >= VISO_FORMAT_ISO_LFN); i++) { + for (int i = 0; i <= max_vd; i++) { cdrom_image_viso_log("VISO: Generating directory record set #%d:\n", i); /* Go through directories. */ @@ -1302,7 +1323,7 @@ next_dir: viso_pwrite(data, dir->pt_offsets[i << 1], 4, 1, viso->tf.file); /* little endian */ viso_pwrite(data + 4, dir->pt_offsets[(i << 1) | 1], 4, 1, viso->tf.file); /* big endian */ - if (i == (viso->format >= VISO_FORMAT_ISO_LFN)) /* overwrite pt_offsets in the union if we no longer need them */ + if (i == max_vd) /* overwrite pt_offsets in the union if we no longer need them */ dir->file = NULL; /* Go through entries in this directory. */ @@ -1317,7 +1338,7 @@ next_dir: ((dir_type == VISO_DIR_PARENT) ? ".." : ((dir_type < VISO_DIR_PARENT) ? "." : (i ? entry->basename : entry->name_short)))); /* Fill directory record. */ - viso_fill_dir_record(data, entry, viso->format, dir_type); + viso_fill_dir_record(data, entry, viso, dir_type); /* Entries cannot cross sector boundaries, so pad to the next sector if needed. */ write = viso->sector_size - (ftello64(viso->tf.file) % viso->sector_size); @@ -1456,7 +1477,7 @@ next_entry: } else { p = data; VISO_LBE_32(p, viso->all_sectors * base_factor); - for (int i = 0; i <= (viso->format >= VISO_FORMAT_ISO_LFN); i++) + for (int i = 0; i <= max_vd; i++) viso_pwrite(data, entry->dr_offsets[i] + 2, 8, 1, viso->tf.file); } diff --git a/src/include/86box/machine.h b/src/include/86box/machine.h index ec93ecbef..77678a4dd 100644 --- a/src/include/86box/machine.h +++ b/src/include/86box/machine.h @@ -340,6 +340,7 @@ extern int machine_get_ram_granularity(int m); extern int machine_get_type(int m); extern void machine_close(void); extern int machine_has_mouse(void); +extern int machine_is_sony(void); extern uint8_t machine_get_p1(void); extern void machine_load_p1(int m); diff --git a/src/include/86box/net_3c501.h b/src/include/86box/net_3c501.h new file mode 100644 index 000000000..e45f9a82a --- /dev/null +++ b/src/include/86box/net_3c501.h @@ -0,0 +1,44 @@ +/* + * 86Box An emulator of (mostly) x86-based PC systems and devices, + * using the ISA, EISA, VLB, MCA, and PCI system buses, + * roughly spanning the era between 1981 and 1995. + * + * This file is part of the 86Box Project. + * + * Implementation of the following network controller: + * - 3Com Etherlink 3c500/3c501 (ISA 8-bit). + * + * + * + * Based on @(#)Dev3C501.cpp Oracle (VirtualBox) + * + * Authors: TheCollector1995, + * Oracle + * + * Copyright 2022 TheCollector1995. + * Portions Copyright (C) 2022 Oracle and/or its affilitates. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the: + * + * Free Software Foundation, Inc. + * 59 Temple Place - Suite 330 + * Boston, MA 02111-1307 + * USA. + */ +#ifndef NET_3C501_H +#define NET_3C501_H + +extern const device_t threec501_device; + +#endif /*NET_3C501_H*/ diff --git a/src/include/86box/snd_ad1848.h b/src/include/86box/snd_ad1848.h index f9f73a60c..bdf5022e5 100644 --- a/src/include/86box/snd_ad1848.h +++ b/src/include/86box/snd_ad1848.h @@ -66,6 +66,7 @@ extern void ad1848_write(uint16_t addr, uint8_t val, void *priv); extern void ad1848_update(ad1848_t *ad1848); extern void ad1848_speed_changed(ad1848_t *ad1848); extern void ad1848_filter_cd_audio(int channel, double *buffer, void *priv); +extern void ad1848_filter_aux2(void* priv, double* out_l, double* out_r); extern void ad1848_init(ad1848_t *ad1848, uint8_t type); diff --git a/src/include/86box/snd_sb.h b/src/include/86box/snd_sb.h index 782a947f6..577335976 100644 --- a/src/include/86box/snd_sb.h +++ b/src/include/86box/snd_sb.h @@ -147,6 +147,9 @@ typedef struct sb_t { pnp_rom[512]; uint16_t opl_pnp_addr; + + void *opl_mixer; + void (*opl_mix)(void*, double*, double*); } sb_t; extern void sb_ct1345_mixer_write(uint16_t addr, uint8_t val, void *p); diff --git a/src/machine/m_xt_olivetti.c b/src/machine/m_xt_olivetti.c index d92df9f10..c978e2caf 100644 --- a/src/machine/m_xt_olivetti.c +++ b/src/machine/m_xt_olivetti.c @@ -1385,10 +1385,12 @@ m24_kbd_init(m24_kbd_t *kbd) m24_kbd_reset(kbd); timer_add(&kbd->send_delay_timer, m24_kbd_poll, kbd, 1); - /* Tell mouse driver about our internal mouse. */ - mouse_reset(); - mouse_set_buttons(2); - mouse_set_poll(ms_poll, kbd); + if (mouse_type == MOUSE_TYPE_INTERNAL) { + /* Tell mouse driver about our internal mouse. */ + mouse_reset(); + mouse_set_buttons(2); + mouse_set_poll(ms_poll, kbd); + } keyboard_set_table((kbd->id == 0x01) ? scancode_olivetti_m24_deluxe : scancode_olivetti_m240); keyboard_set_is_amstrad(0); diff --git a/src/machine/machine_table.c b/src/machine/machine_table.c index a37f1cae0..50666841f 100644 --- a/src/machine/machine_table.c +++ b/src/machine/machine_table.c @@ -12211,3 +12211,9 @@ machine_has_mouse(void) { return (machines[machine].flags & MACHINE_MOUSE); } + +int +machine_is_sony(void) +{ + return (!strcmp(machines[machine].internal_name, "pcv90")); +} diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index 16708d6c4..98252d49e 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -13,8 +13,8 @@ # Copyright 2020,2021 David Hrdlička. # -add_library(net OBJECT network.c net_pcap.c net_slirp.c net_dp8390.c net_3c503.c - net_ne2000.c net_pcnet.c net_wd8003.c net_plip.c net_event.c) +add_library(net OBJECT network.c net_pcap.c net_slirp.c net_dp8390.c net_3c501.c + net_3c503.c net_ne2000.c net_pcnet.c net_wd8003.c net_plip.c net_event.c) option(SLIRP_EXTERNAL "Link against the system-provided libslirp library" OFF) mark_as_advanced(SLIRP_EXTERNAL) diff --git a/src/network/net_3c501.c b/src/network/net_3c501.c new file mode 100644 index 000000000..19ad587c2 --- /dev/null +++ b/src/network/net_3c501.c @@ -0,0 +1,1223 @@ +/* + * 86Box An emulator of (mostly) x86-based PC systems and devices, + * using the ISA, EISA, VLB, MCA, and PCI system buses, + * roughly spanning the era between 1981 and 1995. + * + * This file is part of the 86Box Project. + * + * Implementation of the following network controller: + * - 3Com Etherlink 3c500/3c501 (ISA 8-bit). + * + * + * + * Based on @(#)Dev3C501.cpp Oracle (VirtualBox) + * + * Authors: TheCollector1995, + * Oracle + * + * Copyright 2022 TheCollector1995. + * Portions Copyright (C) 2022 Oracle and/or its affilitates. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the: + * + * Free Software Foundation, Inc. + * 59 Temple Place - Suite 330 + * Boston, MA 02111-1307 + * USA. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#define HAVE_STDARG_H +#include <86box/86box.h> +#include <86box/io.h> +#include <86box/dma.h> +#include <86box/pic.h> +#include <86box/mem.h> +#include <86box/random.h> +#include <86box/device.h> +#include <86box/thread.h> +#include <86box/timer.h> +#include <86box/network.h> +#include <86box/net_3c501.h> +#include <86box/bswap.h> + +/* Maximum number of times we report a link down to the guest (failure to send frame) */ +#define ELNK_MAX_LINKDOWN_REPORTED 3 + +/* Maximum number of times we postpone restoring a link that is temporarily down. */ +#define ELNK_MAX_LINKRST_POSTPONED 3 + +/* Maximum frame size we handle */ +#define MAX_FRAME 1536 + +/* Size of the packet buffer. */ +#define ELNK_BUF_SIZE 2048 + +/* The packet buffer address mask. */ +#define ELNK_BUF_ADR_MASK (ELNK_BUF_SIZE - 1) + +/* The GP buffer pointer address within the buffer. */ +#define ELNK_GP(dev) (dev->uGPBufPtr & ELNK_BUF_ADR_MASK) + +/* The GP buffer pointer mask. + * NB: The GP buffer pointer is internally a 12-bit counter. When addressing into the + * packet buffer, bit 11 is ignored. Required to pass 3C501 diagnostics. + */ +#define ELNK_GP_MASK 0xfff + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + + +/** + * EtherLink Transmit Command Register. + */ +typedef struct ELNK_XMIT_CMD { + uint8_t det_ufl : 1; /* Detect underflow. */ + uint8_t det_coll : 1; /* Detect collision. */ + uint8_t det_16col : 1; /* Detect collision 16. */ + uint8_t det_succ : 1; /* Detect successful xmit. */ + uint8_t unused : 4; +} EL_XMT_CMD; + +/** + * EtherLink Transmit Status Register. + * + * We will never see any real collisions, although collisions (including 16 + * successive collisions) may be useful to report when the link is down + * (something the 3C501 does not have a concept of). + */ +typedef struct ELNK_XMIT_STAT { + uint8_t uflow : 1; /* Underflow on transmit. */ + uint8_t coll : 1; /* Collision on transmit. */ + uint8_t coll16 : 1; /* 16 collisions on transmit. */ + uint8_t ready : 1; /* Ready for a new frame. */ + uint8_t undef : 4; +} EL_XMT_STAT; + +/** Address match (adr_match) modes. */ +typedef enum { + EL_ADRM_DISABLED = 0, /* Receiver disabled. */ + EL_ADRM_PROMISC = 1, /* Receive all addresses. */ + EL_ADRM_BCAST = 2, /* Receive station + broadcast. */ + EL_ADRM_MCAST = 3 /* Receive station + multicast. */ +} EL_ADDR_MATCH; + +/** + * EtherLink Receive Command Register. + */ +typedef struct ELNK_RECV_CMD { + uint8_t det_ofl : 1; /* Detect overflow errors. */ + uint8_t det_fcs : 1; /* Detect FCS errors. */ + uint8_t det_drbl : 1; /* Detect dribble error. */ + uint8_t det_runt : 1; /* Detect short frames. */ + uint8_t det_eof : 1; /* Detect EOF (frames without overflow). */ + uint8_t acpt_good : 1; /* Accept good frames. */ + uint8_t adr_match : 2; /* Address match mode. */ +} EL_RCV_CMD; + +/** + * EtherLink Receive Status Register. + */ +typedef struct ELNK_RECV_STAT { + uint8_t oflow : 1; /* Overflow on receive. */ + uint8_t fcs : 1; /* FCS error. */ + uint8_t dribble : 1; /* Dribble error. */ + uint8_t runt : 1; /* Short frame. */ + uint8_t no_ovf : 1; /* Received packet w/o overflow. */ + uint8_t good : 1; /* Received good packet. */ + uint8_t undef : 1; + uint8_t stale : 1; /* Stale receive status. */ +} EL_RCV_STAT; + +/** Buffer control (buf_ctl) modes. */ +typedef enum { + EL_BCTL_SYSTEM = 0, /* Host has buffer access. */ + EL_BCTL_XMT_RCV = 1, /* Transmit, then receive. */ + EL_BCTL_RECEIVE = 2, /* Receive. */ + EL_BCTL_LOOPBACK = 3 /* Loopback. */ +} EL_BUFFER_CONTROL; + +/** + * EtherLink Auxiliary Status Register. + */ +typedef struct ELNK_AUX_CMD { + uint8_t ire : 1; /* Interrupt Request Enable. */ + uint8_t xmit_bf : 1; /* Xmit packets with bad FCS. */ + uint8_t buf_ctl : 2; /* Packet buffer control. */ + uint8_t unused : 1; + uint8_t dma_req : 1; /* DMA request. */ + uint8_t ride : 1; /* Request Interrupt and DMA Enable. */ + uint8_t reset : 1; /* Card in reset while set. */ +} EL_AUX_CMD; + +/** + * EtherLink Auxiliary Status Register. + */ +typedef struct ELNK_AUX_STAT { + uint8_t recv_bsy : 1; /* Receive busy. */ + uint8_t xmit_bf : 1; /* Xmit packets with bad FCS. */ + uint8_t buf_ctl : 2; /* Packet buffer control. */ + uint8_t dma_done : 1; /* DMA done. */ + uint8_t dma_req : 1; /* DMA request. */ + uint8_t ride : 1; /* Request Interrupt and DMA Enable. */ + uint8_t xmit_bsy : 1; /* Transmit busy. */ +} EL_AUX_STAT; + +/** + * Internal interrupt status. + */ +typedef struct ELNK_INTR_STAT { + uint8_t recv_intr : 1; /* Receive interrupt status. */ + uint8_t xmit_intr : 1; /* Transmit interrupt status. */ + uint8_t dma_intr : 1; /* DMA interrupt status. */ + uint8_t unused : 5; +} EL_INTR_STAT; + +typedef struct { + uint32_t base_address; + int base_irq; + uint32_t bios_addr; + uint8_t maclocal[6]; /* configured MAC (local) address. */ + bool fISR; /* Internal interrupt flag. */ + int fDMA; /* Internal DMA active flag. */ + int fInReset; /* Internal in-reset flag. */ + uint8_t aPROM[8]; /* The PROM contents. Only 8 bytes addressable, R/O. */ + uint8_t aStationAddr[6]; /* The station address programmed by the guest, W/O. */ + uint16_t uGPBufPtr; /* General Purpose (GP) Buffer Pointer, R/W. */ + uint16_t uRCVBufPtr; /* Receive (RCV) Buffer Pointer, R/W. */ + /** Transmit Command Register, W/O. */ + union { + uint8_t XmitCmdReg; + EL_XMT_CMD XmitCmd; + }; + /** Transmit Status Register, R/O. */ + union { + uint8_t XmitStatReg; + EL_XMT_STAT XmitStat; + }; + /** Receive Command Register, W/O. */ + union { + uint8_t RcvCmdReg; + EL_RCV_CMD RcvCmd; + }; + /** Receive Status Register, R/O. */ + union { + uint8_t RcvStatReg; + EL_RCV_STAT RcvStat; + }; + /** Auxiliary Command Register, W/O. */ + union { + uint8_t AuxCmdReg; + EL_AUX_CMD AuxCmd; + }; + /** Auxiliary Status Register, R/O. */ + union { + uint8_t AuxStatReg; + EL_AUX_STAT AuxStat; + }; + int fLinkUp; /* If set the link is currently up. */ + int fLinkTempDown; /* If set the link is temporarily down because of a saved state load. */ + uint16_t cLinkDownReported; /* Number of times we've reported the link down. */ + uint16_t cLinkRestorePostponed; /* Number of times we've postponed the link restore. */ + /* Internal interrupt state. */ + union { + uint8_t IntrStateReg; + EL_INTR_STAT IntrState; + }; + uint32_t cMsLinkUpDelay; /* MS to wait before we enable the link. */ + int dma_channel; + uint8_t abLoopBuf[ELNK_BUF_SIZE]; /* The loopback transmit buffer (avoid stack allocations). */ + uint8_t abRuntBuf[64]; /* The runt pad buffer (only really needs 60 bytes). */ + uint8_t abPacketBuf[ELNK_BUF_SIZE]; /* The packet buffer. */ + int dma_pos; + pc_timer_t timer_restore; + netcard_t *netcard; +} threec501_t; + +#ifdef ENABLE_3COM501_LOG +int threec501_do_log = ENABLE_3COM501_LOG; + +static void +threec501_log(const char *fmt, ...) +{ + va_list ap; + + if (threec501_do_log) { + va_start(ap, fmt); + pclog_ex(fmt, ap); + va_end(ap); + } +} +#else +# define threec501_log(fmt, ...) +#endif + +static void elnkSoftReset(threec501_t *dev); +static void elnkR3HardReset(threec501_t *dev); + +#ifndef ETHER_IS_MULTICAST /* Net/Open BSD macro it seems */ +# define ETHER_IS_MULTICAST(a) ((*(uint8_t *) (a)) & 1) +#endif + +#define ETHER_ADDR_LEN ETH_ALEN +#define ETH_ALEN 6 +#pragma pack(1) +struct ether_header /** @todo Use RTNETETHERHDR? */ +{ + uint8_t ether_dhost[ETH_ALEN]; /**< destination ethernet address */ + uint8_t ether_shost[ETH_ALEN]; /**< source ethernet address */ + uint16_t ether_type; /**< packet type ID field */ +}; +#pragma pack() + +static void +elnk_do_irq(threec501_t *dev, int set) +{ + if (set) + picint(1 << dev->base_irq); + else + picintc(1 << dev->base_irq); +} + +/** + * Checks if the link is up. + * @returns true if the link is up. + * @returns false if the link is down. + */ +static __inline int +elnkIsLinkUp(threec501_t *dev) +{ + return !dev->fLinkTempDown && dev->fLinkUp; +} + +/** + * Takes down the link temporarily if it's current status is up. + * + * This is used during restore and when replumbing the network link. + * + * The temporary link outage is supposed to indicate to the OS that all network + * connections have been lost and that it for instance is appropriate to + * renegotiate any DHCP lease. + * + * @param pThis The shared instance data. + */ +static void +elnkTempLinkDown(threec501_t *dev) +{ + if (dev->fLinkUp) { + dev->fLinkTempDown = 1; + dev->cLinkDownReported = 0; + dev->cLinkRestorePostponed = 0; + timer_set_delay_u64(&dev->timer_restore, (dev->cMsLinkUpDelay * 1000) * TIMER_USEC); + } +} + +/** + * @interface_method_impl{PDMDEVREG,pfnReset} + */ +static void +elnkR3Reset(void *priv) +{ + threec501_t *dev = (threec501_t *) priv; + + if (dev->fLinkTempDown) { + dev->cLinkDownReported = 0x1000; + dev->cLinkRestorePostponed = 0x1000; + timer_disable(&dev->timer_restore); + } + + /** @todo How to flush the queues? */ + elnkR3HardReset(dev); +} + +static void +elnkR3HardReset(threec501_t *dev) +{ + dev->fISR = false; + elnk_do_irq(dev, 0); + + /* Clear the packet buffer and station address. */ + memset(dev->abPacketBuf, 0, sizeof(dev->abPacketBuf)); + memset(dev->aStationAddr, 0, sizeof(dev->aStationAddr)); + + /* Reset the buffer pointers. */ + dev->uGPBufPtr = 0; + dev->uRCVBufPtr = 0; + + elnkSoftReset(dev); +} + + +/** + * Check if incoming frame matches the station address. + */ +static __inline int +padr_match(threec501_t *dev, const uint8_t *buf) +{ + struct ether_header *hdr = (struct ether_header *)buf; + int result; + + /* Checks own + broadcast as well as own + multicast. */ + result = (dev->RcvCmd.adr_match >= EL_ADRM_BCAST) && !memcmp(hdr->ether_dhost, dev->aStationAddr, 6); + + return result; +} + +/** + * Check if incoming frame is an accepted broadcast frame. + */ +static __inline int +padr_bcast(threec501_t *dev, const uint8_t *buf) +{ + static uint8_t aBCAST[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + struct ether_header *hdr = (struct ether_header *)buf; + int result = (dev->RcvCmd.adr_match == EL_ADRM_BCAST) && !memcmp(hdr->ether_dhost, aBCAST, 6); + return result; +} + + +/** + * Check if incoming frame is an accepted multicast frame. + */ +static __inline int +padr_mcast(threec501_t *dev, const uint8_t *buf) +{ + struct ether_header *hdr = (struct ether_header *)buf; + int result = (dev->RcvCmd.adr_match == EL_ADRM_MCAST) && ETHER_IS_MULTICAST(hdr->ether_dhost); + return result; +} + +/** + * Update the device IRQ line based on internal state. + */ +static void +elnkUpdateIrq(threec501_t *dev) +{ + bool fISR = false; + + /* IRQ is active if any interrupt source is active and interrupts + * are enabled via RIDE or IRE. + */ + if (dev->IntrStateReg && (dev->AuxCmd.ride || dev->AuxCmd.ire)) + fISR = true; + +#ifdef ENABLE_3COM501_LOG + threec501_log("3Com501: elnkUpdateIrq: fISR=%d\n", fISR); +#endif + + if (fISR != dev->fISR) { + elnk_do_irq(dev, fISR); + dev->fISR = fISR; + } +} + +/** + * Perform a software reset of the NIC. + */ +static void +elnkSoftReset(threec501_t *dev) +{ +#ifdef ENABLE_3COM501_LOG + threec501_log("3Com501: elnkSoftReset\n"); +#endif + + /* Clear some of the user-visible register state. */ + dev->XmitCmdReg = 0; + dev->XmitStatReg = 0; + dev->RcvCmdReg = 0; + dev->RcvStatReg = 0; + dev->AuxCmdReg = 0; + dev->AuxStatReg = 0; + + /* The "stale receive status" is cleared by receiving an "interesting" packet. */ + dev->RcvStat.stale = 1; + + /* By virtue of setting the buffer control to system, transmit is set to busy. */ + dev->AuxStat.xmit_bsy = 1; + + /* Clear internal interrupt state. */ + dev->IntrStateReg = 0; + elnkUpdateIrq(dev); + + /* Note that a soft reset does not clear the packet buffer; software often + * assumes that it survives soft reset. The programmed station address is + * likewise not reset, and the buffer pointers are not reset either. + * Verified on a real 3C501. + */ + + /* No longer in reset state. */ + dev->fInReset = 0; +} + +/** + * Write incoming data into the packet buffer. + */ +static int +elnkReceiveLocked(void *priv, uint8_t *src, int size) +{ + threec501_t *dev = (threec501_t *) priv; + int is_padr = 0, is_bcast = 0, is_mcast = 0; + bool fLoopback = dev->RcvCmd.adr_match == EL_BCTL_LOOPBACK; + + union { + uint8_t RcvStatNewReg; + EL_RCV_STAT RcvStatNew; + } rcvstatnew; + + /* Drop everything if address matching is disabled. */ + if (dev->RcvCmd.adr_match == EL_ADRM_DISABLED) + return 0; + + /* Drop zero-length packets (how does that even happen?). */ + if (!size) + return 0; + + /* + * Drop all packets if the cable is not connected (and not in loopback). + */ + if (!elnkIsLinkUp(dev) && !fLoopback) + return 0; + + /* + * Do not receive further packets until receive status was read. + */ + if (dev->RcvStat.stale == 0) + return 0; + +#ifdef ENABLE_3COM501_LOG + threec501_log("3Com501: size on wire=%d, RCV ptr=%u\n", size, dev->uRCVBufPtr); +#endif + + /* + * Perform address matching. Packets which do not pass the address + * filter are always ignored. + */ + /// @todo cbToRecv must be 6 or more (complete address) + if ((dev->RcvCmd.adr_match == EL_ADRM_PROMISC) /* promiscuous enabled */ + || (is_padr = padr_match(dev, src)) + || (is_bcast = padr_bcast(dev, src)) + || (is_mcast = padr_mcast(dev, src))) { + uint8_t *dst = dev->abPacketBuf + dev->uRCVBufPtr; + +#ifdef ENABLE_3COM501_LOG + threec501_log("3Com501 Packet passed address filter (is_padr=%d, is_bcast=%d, is_mcast=%d), size=%d\n", is_padr, is_bcast, is_mcast, size); +#endif + + /* Receive status is evaluated from scratch. The stale bit must remain set until we know better. */ + rcvstatnew.RcvStatNewReg = 0; + rcvstatnew.RcvStatNew.stale = 1; + dev->RcvStatReg = 0x80; + + /* Detect errors: Runts, overflow, and FCS errors. + * NB: Dribble errors can not happen because we can only receive an + * integral number of bytes. FCS errors are only possible in loopback + * mode in case the FCS is deliberately corrupted. + */ + + /* See if we need to pad, and how much. Have to be careful because the + * Receive Buffer Pointer might be near the end of the buffer. + */ + if (size < 60) { + /* In loopback mode only, short packets are flagged as errors because + * diagnostic tools want to see the errors. Otherwise they're padded to + * minimum length (if packet came over the wire, it should have been + * properly padded). + */ + /// @todo This really is kind of wrong. We shouldn't be doing any + /// padding here, it should be done by the sending side! + if (!fLoopback) { + memset(dev->abRuntBuf, 0, sizeof(dev->abRuntBuf)); + memcpy(dev->abRuntBuf, src, size); + size = 60; + src = dev->abRuntBuf; + } else { +#ifdef ENABLE_3COM501_LOG + threec501_log("3Com501 runt, size=%d\n", size); +#endif + rcvstatnew.RcvStatNew.runt = 1; + } + } + + /* We don't care how big the frame is; if it fits into the buffer, all is + * good. But conversely if the Receive Buffer Pointer is initially near the + * end of the buffer, a small frame can trigger an overflow. + */ + if ((dev->uRCVBufPtr + size) <= ELNK_BUF_SIZE) + rcvstatnew.RcvStatNew.no_ovf = 1; + else { +#ifdef ENABLE_3COM501_LOG + threec501_log("3Com501 overflow, size=%d\n", size); +#endif + rcvstatnew.RcvStatNew.oflow = 1; + } + + if (fLoopback && dev->AuxCmd.xmit_bf) { +#ifdef ENABLE_3COM501_LOG + threec501_log("3Com501 bad FCS\n"); +#endif + rcvstatnew.RcvStatNew.fcs = 1; + } + + /* Error-free packets are considered good. */ + if (rcvstatnew.RcvStatNew.no_ovf && !rcvstatnew.RcvStatNew.fcs && !rcvstatnew.RcvStatNew.runt) + rcvstatnew.RcvStatNew.good = 1; + + uint16_t cbCopy = (uint16_t)MIN(ELNK_BUF_SIZE - dev->uRCVBufPtr, size); + + /* All packets that passed the address filter are copied to the buffer. */ + + /* Copy incoming data to the packet buffer. NB: Starts at the current + * Receive Buffer Pointer position. + */ + memcpy(dst, src, cbCopy); + + /* Packet length is indicated via the receive buffer pointer. */ + dev->uRCVBufPtr = (dev->uRCVBufPtr + cbCopy) & ELNK_GP_MASK; + +#ifdef ENABLE_3COM501_LOG + threec501_log("3Com501 Received packet, size=%d, RP=%u\n", cbCopy, dev->uRCVBufPtr); +#endif + + /* + * If one of the "interesting" conditions was hit, stop receiving until + * the status register is read (mark it not stale). + * NB: The precise receive logic is not very well described in the EtherLink + * documentation. It was refined using the 3C501.EXE diagnostic utility. + */ + if ( (rcvstatnew.RcvStatNew.good && dev->RcvCmd.acpt_good) + || (rcvstatnew.RcvStatNew.no_ovf && dev->RcvCmd.det_eof) + || (rcvstatnew.RcvStatNew.runt && dev->RcvCmd.det_runt) + || (rcvstatnew.RcvStatNew.dribble && dev->RcvCmd.det_drbl) + || (rcvstatnew.RcvStatNew.fcs && dev->RcvCmd.det_fcs) + || (rcvstatnew.RcvStatNew.oflow && dev->RcvCmd.det_ofl)) { + dev->AuxStat.recv_bsy = 0; + dev->IntrState.recv_intr = 1; + rcvstatnew.RcvStatNew.stale = 0; /* Prevents further receive until set again. */ + } + + /* Finally update the receive status. */ + dev->RcvStat = rcvstatnew.RcvStatNew; + +#ifdef ENABLE_3COM501_LOG + threec501_log("3Com501: RcvCmd=%02X, RcvStat=%02X, RCVBufPtr=%u\n", dev->RcvCmdReg, dev->RcvStatReg, dev->uRCVBufPtr); +#endif + + elnkUpdateIrq(dev); + } + + return 1; +} + +/** + * Actually try transmit frames. + * + * @threads TX or EMT. + */ +static void +elnkAsyncTransmit(threec501_t *dev) +{ + /* + * Just drop it if not transmitting. Can happen with delayed transmits + * if transmit was disabled in the meantime. + */ + if (!dev->AuxStat.xmit_bsy) { +#ifdef ENABLE_3COM501_LOG + threec501_log("3Com501: Nope, xmit disabled\n"); +#endif + return; + } + + if (((dev->AuxCmd.buf_ctl != EL_BCTL_XMT_RCV) && (dev->AuxCmd.buf_ctl != EL_BCTL_LOOPBACK))) { +#ifdef ENABLE_3COM501_LOG + threec501_log("3Com501: Nope, not in xmit-then-receive or loopback state\n"); +#endif + return; + } + + /* + * Blast out data from the packet buffer. + */ + do { + /* Don't send anything when the link is down. */ + if ((!elnkIsLinkUp(dev) + && dev->cLinkDownReported > ELNK_MAX_LINKDOWN_REPORTED)) + break; + + bool const fLoopback = dev->AuxCmd.buf_ctl == EL_BCTL_LOOPBACK; + + /* + * Sending is easy peasy, there is by definition always + * a complete packet on hand. + */ + const unsigned cb = ELNK_BUF_SIZE - ELNK_GP(dev); /* Packet size. */ +#ifdef ENABLE_3COM501_LOG + threec501_log("3Com501: cb=%d, loopback=%d.\n", cb, fLoopback); +#endif + + dev->XmitStatReg = 0; /* Clear transmit status before filling it out. */ + + if (elnkIsLinkUp(dev) || fLoopback) { + if (cb <= MAX_FRAME) { + if (fLoopback) { + elnkReceiveLocked(dev, &dev->abPacketBuf[ELNK_GP(dev)], cb); + } else { +#ifdef ENABLE_3COM501_LOG + threec501_log("3Com501: elnkAsyncTransmit: transmit loopbuf xmit pos = %d\n", cb); +#endif + network_tx(dev->netcard, &dev->abPacketBuf[ELNK_GP(dev)], cb); + } + dev->XmitStat.ready = 1; + } else { + /* Signal error, as this violates the Ethernet specs. */ + /** @todo check if the correct error is generated. */ +#ifdef ENABLE_3COM501_LOG + threec501_log("3Com501: illegal giant frame (%u bytes) -> signalling error\n", cb); +#endif + ; + } + } else { + /* Signal a transmit error pretending there was a collision. */ + dev->cLinkDownReported++; + dev->XmitStat.coll = 1; + } + + /* Transmit officially done, update register state. */ + dev->AuxStat.xmit_bsy = 0; + dev->IntrState.xmit_intr = !!(dev->XmitCmdReg & dev->XmitStatReg); +#ifdef ENABLE_3COM501_LOG + threec501_log("3Com501: XmitCmd=%02X, XmitStat=%02X\n", dev->XmitCmdReg, dev->XmitStatReg); +#endif + + /* NB: After a transmit, the GP Buffer Pointer points just past + * the end of the packet buffer (3C501 diagnostics). + */ + dev->uGPBufPtr = ELNK_BUF_SIZE; + + /* NB: The buffer control does *not* change to Receive and stays the way it was. */ + if (!fLoopback) { + dev->AuxStat.recv_bsy = 1; /* Receive Busy now set until a packet is received. */ + } + } while (0); /* No loop, because there isn't ever more than one packet to transmit. */ + + elnkUpdateIrq(dev); +} + +static void +elnkCsrWrite(threec501_t *dev, uint8_t data) +{ + bool fTransmit = false; + bool fReceive = false; + bool fDMAR; + int mode; + + union { + uint8_t reg; + EL_AUX_CMD val; + } auxcmd; + + auxcmd.reg = data; + + /* Handle reset first. */ + if (dev->AuxCmd.reset != auxcmd.val.reset) { + if (auxcmd.val.reset) { + /* Card is placed into reset. Just set the flag. NB: When in reset + * state, we permit writes to other registers, but those have no + * effect and will be overwritten when the card is taken out of reset. + */ +#ifdef ENABLE_3COM501_LOG + threec501_log("3Com501: Card going into reset\n"); +#endif + dev->fInReset = true; + + /* Many EtherLink drivers like to reset the card a lot. That can lead to + * packet loss if a packet was already received before the card was reset. + */ + } else { + /* Card is being taken out of reset. */ +#ifdef ENABLE_3COM501_LOG + threec501_log("3Com501: Card going out of reset\n"); +#endif + elnkSoftReset(dev); + } + dev->AuxCmd.reset = auxcmd.val.reset; /* Update the reset bit, if nothing else. */ + } + + /* If the card is in reset, stop right here. */ + if (dev->fInReset) { +#ifdef ENABLE_3COM501_LOG + threec501_log("3Com501: Reset\n"); +#endif + return; + } + + /* Evaluate DMA state. If it changed, we'll have to go back to R3. */ + fDMAR = auxcmd.val.dma_req && auxcmd.val.ride; + if (fDMAR != dev->fDMA) { + /* Start/stop DMA as requested. */ + dev->fDMA = fDMAR; + if (fDMAR) { + dma_set_drq(dev->dma_channel, fDMAR); + mode = dma_mode(dev->dma_channel); +#ifdef ENABLE_3COM501_LOG + threec501_log("3Com501: DMA Mode = %02x.\n", mode & 0x0c); +#endif + if ((mode & 0x0c) == 0x04) { + while (dev->dma_pos < (ELNK_BUF_SIZE - ELNK_GP(dev))) { + dma_channel_write(dev->dma_channel, dev->abPacketBuf[ELNK_GP(dev) + dev->dma_pos]); + dev->dma_pos++; + } + } else { + while (dev->dma_pos < (ELNK_BUF_SIZE - ELNK_GP(dev))) { + int dma_data = dma_channel_read(dev->dma_channel); + dev->abPacketBuf[ELNK_GP(dev) + dev->dma_pos] = dma_data & 0xff; + dev->dma_pos++; + } + } + dev->uGPBufPtr = (dev->uGPBufPtr + dev->dma_pos) & ELNK_GP_MASK; + dma_set_drq(dev->dma_channel, 0); + dev->dma_pos = 0; + dev->IntrState.dma_intr = 1; + dev->AuxStat.dma_done = 1; + elnkUpdateIrq(dev); + } +#ifdef ENABLE_3COM501_LOG + threec501_log("3Com501: DMARQ for channel %u set to %u\n", dev->dma_channel, fDMAR); +#endif + } + + /* Interrupt enable changes. */ + if ((dev->AuxCmd.ire != auxcmd.val.ire) || (dev->AuxCmd.ride != auxcmd.val.ride)) { + dev->AuxStat.ride = dev->AuxCmd.ride = auxcmd.val.ride; + dev->AuxCmd.ire = auxcmd.val.ire; /* NB: IRE is not visible in the aux status register. */ + } + + /* DMA Request changes. */ + if (dev->AuxCmd.dma_req != auxcmd.val.dma_req) { + dev->AuxStat.dma_req = dev->AuxCmd.dma_req = auxcmd.val.dma_req; + if (!auxcmd.val.dma_req) { + /* Clearing the DMA Request bit also clears the DMA Done status bit and any DMA interrupt. */ + dev->IntrState.dma_intr = 0; + dev->AuxStat.dma_done = 0; + } + } + + /* Packet buffer control changes. */ + if (dev->AuxCmd.buf_ctl != auxcmd.val.buf_ctl) { +#ifdef ENABLE_3COM501_LOG + static const char *apszBuffCntrl[4] = { "System", "Xmit then Recv", "Receive", "Loopback" }; + threec501_log("3Com501: Packet buffer control `%s' -> `%s'\n", apszBuffCntrl[dev->AuxCmd.buf_ctl], apszBuffCntrl[auxcmd.val.buf_ctl]); +#endif + if (auxcmd.val.buf_ctl == EL_BCTL_XMT_RCV) { + /* Transmit, then receive. */ + fTransmit = true; + dev->AuxStat.recv_bsy = 0; + } else if (auxcmd.val.buf_ctl == EL_BCTL_SYSTEM) { + dev->AuxStat.xmit_bsy = 1; /* Transmit Busy is set here and cleared once actual transmit completes. */ + dev->AuxStat.recv_bsy = 0; + } else if (auxcmd.val.buf_ctl == EL_BCTL_RECEIVE) { + /* Special case: If going from xmit-then-receive mode to receive mode, and we received + * a packet already (right after the receive), don't restart receive and lose the already + * received packet. + */ + if (!dev->uRCVBufPtr) + fReceive = true; + } else { + /* For loopback, we go through the regular transmit and receive path. That may be an + * overkill but the receive path is too complex for a special loopback-only case. + */ + fTransmit = true; + dev->AuxStat.recv_bsy = 1; /* Receive Busy now set until a packet is received. */ + } + dev->AuxStat.buf_ctl = dev->AuxCmd.buf_ctl = auxcmd.val.buf_ctl; + } + + /* NB: Bit 1 (xmit_bf, transmit packets with bad FCS) is a simple control + * bit which does not require special handling here. Just copy it over. + */ + dev->AuxStat.xmit_bf = dev->AuxCmd.xmit_bf = auxcmd.val.xmit_bf; + + /* There are multiple bits that affect interrupt state. Handle them now. */ + elnkUpdateIrq(dev); + + /* After fully updating register state, do a transmit (including loopback) or receive. */ + if (fTransmit) + elnkAsyncTransmit(dev); + else if (fReceive) { + dev->AuxStat.recv_bsy = 1; /* Receive Busy now set until a packet is received. */ + } +} + +static uint8_t +threec501_read(uint16_t addr, void *priv) +{ + threec501_t *dev = (threec501_t *) priv; + uint8_t retval = 0xff; + + switch (addr & 0x0f) { + case 0x00: /* Receive status register aliases. The SEEQ 8001 */ + case 0x02: /* EDLC clearly only decodes one bit for reads. */ + case 0x04: + case 0x06: /* Receive status register. */ + retval = dev->RcvStatReg; + dev->RcvStat.stale = 1; /* Allows further reception. */ + dev->IntrState.recv_intr = 0; /* Reading clears receive interrupt. */ + elnkUpdateIrq(dev); + break; + + case 0x01: /* Transmit status register aliases. */ + case 0x03: + case 0x05: + case 0x07: /* Transmit status register. */ + retval = dev->XmitStatReg; + dev->IntrState.xmit_intr = 0; /* Reading clears transmit interrupt. */ + elnkUpdateIrq(dev); + break; + + case 0x08: /* GP Buffer pointer LSB. */ + retval = (dev->uGPBufPtr & 0xff); + break; + case 0x09: /* GP Buffer pointer MSB. */ + retval = (dev->uGPBufPtr >> 8); + break; + + case 0x0a: /* RCV Buffer pointer LSB. */ + retval = (dev->uRCVBufPtr & 0xff); + break; + case 0x0b: /* RCV Buffer pointer MSB. */ + retval = (dev->uRCVBufPtr >> 8); + break; + + case 0x0c: /* Ethernet address PROM window. */ + case 0x0d: /* Alias. */ + /* Reads use low 3 bits of GP buffer pointer, no auto-increment. */ + retval = dev->aPROM[dev->uGPBufPtr & 7]; + break; + + case 0x0e: /* Auxiliary status register. */ + retval = dev->AuxStatReg; + break; + + case 0x0f: /* Buffer window. */ + /* Reads use low 11 bits of GP buffer pointer, auto-increment. */ + retval = dev->abPacketBuf[ELNK_GP(dev)]; + dev->uGPBufPtr = (dev->uGPBufPtr + 1) & ELNK_GP_MASK; + break; + } + + elnkUpdateIrq(dev); +#ifdef ENABLE_3COM501_LOG + threec501_log("3Com501: read addr %x, value %x\n", addr & 0x0f, retval); +#endif + return (retval); +} + +static uint8_t +threec501_nic_readb(uint16_t addr, void *priv) +{ + return threec501_read(addr, priv); +} + +static uint16_t +threec501_nic_readw(uint16_t addr, void *priv) +{ + return threec501_read(addr, priv) | (threec501_read(addr + 1, priv) << 8); +} + +static void +threec501_write(uint16_t addr, uint8_t value, void *priv) +{ + threec501_t *dev = (threec501_t *) priv; + int reg = (addr & 0x0f); + + switch (reg) { + case 0x00: /* Six bytes of station address. */ + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + dev->aStationAddr[reg] = value; + break; + + case 0x06: /* Receive command. */ + dev->RcvCmdReg = value; +#ifdef ENABLE_3COM501_LOG + threec501_log("3Com501: Receive Command register set to %02X\n", dev->RcvCmdReg); +#endif + break; + + case 0x07: /* Transmit command. */ + dev->XmitCmdReg = value; +#ifdef ENABLE_3COM501_LOG + threec501_log("3Com501: Transmit Command register set to %02X\n", dev->XmitCmdReg); +#endif + break; + + case 0x08: /* GP Buffer pointer LSB. */ + dev->uGPBufPtr = (dev->uGPBufPtr & 0xff00) | value; + break; + case 0x09: /* GP Buffer pointer MSB. */ + dev->uGPBufPtr = (dev->uGPBufPtr & 0x00ff) | (value << 8); + break; + + case 0x0a: /* RCV Buffer pointer clear. */ + dev->uRCVBufPtr = 0; +#ifdef ENABLE_3COM501_LOG + threec501_log("3Com501: RCV Buffer Pointer cleared (%02X)\n", value); +#endif + break; + + case 0x0b: /* RCV buffer pointer MSB. */ + case 0x0c: /* Ethernet address PROM window. */ + case 0x0d: /* Undocumented. */ +#ifdef ENABLE_3COM501_LOG + threec501_log("3Com501: Writing read-only register %02X!\n", reg); +#endif + break; + + case 0x0e: /* Auxiliary Command (CSR). */ + elnkCsrWrite(dev, value); + break; + + case 0x0f: /* Buffer window. */ + /* Writes use low 11 bits of GP buffer pointer, auto-increment. */ + if (dev->AuxCmd.buf_ctl != EL_BCTL_SYSTEM) { + /// @todo Does this still increment GPBufPtr? + break; + } + dev->abPacketBuf[ELNK_GP(dev)] = value; + dev->uGPBufPtr = (dev->uGPBufPtr + 1) & ELNK_GP_MASK; + break; + } + +#ifdef ENABLE_3COM501_LOG + threec501_log("3Com501: write addr %x, value %x\n", reg, value); +#endif +} + +static void +threec501_nic_writeb(uint16_t addr, uint8_t value, void *priv) +{ + threec501_write(addr, value, priv); +} + +static void +threec501_nic_writew(uint16_t addr, uint16_t value, void *priv) +{ + threec501_write(addr, value & 0xff, priv); + threec501_write(addr + 1, value >> 8, priv); +} + +static int +elnkSetLinkState(void *priv, uint32_t link_state) +{ + threec501_t *dev = (threec501_t *) priv; + + if (link_state & NET_LINK_TEMP_DOWN) { + elnkTempLinkDown(dev); + return 1; + } + + bool link_up = !(link_state & NET_LINK_DOWN); + if (dev->fLinkUp != link_up) { + dev->fLinkUp = link_up; + if (link_up) { + dev->fLinkTempDown = 1; + dev->cLinkDownReported = 0; + dev->cLinkRestorePostponed = 0; + timer_set_delay_u64(&dev->timer_restore, (dev->cMsLinkUpDelay * 1000) * TIMER_USEC); + } else { + dev->cLinkDownReported = 0; + dev->cLinkRestorePostponed = 0; + } + } + + return 0; +} + +static void +elnkR3TimerRestore(void *priv) +{ + threec501_t *dev = (threec501_t *) priv; + + if ((dev->cLinkDownReported <= ELNK_MAX_LINKDOWN_REPORTED) && + (dev->cLinkRestorePostponed <= ELNK_MAX_LINKRST_POSTPONED)) { + timer_advance_u64(&dev->timer_restore, 1500000 * TIMER_USEC); + dev->cLinkRestorePostponed++; + } else { + dev->fLinkTempDown = 0; + } +} + +static void * +threec501_nic_init(const device_t *info) +{ + uint32_t mac; + threec501_t *dev; + + dev = malloc(sizeof(threec501_t)); + memset(dev, 0x00, sizeof(threec501_t)); + dev->maclocal[0] = 0x02; /* 02:60:8C (3Com OID) */ + dev->maclocal[1] = 0x60; + dev->maclocal[2] = 0x8C; + + dev->base_address = device_get_config_hex16("base"); + dev->base_irq = device_get_config_int("irq"); + dev->dma_channel = device_get_config_int("dma"); + + dev->fLinkUp = 1; + dev->cMsLinkUpDelay = 5000; + + /* See if we have a local MAC address configured. */ + mac = device_get_config_mac("mac", -1); + + /* + * Make this device known to the I/O system. + * PnP and PCI devices start with address spaces inactive. + */ + io_sethandler(dev->base_address, 0x10, + threec501_nic_readb, threec501_nic_readw, NULL, + threec501_nic_writeb, threec501_nic_writew, NULL, dev); + + /* Set up our BIA. */ + if (mac & 0xff000000) { + /* Generate new local MAC. */ + dev->maclocal[3] = random_generate(); + dev->maclocal[4] = random_generate(); + dev->maclocal[5] = random_generate(); + mac = (((int) dev->maclocal[3]) << 16); + mac |= (((int) dev->maclocal[4]) << 8); + mac |= ((int) dev->maclocal[5]); + device_set_config_mac("mac", mac); + } else { + dev->maclocal[3] = (mac >> 16) & 0xff; + dev->maclocal[4] = (mac >> 8) & 0xff; + dev->maclocal[5] = (mac & 0xff); + } + + /* Initialize the PROM */ + memcpy(dev->aPROM, dev->maclocal, sizeof(dev->maclocal)); + dev->aPROM[6] = dev->aPROM[7] = 0; /* The two padding bytes. */ + +#ifdef ENABLE_3COM501_LOG + threec501_log("I/O=%04x, IRQ=%d, DMA=%d, MAC=%02x:%02x:%02x:%02x:%02x:%02x\n", + dev->base_address, dev->base_irq, dev->dma_channel, + dev->aPROM[0], dev->aPROM[1], dev->aPROM[2], + dev->aPROM[3], dev->aPROM[4], dev->aPROM[5]); +#endif + + /* Reset the board. */ + elnkR3HardReset(dev); + + /* Attach ourselves to the network module. */ + dev->netcard = network_attach(dev, dev->aPROM, elnkReceiveLocked, elnkSetLinkState); + + timer_add(&dev->timer_restore, elnkR3TimerRestore, dev, 0); + + return (dev); +} + +static void +threec501_nic_close(void *priv) +{ + threec501_t *dev = (threec501_t *) priv; + +#ifdef ENABLE_3COM501_LOG + threec501_log("3Com501: closed\n"); +#endif + + free(dev); +} + +static const device_config_t threec501_config[] = { + { + .name = "base", + .description = "Address", + .type = CONFIG_HEX16, + .default_string = "", + .default_int = 0x300, + .file_filter = "", + .spinner = { 0 }, + .selection = { + { .description = "0x280", .value = 0x280 }, + { .description = "0x300", .value = 0x300 }, + { .description = "0x310", .value = 0x310 }, + { .description = "0x320", .value = 0x320 }, + { .description = "" } + }, + }, + { + .name = "irq", + .description = "IRQ", + .type = CONFIG_SELECTION, + .default_string = "", + .default_int = 3, + .file_filter = "", + .spinner = { 0 }, + .selection = { + { .description = "IRQ 2/9", .value = 9 }, + { .description = "IRQ 3", .value = 3 }, + { .description = "IRQ 4", .value = 4 }, + { .description = "IRQ 5", .value = 5 }, + { .description = "IRQ 6", .value = 6 }, + { .description = "IRQ 7", .value = 7 }, + { .description = "" } + }, + }, + { + .name = "dma", + .description = "DMA channel", + .type = CONFIG_SELECTION, + .default_string = "", + .default_int = 3, + .file_filter = "", + .spinner = { 0 }, + .selection = { + { .description = "DMA 1", .value = 1 }, + { .description = "DMA 2", .value = 2 }, + { .description = "DMA 3", .value = 3 }, + { .description = "" } + }, + }, + { + .name = "mac", + .description = "MAC Address", + .type = CONFIG_MAC, + .default_string = "", + .default_int = -1 + }, + { .name = "", .description = "", .type = CONFIG_END } +}; + +const device_t threec501_device = { + .name = "3Com EtherLink (3c500/3c501)", + .internal_name = "3c501", + .flags = DEVICE_ISA, + .local = 0, + .init = threec501_nic_init, + .close = threec501_nic_close, + .reset = elnkR3Reset, + { .available = NULL }, + .speed_changed = NULL, + .force_redraw = NULL, + .config = threec501_config +}; diff --git a/src/network/net_pcnet.c b/src/network/net_pcnet.c index d3c5e29b5..e1f62c874 100644 --- a/src/network/net_pcnet.c +++ b/src/network/net_pcnet.c @@ -3065,6 +3065,8 @@ static const device_config_t pcnet_isa_config[] = { { .description = "IRQ 4", .value = 4 }, { .description = "IRQ 5", .value = 5 }, { .description = "IRQ 9", .value = 9 }, + { .description = "IRQ 10", .value = 10 }, + { .description = "IRQ 11", .value = 11 }, { .description = "" } }, }, @@ -3124,6 +3126,8 @@ static const device_config_t pcnet_vlb_config[] = { { .description = "IRQ 4", .value = 4 }, { .description = "IRQ 5", .value = 5 }, { .description = "IRQ 9", .value = 9 }, + { .description = "IRQ 10", .value = 10 }, + { .description = "IRQ 11", .value = 11 }, { .description = "" } }, }, diff --git a/src/network/network.c b/src/network/network.c index 0fc137092..f6e9eb565 100644 --- a/src/network/network.c +++ b/src/network/network.c @@ -67,6 +67,7 @@ #include <86box/ui.h> #include <86box/timer.h> #include <86box/network.h> +#include <86box/net_3c501.h> #include <86box/net_3c503.h> #include <86box/net_ne2000.h> #include <86box/net_pcnet.h> @@ -95,6 +96,7 @@ static const device_t net_none_device = { static const device_t *net_cards[] = { &net_none_device, + &threec501_device, &threec503_device, &pcnet_am79c960_device, &pcnet_am79c961_device, diff --git a/src/scsi/scsi_cdrom.c b/src/scsi/scsi_cdrom.c index f6924864d..80ff4184a 100644 --- a/src/scsi/scsi_cdrom.c +++ b/src/scsi/scsi_cdrom.c @@ -30,6 +30,7 @@ #include <86box/device.h> #include <86box/scsi.h> #include <86box/scsi_device.h> +#include <86box/machine.h> #include <86box/nvr.h> #include <86box/hdc.h> #include <86box/hdc_ide.h> @@ -2373,6 +2374,8 @@ scsi_cdrom_command(scsi_common_t *sc, uint8_t *cdb) } else { if (dev->early) ide_padstr8(dev->buffer + idx, 8, "NEC"); /* Vendor */ + else if (machine_is_sony()) + ide_padstr8(dev->buffer + idx, 8, "SONY"); /* Vendor */ else ide_padstr8(dev->buffer + idx, 8, "HITACHI"); /* Vendor */ } @@ -2389,6 +2392,8 @@ scsi_cdrom_command(scsi_common_t *sc, uint8_t *cdb) } else { if (dev->early) ide_padstr8(dev->buffer + idx, 40, "CD-ROM DRIVE:260"); /* Product */ + else if (machine_is_sony()) + ide_padstr8(dev->buffer + idx, 40, "CD-ROM CDU76"); /* Product */ else ide_padstr8(dev->buffer + idx, 40, "CDR-8130"); /* Product */ } @@ -2446,6 +2451,10 @@ scsi_cdrom_command(scsi_common_t *sc, uint8_t *cdb) ide_padstr8(dev->buffer + 8, 8, "NEC"); /* Vendor */ ide_padstr8(dev->buffer + 16, 16, "CD-ROM DRIVE:260"); /* Product */ ide_padstr8(dev->buffer + 32, 4, "1.01"); /* Revision */ + } else if (machine_is_sony()) { + ide_padstr8(dev->buffer + 8, 8, "SONY"); /* Vendor */ + ide_padstr8(dev->buffer + 16, 16, "CD-ROM CDU76"); /* Product */ + ide_padstr8(dev->buffer + 32, 4, "1.0i"); /* Revision */ } else { ide_padstr8(dev->buffer + 8, 8, "HITACHI"); /* Vendor */ ide_padstr8(dev->buffer + 16, 16, "CDR-8130"); /* Product */ @@ -2749,6 +2758,9 @@ scsi_cdrom_identify(ide_t *ide, int ide_has_dma) ide_padstr((char *) (ide->buffer + 23), ".110 ", 8); /* Firmware */ ide_padstr((char *) (ide->buffer + 27), "EN C DCR-MOD IREV2:06 ", 40); /* Model */ # endif + } else if (machine_is_sony()) { + ide_padstr((char *) (ide->buffer + 23), "1.0i ", 8); /* Firmware */ + ide_padstr((char *) (ide->buffer + 27), "CD-ROM CDU76 ", 40); /* Model */ } else { ide_padstr((char *) (ide->buffer + 23), "0020 ", 8); /* Firmware */ ide_padstr((char *) (ide->buffer + 27), "HITACHI CDR-8130 ", 40); /* Model */ diff --git a/src/scsi/scsi_ncr5380.c b/src/scsi/scsi_ncr5380.c index 526dff73a..668f9ea9f 100644 --- a/src/scsi/scsi_ncr5380.c +++ b/src/scsi/scsi_ncr5380.c @@ -1354,22 +1354,7 @@ t128_read(uint32_t addr, void *priv) ret = ncr_dev->t128.status; ncr_log("T128 status read = %02x, cur bus = %02x, req = %02x, dma = %02x\n", ret, ncr->cur_bus, ncr->cur_bus & BUS_REQ, ncr->mode & MODE_DMA); } else if (addr >= 0x1d00 && addr < 0x1e00) { - if (addr >= 0x1d00 && addr < 0x1d20) - ret = ncr_read(0, ncr_dev); - else if (addr >= 0x1d20 && addr < 0x1d40) - ret = ncr_read(1, ncr_dev); - else if (addr >= 0x1d40 && addr < 0x1d60) - ret = ncr_read(2, ncr_dev); - else if (addr >= 0x1d60 && addr < 0x1d80) - ret = ncr_read(3, ncr_dev); - else if (addr >= 0x1d80 && addr < 0x1da0) - ret = ncr_read(4, ncr_dev); - else if (addr >= 0x1da0 && addr < 0x1dc0) - ret = ncr_read(5, ncr_dev); - else if (addr >= 0x1dc0 && addr < 0x1de0) - ret = ncr_read(6, ncr_dev); - else if (addr >= 0x1de0 && addr < 0x1e00) - ret = ncr_read(7, ncr_dev); + ret = ncr_read((addr - 0x1d00) >> 5, ncr_dev); } else if (addr >= 0x1e00 && addr < 0x2000) { if (ncr_dev->t128.host_pos >= MIN(512, dev->buffer_length) || ncr->dma_mode != DMA_INITIATOR_RECEIVE) { ret = 0xff; @@ -1408,24 +1393,9 @@ t128_write(uint32_t addr, uint8_t val, void *priv) } ncr_dev->t128.ctrl = val; ncr_log("T128 ctrl write = %02x\n", val); - } else if (addr >= 0x1d00 && addr < 0x1e00) { - if (addr >= 0x1d00 && addr < 0x1d20) - ncr_write(0, val, ncr_dev); - else if (addr >= 0x1d20 && addr < 0x1d40) - ncr_write(1, val, ncr_dev); - else if (addr >= 0x1d40 && addr < 0x1d60) - ncr_write(2, val, ncr_dev); - else if (addr >= 0x1d60 && addr < 0x1d80) - ncr_write(3, val, ncr_dev); - else if (addr >= 0x1d80 && addr < 0x1da0) - ncr_write(4, val, ncr_dev); - else if (addr >= 0x1da0 && addr < 0x1dc0) - ncr_write(5, val, ncr_dev); - else if (addr >= 0x1dc0 && addr < 0x1de0) - ncr_write(6, val, ncr_dev); - else if (addr >= 0x1de0 && addr < 0x1e00) - ncr_write(7, val, ncr_dev); - } else if (addr >= 0x1e00 && addr < 0x2000) { + } else if (addr >= 0x1d00 && addr < 0x1e00) + ncr_write((addr - 0x1d00) >> 5, val, ncr_dev); + else if (addr >= 0x1e00 && addr < 0x2000) { if (ncr_dev->t128.host_pos < MIN(512, dev->buffer_length) && ncr->dma_mode == DMA_SEND) { ncr_dev->t128.buffer[ncr_dev->t128.host_pos] = val; ncr_dev->t128.host_pos++; diff --git a/src/sound/snd_ad1848.c b/src/sound/snd_ad1848.c index bf3858aa3..558024fb5 100644 --- a/src/sound/snd_ad1848.c +++ b/src/sound/snd_ad1848.c @@ -378,24 +378,27 @@ ad1848_write(uint16_t addr, uint8_t val, void *priv) case 25: return; + case 27: + if (ad1848->type != AD1848_TYPE_DEFAULT) + return; + break; } ad1848->regs[ad1848->index] = val; if (updatefreq) ad1848_updatefreq(ad1848); - if (ad1848->type >= AD1848_TYPE_CS4231) { /* TODO: configure CD volume for CS4248/AD1848 too */ - temp = (ad1848->type == AD1848_TYPE_CS4231) ? 18 : 4; - if (ad1848->regs[temp] & 0x80) - ad1848->cd_vol_l = 0; - else - ad1848->cd_vol_l = ad1848_vols_5bits_aux_gain[ad1848->regs[temp] & 0x1f]; - temp++; - if (ad1848->regs[temp] & 0x80) - ad1848->cd_vol_r = 0; - else - ad1848->cd_vol_r = ad1848_vols_5bits_aux_gain[ad1848->regs[temp] & 0x1f]; - } + temp = (ad1848->type < AD1848_TYPE_CS4231) ? 2 : ((ad1848->type == AD1848_TYPE_CS4231) ? 18 : 4); + if (ad1848->regs[temp] & 0x80) + ad1848->cd_vol_l = 0; + else + ad1848->cd_vol_l = ad1848_vols_5bits_aux_gain[ad1848->regs[temp] & 0x1f]; + temp++; + if (ad1848->regs[temp] & 0x80) + ad1848->cd_vol_r = 0; + else + ad1848->cd_vol_r = ad1848_vols_5bits_aux_gain[ad1848->regs[temp] & 0x1f]; + break; case 2: @@ -599,6 +602,24 @@ ad1848_filter_cd_audio(int channel, double *buffer, void *priv) *buffer = c; } +void +ad1848_filter_aux2(void *priv, double *out_l, double *out_r) +{ + ad1848_t *ad1848 = (ad1848_t *) priv; + + if (ad1848->regs[4] & 0x80) { + *out_l = 0.0; + } else { + *out_l = ((*out_l) * ad1848_vols_5bits_aux_gain[ad1848->regs[4] & 0x1f]) / 65536.0; + } + + if (ad1848->regs[5] & 0x80) { + *out_r = 0.0; + } else { + *out_r = ((*out_r) * ad1848_vols_5bits_aux_gain[ad1848->regs[5] & 0x1f]) / 65536.0; + } +} + void ad1848_init(ad1848_t *ad1848, uint8_t type) { diff --git a/src/sound/snd_opl_ymfm.cpp b/src/sound/snd_opl_ymfm.cpp index c45c76479..bb52f3c55 100644 --- a/src/sound/snd_opl_ymfm.cpp +++ b/src/sound/snd_opl_ymfm.cpp @@ -320,12 +320,12 @@ ymfm_drv_read(uint16_t port, void *priv) { YMFMChipBase *drv = (YMFMChipBase *) priv; - if (port == 0x381) { - port += 4; - } /* Point to register read port. */ - if (drv->flags() & FLAG_CYCLES) { + if ((port == 0x380) || (port == 0x381)) + port |= 4; + + /* Point to register read port. */ + if (drv->flags() & FLAG_CYCLES) cycles -= ((int) (isa_timing * 8)); - } uint8_t ret = drv->read(port); drv->update(); @@ -339,9 +339,8 @@ ymfm_drv_write(uint16_t port, uint8_t val, void *priv) { YMFMChipBase *drv = (YMFMChipBase *) priv; ymfm_log("YMFM write port %04x value = %02x\n", port, val); - if (port == 0x380 || port == 0x381) { - port += 4; - } + if ((port == 0x380) || (port == 0x381)) + port |= 4; drv->write(port, val); drv->update(); } diff --git a/src/sound/snd_optimc.c b/src/sound/snd_optimc.c index eb48211c6..9c3e4c133 100644 --- a/src/sound/snd_optimc.c +++ b/src/sound/snd_optimc.c @@ -70,6 +70,18 @@ typedef struct optimc_t { uint8_t regs[6]; } optimc_t, opti_82c929_t; +static void +optimc_filter_opl(void* priv, double* out_l, double* out_r) +{ + optimc_t *optimc = (optimc_t *) priv; + + if (optimc->cur_wss_enabled) { + *out_l /= optimc->sb->mixer_sbpro.fm_l; + *out_r /= optimc->sb->mixer_sbpro.fm_r; + ad1848_filter_aux2((void*)&optimc->ad1848, out_l, out_r); + } +} + static uint8_t optimc_wss_read(uint16_t addr, void *priv) { @@ -141,41 +153,38 @@ optimc_reg_write(uint16_t addr, uint8_t val, void *p) if (optimc->reg_enabled) { switch (idx) { case 0: /* MC1 */ - { - optimc->regs[0] = val; - if (val != old) { - optimc->cur_mode = optimc->cur_wss_enabled = !!(val & 0x80); - io_removehandler(optimc->cur_wss_addr, 0x0004, optimc_wss_read, NULL, NULL, optimc_wss_write, NULL, NULL, optimc); - io_removehandler(optimc->cur_wss_addr + 0x0004, 0x0004, ad1848_read, NULL, NULL, ad1848_write, NULL, NULL, &optimc->ad1848); - switch ((val >> 4) & 0x3) { - case 0: /* WSBase = 0x530 */ - { - optimc->cur_wss_addr = 0x530; - break; - } - case 1: /* WSBase = 0xE80 */ - { - optimc->cur_wss_addr = 0xE80; - break; - } - case 2: /* WSBase = 0xF40 */ - { - optimc->cur_wss_addr = 0xF40; - break; - } - case 3: /* WSBase = 0x604 */ - { - optimc->cur_wss_addr = 0x604; - break; - } - } - io_sethandler(optimc->cur_wss_addr, 0x0004, optimc_wss_read, NULL, NULL, optimc_wss_write, NULL, NULL, optimc); - io_sethandler(optimc->cur_wss_addr + 0x0004, 0x0004, ad1848_read, NULL, NULL, ad1848_write, NULL, NULL, &optimc->ad1848); + optimc->regs[0] = val; + if (val != old) { + optimc->cur_mode = optimc->cur_wss_enabled = !!(val & 0x80); - gameport_remap(optimc->gameport, (optimc->regs[0] & 0x1) ? 0x00 : 0x200); + sound_set_cd_audio_filter(NULL, NULL); + if (optimc->cur_wss_enabled) /* WSS */ + sound_set_cd_audio_filter(ad1848_filter_cd_audio, &optimc->ad1848); + else /* SBPro */ + sound_set_cd_audio_filter(sbpro_filter_cd_audio, optimc->sb); + + io_removehandler(optimc->cur_wss_addr, 0x0004, optimc_wss_read, NULL, NULL, optimc_wss_write, NULL, NULL, optimc); + io_removehandler(optimc->cur_wss_addr + 0x0004, 0x0004, ad1848_read, NULL, NULL, ad1848_write, NULL, NULL, &optimc->ad1848); + switch ((val >> 4) & 0x3) { + case 0: /* WSBase = 0x530 */ + optimc->cur_wss_addr = 0x530; + break; + case 1: /* WSBase = 0xE80 */ + optimc->cur_wss_addr = 0xE80; + break; + case 2: /* WSBase = 0xF40 */ + optimc->cur_wss_addr = 0xF40; + break; + case 3: /* WSBase = 0x604 */ + optimc->cur_wss_addr = 0x604; + break; } - break; + io_sethandler(optimc->cur_wss_addr, 0x0004, optimc_wss_read, NULL, NULL, optimc_wss_write, NULL, NULL, optimc); + io_sethandler(optimc->cur_wss_addr + 0x0004, 0x0004, ad1848_read, NULL, NULL, ad1848_write, NULL, NULL, &optimc->ad1848); + + gameport_remap(optimc->gameport, (optimc->regs[0] & 0x1) ? 0x00 : 0x200); } + break; case 1: /* MC2 */ optimc->regs[1] = val; break; @@ -263,10 +272,9 @@ optimc_reg_write(uint16_t addr, uint8_t val, void *p) } if (optimc->reg_enabled) optimc->reg_enabled = 0; - if (addr == 0xF8F && (val == optimc->type || val == 0x00)) { - if (addr == 0xF8F && val == optimc->type && !optimc->reg_enabled) { + if ((addr == 0xF8F) && ((val == optimc->type) || (val == 0x00))) { + if ((addr == 0xF8F) && (val == optimc->type) && !optimc->reg_enabled) optimc->reg_enabled = 1; - } if (reg_enable_phase) { switch (reg_enable_phase) { case 1: @@ -305,7 +313,8 @@ optimc_reg_read(uint16_t addr, void *p) case 3: /* MC4 */ case 4: /* MC5 */ temp = optimc->regs[addr - 0xF8D]; - case 5: /* MC6 (not readable) */ + case 5: /* MC6 */ + temp = optimc->regs[5]; break; case 2: /* MC3 */ temp = (optimc->regs[2] & ~0x3) | 0x2; @@ -333,14 +342,14 @@ optimc_init(const device_t *info) optimc->cur_mpu401_addr = 0x330; optimc->cur_mpu401_enabled = 1; - optimc->regs[0] = ((device_get_config_int("gameport")) ? 0x01 : 0x00); + optimc->regs[0] = 0x00; optimc->regs[1] = 0x03; optimc->regs[2] = 0x00; optimc->regs[3] = 0x00; optimc->regs[4] = 0x3F; optimc->regs[5] = 0x83; - optimc->gameport = gameport_add(&gameport_device); + optimc->gameport = gameport_add(&gameport_pnp_device); gameport_remap(optimc->gameport, (optimc->regs[0] & 0x1) ? 0x00 : 0x200); if (info->local & 0x100) @@ -365,19 +374,21 @@ optimc_init(const device_t *info) sb_dsp_setdma8(&optimc->sb->dsp, optimc->cur_dma); sb_ct1345_mixer_reset(optimc->sb); + optimc->sb->opl_mixer = optimc; + optimc->sb->opl_mix = optimc_filter_opl; + optimc->fm_type = (info->local & OPTIMC_OPL4) ? FM_YMF278B : FM_YMF262; fm_driver_get(optimc->fm_type, &optimc->sb->opl); io_sethandler(optimc->cur_addr + 0, 0x0004, optimc->sb->opl.read, NULL, NULL, optimc->sb->opl.write, NULL, NULL, optimc->sb->opl.priv); io_sethandler(optimc->cur_addr + 8, 0x0002, optimc->sb->opl.read, NULL, NULL, optimc->sb->opl.write, NULL, NULL, optimc->sb->opl.priv); io_sethandler(0x0388, 0x0004, optimc->sb->opl.read, NULL, NULL, optimc->sb->opl.write, NULL, NULL, optimc->sb->opl.priv); - if (optimc->fm_type == FM_YMF278B) { + if (optimc->fm_type == FM_YMF278B) io_sethandler(0x380, 2, optimc->sb->opl.read, NULL, NULL, optimc->sb->opl.write, NULL, NULL, optimc->sb->opl.priv); - } io_sethandler(optimc->cur_addr + 4, 0x0002, sb_ct1345_mixer_read, NULL, NULL, sb_ct1345_mixer_write, NULL, NULL, optimc->sb); sound_add_handler(optimc_get_buffer, optimc); - sound_set_cd_audio_filter(sbpro_filter_cd_audio, optimc->sb); + sound_set_cd_audio_filter(sbpro_filter_cd_audio, optimc->sb); /* CD audio filter for the default context */ optimc->mpu = (mpu_t *) malloc(sizeof(mpu_t)); memset(optimc->mpu, 0, sizeof(mpu_t)); @@ -416,12 +427,6 @@ mirosound_pcm10_available(void) static const device_config_t acermagic_s20_config[] = { // clang-format off - { - .name = "gameport", - .description = "Gameport", - .type = CONFIG_BINARY, - .default_int = 0 - }, { .name = "receive_input", .description = "Receive input (SB MIDI)", @@ -466,4 +471,4 @@ const device_t mirosound_pcm10_device = { .speed_changed = optimc_speed_changed, .force_redraw = NULL, .config = acermagic_s20_config -}; \ No newline at end of file +}; diff --git a/src/sound/snd_sb.c b/src/sound/snd_sb.c index 1c3b97756..7660ace90 100644 --- a/src/sound/snd_sb.c +++ b/src/sound/snd_sb.c @@ -290,6 +290,9 @@ sb_get_buffer_sbpro(int32_t *buffer, int len, void *p) } else { out_l = (((double) opl_buf[c]) * mixer->fm_l) * 0.7171630859375; out_r = (((double) opl_buf[c + 1]) * mixer->fm_r) * 0.7171630859375; + if (sb->opl_mix && sb->opl_mixer) { + sb->opl_mix(sb->opl_mixer, &out_l, &out_r); + } } } diff --git a/src/win/Makefile.mingw b/src/win/Makefile.mingw index 7d5621a8e..61ed25f9b 100644 --- a/src/win/Makefile.mingw +++ b/src/win/Makefile.mingw @@ -647,7 +647,7 @@ NETOBJ := network.o \ arp_table.o bootp.o cksum.o dnssearch.o if.o ip_icmp.o ip_input.o \ ip_output.o mbuf.o misc.o sbuf.o slirp.o socket.o tcp_input.o \ tcp_output.o tcp_subr.o tcp_timer.o udp.o util.o version.o \ - net_dp8390.o \ + net_dp8390.o net_3c501.o \ net_3c503.o net_ne2000.o \ net_pcnet.o net_wd8003.o \ net_plip.o net_event.o