Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ set(COMMON_SOURCES
src/BlueSCSI_disk.cpp
src/BlueSCSI_cdrom.cpp
src/BlueSCSI_tape.cpp
src/BlueSCSI_printer.cpp
src/BlueSCSI_log.cpp
src/BlueSCSI_log_trace.cpp
src/BlueSCSI_blink.cpp
Expand Down
3 changes: 3 additions & 0 deletions lib/BlueSCSI_platform_RP2MCU/rp2040-template.ld
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ SECTIONS
*BlueSCSI_settings.cpp.o(.text .text*)
*QuirksCheck.cpp.o(.text .text*)
*(.text*scsiDiskFilenameValid*)
*(.text*scsiDiskFolderIs*)
*(.text*scsiDiskFolderContainsCueSheet*)
*(.text*scsiDiskOpenHDDImage*)
*(.text*scsiDiskGetNextImageName*)
*(.text*scsiDiskProgramRomDrive*)
Expand Down Expand Up @@ -189,6 +191,7 @@ SECTIONS
*(.text*scsiTapeCommand*)
*(.text*scsiCDRomCommand*)
*(.text*scsiMOCommand*)
*BlueSCSI_printer.cpp.o(.text .text*)
*(.text*doReadCD*)
*(.text*doReadTOC*)
*(.text*doReadHeader*)
Expand Down
1 change: 1 addition & 0 deletions lib/SCSI2SD/include/scsi2sd.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ typedef enum
S2S_CFG_NETWORK = 6,
S2S_CFG_ZIP100 = 7,
S2S_CFG_AMIGAWIFI = 8,
S2S_CFG_PRINTER = 9,
S2S_CFG_NOT_SET = 255

} S2S_CFG_TYPE;
Expand Down
29 changes: 27 additions & 2 deletions lib/SCSI2SD/src/firmware/inquiry.c
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,14 @@ void s2s_scsiInquiry()
case S2S_CFG_AMIGAWIFI:
scsiDev.data[2] = 0x01; // Page code.
break;


case S2S_CFG_PRINTER:
// Apple LaserWriter IISC reports pre-SCSI-2 (ANSI version 0,
// response format SCSI-1 CCS).
scsiDev.data[2] = 0x00;
scsiDev.data[3] = 0x00;
break;

default:
// Accept defaults for a fixed disk.
break;
Expand Down Expand Up @@ -282,8 +289,21 @@ uint32_t s2s_getStandardInquiry(
out[7] = 0x00; // Disable sync and linked commands
out[4] = 0x75; // 117 length
}
if(cfg->deviceType == S2S_CFG_PRINTER)
{
// Apple LaserWriter IISC trailing vendor-specific bytes (firmware/ROM
// rev). The driver only checks the first 36 bytes, but TattleTech and
// other diagnostics may read these.
static const uint8_t LaserWriterVendor[] =
{0x00, 0x00, 0x00, 0xFE, 0x20, 0x27, 0x20, 0xFF};
if (size + sizeof(LaserWriterVendor) <= maxlen)
{
memcpy(&out[size], LaserWriterVendor, sizeof(LaserWriterVendor));
size += sizeof(LaserWriterVendor);
}
}
// Iomega already has a vendor inquiry
if(cfg->deviceType != S2S_CFG_NETWORK && cfg->deviceType != S2S_CFG_ZIP100) {
else if(cfg->deviceType != S2S_CFG_NETWORK && cfg->deviceType != S2S_CFG_ZIP100) {
memcpy(&out[size], INQUIRY_NAME, sizeof(INQUIRY_NAME) - 1);
size += sizeof(INQUIRY_NAME) - 1;
out[size++] = TOOLBOX_API;
Expand Down Expand Up @@ -322,6 +342,11 @@ uint8_t getDeviceTypeQualifier()
return 0x03;
break;

case S2S_CFG_PRINTER:
// printer device
return 0x02;
break;

default:
// Accept defaults for a fixed disk.
return 0;
Expand Down
25 changes: 25 additions & 0 deletions lib/SCSI2SD/src/firmware/printer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Copyright (C) 2026 Eric Helgeson
*
* This file is part of BlueSCSI
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
**/

#ifndef PRINTER_H
#define PRINTER_H

int scsiPrinterCommand(void);

#endif
3 changes: 3 additions & 0 deletions lib/SCSI2SD/src/firmware/scsi.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Copyright (c) 2023 joshua stein <jcs@jcs.org>
// Copyright (c) 2023 Andrea Ottaviani <andrea.ottaviani.69@gmail.com>
// Copyright (c) 2024-2025 Rabbit Hole Computing™
// Copyright (c) 2026 Eric Helgeson
//
// This file is part of SCSI2SD.
//
Expand Down Expand Up @@ -33,6 +34,7 @@
#include "bsp.h"
#include "cdrom.h"
#include "network.h"
#include "printer.h"
#include "tape.h"
#include "mo.h"
#include "vendor.h"
Expand Down Expand Up @@ -717,6 +719,7 @@ static void process_Command()
((cfg->deviceType == S2S_CFG_AMIGAWIFI && amigaWifiCommand())) ||
((cfg->deviceType == S2S_CFG_NETWORK && scsiNetworkCommand())) ||
#endif // BLUESCSI_NETWORK
((cfg->deviceType == S2S_CFG_PRINTER) && scsiPrinterCommand()) ||
((cfg->deviceType == S2S_CFG_MO) && scsiMOCommand()))
{
// Already handled.
Expand Down
11 changes: 8 additions & 3 deletions src/BlueSCSI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,8 @@ static const char * typeToChar(int deviceType)
return "Removable";
case S2S_CFG_ZIP100:
return "ZIP100";
case S2S_CFG_PRINTER:
return "Printer";
default:
return "Unknown";
}
Expand Down Expand Up @@ -494,7 +496,7 @@ bool findHDDImages()
}

