diff --git a/devices/serial_port.go b/devices/serial_port.go index d5a1e82..b060e1d 100644 --- a/devices/serial_port.go +++ b/devices/serial_port.go @@ -1,8 +1,9 @@ package devices import ( + "bytes" "fmt" - "os" + "io" "github.com/maxfierke/gogo-gb/mem" ) @@ -74,14 +75,27 @@ func (sc *SerialCtrl) SetClockInternal(enabled bool) { } type SerialPort struct { - clk uint - ctrl SerialCtrl - recv byte - buf byte + clk uint + ctrl SerialCtrl + recv byte + buf byte + reader io.Reader + writer io.Writer } func NewSerialPort() *SerialPort { - return &SerialPort{} + return &SerialPort{ + reader: bytes.NewReader([]byte{}), + writer: io.Discard, + } +} + +func (sp *SerialPort) SetReader(reader io.Reader) { + sp.reader = reader +} + +func (sp *SerialPort) SetWriter(writer io.Writer) { + sp.writer = writer } func (sp *SerialPort) Step(cycles uint, ic *InterruptController) { @@ -97,9 +111,9 @@ func (sp *SerialPort) Step(cycles uint, ic *InterruptController) { } else { sp.clk -= cycles } - } else { - // TODO: Implement external clock } + + // TODO: Implement external clock } func (sp *SerialPort) OnRead(mmu *mem.MMU, addr uint16) mem.MemRead { @@ -120,14 +134,17 @@ func (sp *SerialPort) OnWrite(mmu *mem.MMU, addr uint16, value byte) mem.MemWrit sp.ctrl.Write(value) if sp.ctrl.IsTransferEnabled() && sp.ctrl.IsClockInternal() { - // TODO: derive this somehow and factor in GBC speeds when relevant + // TODO(GBC): derive this somehow and factor in GBC speeds when relevant sp.clk = 8192 - // TODO: Write this to some IO writer - os.Stderr.Write([]byte{sp.buf}) + sp.writer.Write([]byte{sp.buf}) - // TODO: Read this from somewhere - sp.recv = 0xFF + readBuf := make([]byte, 1) + if bytesRead, err := sp.reader.Read(readBuf); err != nil || bytesRead == 0 { + sp.recv = 0xFF + } else { + sp.recv = readBuf[0] + } } return mem.WriteBlock() diff --git a/hardware/dmg.go b/hardware/dmg.go index f1b5260..635a226 100644 --- a/hardware/dmg.go +++ b/hardware/dmg.go @@ -1,6 +1,7 @@ package hardware import ( + "io" "log" "github.com/maxfierke/gogo-gb/cart" @@ -19,6 +20,7 @@ type DMG struct { cartridge *cart.Cartridge ic *devices.InterruptController lcd *devices.LCD + serial *devices.SerialPort // Non-components debugger debug.Debugger @@ -62,6 +64,7 @@ func NewDMGDebug(debugger debug.Debugger) (*DMG, error) { debugger: debugger, ic: ic, lcd: lcd, + serial: serial, }, nil } @@ -96,3 +99,11 @@ func (dmg *DMG) Run() { func (dmg *DMG) SetLogger(logger *log.Logger) { dmg.logger = logger } + +func (dmg *DMG) SetSerialReader(serial io.Reader) { + dmg.serial.SetReader(serial) +} + +func (dmg *DMG) SetSerialWriter(serial io.Writer) { + dmg.serial.SetWriter(serial) +} diff --git a/main.go b/main.go index 5ad350b..83bab70 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ type CLIOptions struct { debugPrint string logPath string logger *log.Logger + serialPort string } const LOG_PREFIX = "" @@ -50,6 +51,7 @@ func main() { func parseOptions(options *CLIOptions) { flag.StringVar(&options.cartPath, "cart", "", "Path to cartridge file (.gb, .gbc)") + flag.StringVar(&options.serialPort, "serial-port", "", "Path to serial port IO (could be a file, UNIX socket, etc.)") flag.StringVar(&options.debugger, "debugger", "none", "Specify debugger to use (\"none\", \"gameboy-doctor\")") flag.StringVar(&options.debugPrint, "debug-print", "", "Print out something for debugging purposes (\"cart-header\", \"opcodes\")") flag.StringVar(&options.logPath, "log", "", "Path to log file. Default/empty implies stdout") @@ -110,6 +112,22 @@ func initDMG(options *CLIOptions) *hardware.DMG { dmg.SetLogger(logger) + if options.serialPort != "" { + if options.serialPort == "stdout" || options.serialPort == "/dev/stdout" { + dmg.SetSerialWriter(os.Stdout) + } else if options.serialPort == "stderr" || options.serialPort == "/dev/stderr" { + dmg.SetSerialWriter(os.Stderr) + } else { + serialPort, err := os.Create(options.serialPort) + if err != nil { + logger.Fatalf("Unable to open file '%s' as serial port: %v\n", options.serialPort, err) + } + + dmg.SetSerialReader(serialPort) + dmg.SetSerialWriter(serialPort) + } + } + return dmg }