From 6da9e9f3ffcd38eb89413cd445f7407739c54bca Mon Sep 17 00:00:00 2001 From: joshua stein Date: Sat, 24 Oct 2020 23:56:40 -0500 Subject: [PATCH] SCSIVideo: Add video mirroring device Receives raw 1bpp screen data as a WRITE(10) command and displays it on the Raspberry Pi's framebuffer. This is setup for reading and displaying a compact Mac's 1bpp 512x342 display, but the depth and screen size should probably come from a SCSI message sent by the remote end during a setup phase. Some high-resolution logging shows it taking about 83 milliseconds to receive the 21888 bytes of data through SCSI, and about 3 milliseconds to convert it and draw it on the framebuffer. --- src/raspberrypi/controllers/scsidev_ctrl.cpp | 18 +- src/raspberrypi/devices/scsi_video.cpp | 170 +++++++++++++++++++ src/raspberrypi/devices/scsi_video.h | 37 ++++ src/raspberrypi/rascsi.cpp | 8 + 4 files changed, 229 insertions(+), 4 deletions(-) create mode 100644 src/raspberrypi/devices/scsi_video.cpp create mode 100644 src/raspberrypi/devices/scsi_video.h diff --git a/src/raspberrypi/controllers/scsidev_ctrl.cpp b/src/raspberrypi/controllers/scsidev_ctrl.cpp index db237096d3..f498bb087c 100644 --- a/src/raspberrypi/controllers/scsidev_ctrl.cpp +++ b/src/raspberrypi/controllers/scsidev_ctrl.cpp @@ -16,6 +16,7 @@ #include "controllers/scsidev_ctrl.h" #include "gpiobus.h" #include "devices/scsi_host_bridge.h" +#include "devices/scsi_video.h" //=========================================================================== // @@ -877,7 +878,8 @@ void FASTCALL SCSIDEV::CmdWrite10() } // Receive message with host bridge - if (ctrl.unit[lun]->GetID() == MAKEID('S', 'C', 'B', 'R')) { + if (ctrl.unit[lun]->GetID() == MAKEID('S', 'C', 'B', 'R') || + ctrl.unit[lun]->GetID() == MAKEID('S', 'C', 'V', 'D')) { CmdSendMessage10(); return; } @@ -1350,8 +1352,9 @@ void FASTCALL SCSIDEV::CmdSendMessage10() return; } - // Error if not a host bridge - if (ctrl.unit[lun]->GetID() != MAKEID('S', 'C', 'B', 'R')) { + // Error if not a host bridge or video device + if (ctrl.unit[lun]->GetID() != MAKEID('S', 'C', 'B', 'R') && + ctrl.unit[lun]->GetID() != MAKEID('S', 'C', 'V', 'D')) { Error(); return; } @@ -1610,7 +1613,7 @@ void FASTCALL SCSIDEV::ReceiveNext() #endif // RASCSI { #ifdef RASCSI - int len; + int len, lun; #endif // RASCSI BOOL result; int i; @@ -1625,6 +1628,8 @@ void FASTCALL SCSIDEV::ReceiveNext() #ifdef RASCSI // Length != 0 if received if (ctrl.length != 0) { + lun = (ctrl.cmd[1] >> 5) & 0x07; + // Receive len = ctrl.bus->ReceiveHandShake( &ctrl.buffer[ctrl.offset], ctrl.length); @@ -1638,6 +1643,11 @@ void FASTCALL SCSIDEV::ReceiveNext() // Offset and Length ctrl.offset += ctrl.length; ctrl.length = 0;; + + if (ctrl.unit[lun]->GetID() == MAKEID('S', 'C', 'V', 'D')) { + ((SCSIVideo*)ctrl.unit[lun])->ReceiveBuffer(len, ctrl.buffer); + } + return; } #else diff --git a/src/raspberrypi/devices/scsi_video.cpp b/src/raspberrypi/devices/scsi_video.cpp new file mode 100644 index 0000000000..0297af31e4 --- /dev/null +++ b/src/raspberrypi/devices/scsi_video.cpp @@ -0,0 +1,170 @@ +//--------------------------------------------------------------------------- +// +// SCSI Video Mirror for Raspberry Pi +// +// Copyright (C) 2020 joshua stein +// +// Licensed under the BSD 3-Clause License. +// See LICENSE file in the project root folder. +// +//--------------------------------------------------------------------------- + +#include "scsi_video.h" + +#include +#include +#include + +static unsigned char reverse_table[256]; + +SCSIVideo::SCSIVideo() : Disk() +{ + struct fb_var_screeninfo fbinfo; + struct fb_fix_screeninfo fbfixinfo; + + disk.id = MAKEID('S', 'C', 'V', 'D'); + + // create lookup table + for (int i = 0; i < 256; i++) { + unsigned char b = i; + b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; + b = (b & 0xCC) >> 2 | (b & 0x33) << 2; + b = (b & 0xAA) >> 1 | (b & 0x55) << 1; + reverse_table[i] = b; + } + + // TODO: receive these through a SCSI message sent by the remote + this->screen_width = 512; + this->screen_height = 342; + + this->fbfd = open("/dev/fb0", O_RDWR); + if (this->fbfd == -1) + err(1, "open /dev/fb0"); + + if (ioctl(this->fbfd, FBIOGET_VSCREENINFO, &fbinfo)) + err(1, "ioctl FBIOGET_VSCREENINFO"); + + if (ioctl(this->fbfd, FBIOGET_FSCREENINFO, &fbfixinfo)) + err(1, "ioctl FBIOGET_FSCREENINFO"); + + if (fbinfo.bits_per_pixel != 32) + errx(1, "TODO: support %d bpp", fbinfo.bits_per_pixel); + + this->fbwidth = fbinfo.xres; + this->fbheight = fbinfo.yres; + this->fbbpp = fbinfo.bits_per_pixel; + this->fblinelen = fbfixinfo.line_length; + this->fbsize = fbfixinfo.smem_len; + + printf("SCSIVideo drawing on %dx%d %d bpp framebuffer\n", + this->fbwidth, this->fbheight, this->fbbpp); + + this->fb = (char *)mmap(0, this->fbsize, PROT_READ | PROT_WRITE, MAP_SHARED, + this->fbfd, 0); + if ((int)this->fb == -1) + err(1, "mmap"); + + memset(this->fb, 0, this->fbsize); +} + +//--------------------------------------------------------------------------- +// +// Destructor +// +//--------------------------------------------------------------------------- +SCSIVideo::~SCSIVideo() +{ + munmap(this->fb, this->fbsize); + close(this->fbfd); +} + +//--------------------------------------------------------------------------- +// +// INQUIRY +// +//--------------------------------------------------------------------------- +int FASTCALL SCSIVideo::Inquiry( + const DWORD *cdb, BYTE *buf, DWORD major, DWORD minor) +{ + int size; + + ASSERT(this); + ASSERT(cdb); + ASSERT(buf); + ASSERT(cdb[0] == 0x12); + + // EVPD check + if (cdb[1] & 0x01) { + disk.code = DISK_INVALIDCDB; + return FALSE; + } + + // Basic data + // buf[0] ... Communication Device + // buf[2] ... SCSI-2 compliant command system + // buf[3] ... SCSI-2 compliant Inquiry response + // buf[4] ... Inquiry additional data + memset(buf, 0, 8); + buf[0] = 0x09; + + // SCSI-2 p.104 4.4.3 Incorrect logical unit handling + if (((cdb[1] >> 5) & 0x07) != disk.lun) { + buf[0] = 0x7f; + } + + buf[2] = 0x02; + buf[3] = 0x02; + buf[4] = 36 - 5 + 8; // required + 8 byte extension + + // Fill with blanks + memset(&buf[8], 0x20, buf[4] - 3); + + // Vendor name + memcpy(&buf[8], "jcs", 3); + + // Product name + memcpy(&buf[16], "video mirror", 12); + + // Revision + memcpy(&buf[32], "1337", 4); + + // Optional function valid flag + buf[36] = '0'; + + // Size of data that can be returned + size = (buf[4] + 5); + + // Limit if the other buffer is small + if (size > (int)cdb[4]) { + size = (int)cdb[4]; + } + + // Success + disk.code = DISK_NOERROR; + return size; +} + +BOOL FASTCALL SCSIVideo::ReceiveBuffer(int len, BYTE *buffer) +{ + int row = 0; + int col = 0; + + for (int i = 0; i < len; i++) { + unsigned char j = reverse_table[buffer[i]]; + + for (int bit = 0; bit < 8; bit++) { + int loc = (col * (this->fbbpp / 8)) + (row * this->fblinelen); + col++; + if (col % this->screen_width == 0) { + col = 0; + row++; + } + + *(this->fb + loc) = (j & (1 << bit)) ? 0 : 255; + *(this->fb + loc + 1) = (j & (1 << bit)) ? 0 : 255; + *(this->fb + loc + 2) = (j & (1 << bit)) ? 0 : 255; + } + } + + return TRUE; +} diff --git a/src/raspberrypi/devices/scsi_video.h b/src/raspberrypi/devices/scsi_video.h new file mode 100644 index 0000000000..fe2cfc62e5 --- /dev/null +++ b/src/raspberrypi/devices/scsi_video.h @@ -0,0 +1,37 @@ +//--------------------------------------------------------------------------- +// +// SCSI Video Mirror for Raspberry Pi +// +// Copyright (C) 2020 joshua stein +// +// Licensed under the BSD 3-Clause License. +// See LICENSE file in the project root folder. +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "os.h" +#include "disk.h" + +class SCSIVideo : public Disk +{ +public: + SCSIVideo(); + virtual ~SCSIVideo(); + + int FASTCALL Inquiry(const DWORD *cdb, BYTE *buf, DWORD major, DWORD minor); + BOOL FASTCALL ReceiveBuffer(int len, BYTE *buffer); + +private: + int screen_width; + int screen_height; + + int fbfd; + char *fb; + int fbwidth; + int fbheight; + int fblinelen; + int fbsize; + int fbbpp; +}; diff --git a/src/raspberrypi/rascsi.cpp b/src/raspberrypi/rascsi.cpp index baf6c34ae3..cfe0365858 100644 --- a/src/raspberrypi/rascsi.cpp +++ b/src/raspberrypi/rascsi.cpp @@ -21,6 +21,7 @@ #include "devices/scsicd.h" #include "devices/scsimo.h" #include "devices/scsi_host_bridge.h" +#include "devices/scsi_video.h" #include "controllers/scsidev_ctrl.h" #include "controllers/sasidev_ctrl.h" #include "gpiobus.h" @@ -296,6 +297,8 @@ void ListDevice(FILE *fp) // mount status output if (pUnit->GetID() == MAKEID('S', 'C', 'B', 'R')) { FPRT(fp, "%s", "HOST BRIDGE"); + } else if (pUnit->GetID() == MAKEID('S', 'C', 'V', 'D')) { + FPRT(fp, "%s", "VIDEO MIRROR"); } else { pUnit->GetPath(filepath); FPRT(fp, "%s", @@ -520,6 +523,9 @@ BOOL ProcessCmd(FILE *fp, int id, int un, int cmd, int type, char *file) case 4: // BRIDGE pUnit = new SCSIBR(); break; + case 5: // Video Mirror + pUnit = new SCSIVideo(); + break; default: FPRT(fp, "Error : Invalid device type\n"); return FALSE; @@ -873,6 +879,8 @@ bool ParseArgument(int argc, char* argv[]) type = 3; } else if (xstrcasecmp(path, "bridge") == 0) { type = 4; + } else if (xstrcasecmp(path, "video") == 0) { + type = 5; } else { // Cannot determine the file type fprintf(stderr,