char name[MAX_FILE_PATH+1];
if(!file.isDir() || scsiDiskFolderContainsCueSheet(&file) || scsiDiskFolderIsTapeFolder(&file)) {
if(!file.isDir() || scsiDiskFolderContainsCueSheet(&file) || scsiDiskFolderIsTapeFolder(&file) || scsiDiskFolderIsPrinterFolder(&file)) {
file.getName(name, MAX_FILE_PATH+1);
file.close();

Expand Down Expand Up @@ -555,12 +557,13 @@ bool findHDDImages()
bool is_re = (tolower(name[0]) == 'r' && tolower(name[1]) == 'e');
bool is_tp = (tolower(name[0]) == 't' && tolower(name[1]) == 'p');
bool is_zp = (tolower(name[0]) == 'z' && tolower(name[1]) == 'p');
bool is_pr = (tolower(name[0]) == 'p' && tolower(name[1]) == 'r');
#ifdef BLUESCSI_NETWORK
bool is_ne = (tolower(name[0]) == 'n' && tolower(name[1]) == 'e');
bool is_am = (tolower(name[0]) == 'a' && tolower(name[1]) == 'm');
#endif // BLUESCSI_NETWORK

if (is_hd || is_cd || is_fd || is_mo || is_re || is_tp || is_zp
if (is_hd || is_cd || is_fd || is_mo || is_re || is_tp || is_zp || is_pr
#ifdef BLUESCSI_NETWORK
|| is_ne || is_am
#endif // BLUESCSI_NETWORK
Expand Down Expand Up @@ -646,6 +649,7 @@ bool findHDDImages()
if (is_re) type = S2S_CFG_REMOVABLE;
if (is_tp) type = S2S_CFG_SEQUENTIAL;
if (is_zp) type = S2S_CFG_ZIP100;
if (is_pr) type = S2S_CFG_PRINTER;

g_scsi_settings.initDevice(id & 7, type);
// Open the image file
Expand Down Expand Up @@ -700,7 +704,8 @@ bool findHDDImages()
{
int capacity_kB = ((uint64_t)cfg->scsiSectors * cfg->bytesPerSector) / 1024;

if (cfg->deviceType == S2S_CFG_NETWORK || cfg->deviceType == S2S_CFG_AMIGAWIFI)
if (cfg->deviceType == S2S_CFG_NETWORK || cfg->deviceType == S2S_CFG_AMIGAWIFI ||
cfg->deviceType == S2S_CFG_PRINTER)
{
logmsg("ID: ", (int)s2s_getTargetId(cfg),
", Type: ", typeToChar((int)cfg->deviceType));
Expand Down
3 changes: 3 additions & 0 deletions src/BlueSCSI_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*
* ZuluSCSI™ - Copyright (c) 2022-2025 Rabbit Hole Computing™
* Portions copyright (c) 2023 joshua stein <jcs@jcs.org>
* Copyright (c) 2026 Eric Helgeson
*
* ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version.
*
Expand Down Expand Up @@ -105,6 +106,7 @@
#define DRIVEINFO_NETWORK {"Dayna", "SCSI/Link", "2.0f", ""}
#define DRIVEINFO_TAPE {"BLUESCSI", "TAPE", PLATFORM_REVISION, ""}
#define DRIVEINFO_AMIGAWIFI {"AmigaNET", "SCSI/Link", "1.0f", ""}
#define DRIVEINFO_PRINTER {"APPLE", "PERSONAL LASER", "1.00", ""}

// Default block size
#define DEFAULT_BLOCKSIZE 512
Expand All @@ -126,6 +128,7 @@
#define APPLE_DRIVEINFO_MAGOPT {"MOST", "RMD-5200", PLATFORM_REVISION, "1.0"}
#define APPLE_DRIVEINFO_NETWORK {"Dayna", "SCSI/Link", "2.0f", ""}
#define APPLE_DRIVEINFO_TAPE {"BlueSCSI", "APPLE_TAPE", PLATFORM_REVISION, ""}
#define APPLE_DRIVEINFO_PRINTER {"APPLE", "PERSONAL LASER", "1.00", ""}

// Default Iomega ZIP drive information
#define IOMEGA_DRIVEINFO_ZIP100 {"IOMEGA", "ZIP 100", "D.13", ""}
Expand Down
41 changes: 38 additions & 3 deletions src/BlueSCSI_disk.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ bool scsiDiskOpenHDDImage(int target_idx, const char *filename, int scsi_lun, in
img.scsiId = target_idx | S2S_CFG_TARGET_ENABLED;
img.sdSectorStart = 0;

if (img.scsiSectors == 0 && type != S2S_CFG_NETWORK && type != S2S_CFG_AMIGAWIFI && !img.file.isFolder())
if (img.scsiSectors == 0 && type != S2S_CFG_NETWORK && type != S2S_CFG_AMIGAWIFI && type != S2S_CFG_PRINTER && !img.file.isFolder())
{
logmsg("---- Error: image file ", filename, " is empty");
img.file.close();
Expand Down Expand Up @@ -607,8 +607,29 @@ bool scsiDiskOpenHDDImage(int target_idx, const char *filename, int scsi_lun, in
logmsg("---- Zip 100 disk (", (int)img.file.size(), " bytes) is not exactly ", ZIP100_DISK_SIZE, " bytes, may not work correctly");
}
}
else if (type == S2S_CFG_PRINTER)
{
logmsg("---- Configuring as printer (LaserWriter IISC)");
img.deviceType = S2S_CFG_PRINTER;
// The PR<id>/ folder doubles as the spool target. If the user
// registered the device with a placeholder file, create the
// folder for them so they have a clear place to look for prints.
char dirpath[16];
snprintf(dirpath, sizeof(dirpath), "/PR%d", target_idx);
if (!SD.exists(dirpath))
{
if (SD.mkdir(dirpath))
{
logmsg("---- Created printer spool folder ", dirpath);
}
else
{
logmsg("---- WARNING: failed to create printer spool folder ", dirpath);
}
}
}

if (type != S2S_CFG_OPTICAL && type != S2S_CFG_NETWORK && type != S2S_CFG_AMIGAWIFI)
if (type != S2S_CFG_OPTICAL && type != S2S_CFG_NETWORK && type != S2S_CFG_AMIGAWIFI && type != S2S_CFG_PRINTER)
{
autoConfigGeometry(img);
}
Expand Down Expand Up @@ -825,7 +846,21 @@ bool scsiDiskFolderIsTapeFolder(FsFile *dir)
dir->getName(filename, sizeof(filename));
// string starts with 'tp', the 3rd character is a SCSI ID, and it has more 3 charters
// e.g. "tp0 - tape 01"
if (strlen(filename) > 3 && strncasecmp("tp", filename, 2) == 0
if (strlen(filename) > 3 && strncasecmp("tp", filename, 2) == 0
&& filename[2] >= '0' && filename[2] - '0' < NUM_SCSIID)
{
return true;
}
return false;
}

bool scsiDiskFolderIsPrinterFolder(FsFile *dir)
{
// A PR<N>/ directory (e.g. "PR4") both registers the printer and acts
// as the spool target. The 3rd character must be a valid SCSI ID digit.
char filename[MAX_FILE_PATH + 1];
dir->getName(filename, sizeof(filename));
if (strlen(filename) >= 3 && strncasecmp("pr", filename, 2) == 0
&& filename[2] >= '0' && filename[2] - '0' < NUM_SCSIID)
{
return true;
Expand Down
5 changes: 5 additions & 0 deletions src/BlueSCSI_disk.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,11 @@ bool scsiDiskFolderContainsCueSheet(FsFile *dir);
// Checks if the directory name is for multi tagged tapes
bool scsiDiskFolderIsTapeFolder(FsFile *dir);

// Checks if the directory name matches the PR<N>/ printer convention
// (e.g. "PR4"). Such a folder both registers a SCSI printer and serves
// as the spool target for captured print jobs.
bool scsiDiskFolderIsPrinterFolder(FsFile *dir);

// Clear the ROM drive header from flash
bool scsiDiskClearRomDrive();
// Program ROM drive and rename image file
Expand Down
Loading