Skip to content

Commit

Permalink
SCSIVideo: Add video mirroring device
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jcs committed Oct 25, 2020
1 parent 0491930 commit 6da9e9f
Show file tree
Hide file tree
Showing 4 changed files with 229 additions and 4 deletions.
18 changes: 14 additions & 4 deletions src/raspberrypi/controllers/scsidev_ctrl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "controllers/scsidev_ctrl.h"
#include "gpiobus.h"
#include "devices/scsi_host_bridge.h"
#include "devices/scsi_video.h"

//===========================================================================
//
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -1610,7 +1613,7 @@ void FASTCALL SCSIDEV::ReceiveNext()
#endif // RASCSI
{
#ifdef RASCSI
int len;
int len, lun;
#endif // RASCSI
BOOL result;
int i;
Expand All @@ -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);
Expand All @@ -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
Expand Down
170 changes: 170 additions & 0 deletions src/raspberrypi/devices/scsi_video.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
//---------------------------------------------------------------------------
//
// SCSI Video Mirror for Raspberry Pi
//
// Copyright (C) 2020 joshua stein <jcs@jcs.org>
//
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
//---------------------------------------------------------------------------

#include "scsi_video.h"

#include <err.h>
#include <fcntl.h>
#include <linux/fb.h>

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;
}
37 changes: 37 additions & 0 deletions src/raspberrypi/devices/scsi_video.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//---------------------------------------------------------------------------
//
// SCSI Video Mirror for Raspberry Pi
//
// Copyright (C) 2020 joshua stein <jcs@jcs.org>
//
// 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;
};
8 changes: 8 additions & 0 deletions src/raspberrypi/rascsi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit 6da9e9f

Please sign in to comment.