pydw

Check-in [cba7297ed1]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Initial project commit
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | master | trunk
Files: files | file ages | folders
SHA3-256:cba7297ed1e4a64ecf70bb4324e5bb636eeab214fdfbfbd62bfdca9c20346580
User & Date: ajv-899-334-8894@vsta.org 2016-07-13 03:14:02
Context
2016-07-15
22:41
Cleanup (close writable disks) and exit on interrupt check-in: 93c183e96a user: ajv-899-334-8894@vsta.org tags: master, trunk
2016-07-13
03:14
Initial project commit check-in: cba7297ed1 user: ajv-899-334-8894@vsta.org tags: master, trunk
03:09
Initial commit check-in: e13a48ba00 user: ajv-900-660-8060@vsta.org tags: master, trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Added .gitignore.





>
>
1
2
*.pyc
*.dsk

Changes to README.md.

1
2











# pydw
Python server for Coco3 Drivewire protocol













>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
# pydw
Python server for Coco3 Drivewire protocol

Launch it as:

python pydw.py d0=firstDisk.dsk d1=secondDisk.dsk

If you pass r0=firstDisk.dsk, it'll be a disk, but return
errors if you try to write to it.

Time support is tested and working.

TTY support has been sketched out, but not brought up.

Added disk.py.

















































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#
# disk.py
#	Disk I/O operations
#

# Map from drive # to (rd-only?, file-handle)
Drives = {}

# Calculate simple checksum
def cksum(buf):
    res = 0
    for c in buf:
	res += ord(c)
    return res & 0xFFFF

# Decode common I/O args
def ioargs(conn):
    global Drives

    args = conn.recv(4)
    dnum = ord(args[0])
    lsn2 = ord(args[1])
    lsn1 = ord(args[2])
    lsn0 = ord(args[3])
    lsn = (lsn2 << 16) | (lsn1 << 8) | lsn0
    print "  drive", dnum, "sector", lsn
    tup = Drives.get(dnum)
    if tup is None:
	# No such drive
	print " DW bad drive"
	conn.send(b'\xF6')
	return None
    return (dnum,lsn,tup)

# Read operation (DW code 0x52)
def read(conn):
    # Get drive & sector # & I/O args
    tup = ioargs(conn)
    if tup is None:
	return
    dnum,lsn,(_ignore,f) = tup

    # Move to this position
    f.seek(lsn * 256)

    # Get the data
    buf = f.recv(256)
    if len(buf) != 256:
	print " DW read error"
	conn.send(b'\xF4')
	return

    # Give it back
    conn.send(b'\x00')
    # Checksum, big endian
    val = cksum(buf)
    conn.send(chr((val >> 8) & 0xFF))
    conn.send(chr(val & 0xFF))
    conn.send(buf)

# Read two bytes as a checksum (big endial)
def readck(conn):
    ck1 = conn.recv(1)
    ck0 = conn.recv(1)
    val = (ord(ck1) << 8) | ord(ck0)
    return val

# Write sector
def write(conn):
    # Get drive & sector # & I/O args
    tup = ioargs(conn)
    if tup is None:
	return

    # Read-only?
    dnum,lsn,(rdonly,f) = tup
    if rdonly:
	print " DW write to read only"
	conn.send(b'\xF5')
	return

    # Get the data to write
    buf = conn.recv(256)
    val = readck(conn)
    bufck = cksum(buf)
    if val != bufck:
	print " DW write bad cksum", val, bufck
	conn.send(b'\xF3')
	return

    # Move to this position & write
    f.seek(lsn * 256)
    f.write(buf)

    # Ok
    conn.send(b'\x00')

# Read, extended
def readx(conn):
    # Drive/sector/IO
    tup = ioargs(conn)
    if tup is None:
	return

    dnum,lsn,(_ignore,f) = tup

    # Move to this position
    f.seek(lsn * 256)

    # Get the data
    buf = f.read(256)
    if len(buf) != 256:
	print " DW readx error"
	iserror = True
	buf = b'\x00'*256
    else:
	iserror = False

    # Write it (even if it's nulls from an error)
    conn.send(buf)

    # Let the Coco tell us what it saw as the checksum
    val = readck(conn)

    # If we had good data, check for OK
    if not iserror:
	if val == cksum(buf):
	    conn.send(b'\x00')
	else:
	    print " DW readx cksum mismatch"
	    conn.send(b'\xF3')

    # We had an I/O error, tell them
    else:
	conn.send(b'\xF4')

Added dw.py.





































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
#
# dw.py
#	DriveWire server
#
# The Coco world has a "drivewire" RS-232 protocol for serving
#  storage (and, later, date/time, terminals, debugging, ...).
# Later, this protocol was implemented in emulators, and talks
#  over TCP instead of RS-232.
# This Python utility talks that DriveWire-over-TCP protocol
#  to serve disk images along with terminals.
import sys, socket, time
import disk, tty

# Default port number
Pnum = 65504

# Ops not to log
Noisy = frozenset([0x43])

# Wrapper around network I/O
class NetIO(object):
    def __init__(self, conn):
	self.conn = conn

    # Receive, waiting if needed
    def recv(self, count):
	res = ""
	while count > 0:
	    buf = self.conn.recv(count)
	    res += buf
	    count -= len(buf)
	    if count > 0:
		# Hold off a bit for client to fill pipe
		# time.sleep(0.1)
		pass

	return res

    # Write bytes
    def send(self, buf):
	self.conn.send(buf)

def usage():
    sys.stderr.write("Usage is: %s [port=<num>] [d<num>=<file>]" \
	" [r<num>=<file>]\n" % (sys.argv[0],))
    sys.exit(1)

def startup():
    global Pnum

    # Need at least *something*
    if len(sys.argv) < 2:
	usage()

    # Walk args
    for arg in sys.argv[1:]:

	# Not terribly UNIX-like; "thing=value" pairs
	tup = arg.split('=')
	if len(tup) != 2:
	    usage()
	k,v = tup

	# Port # override?
	if k == "port":
	    try:
		Pnum = int(sys.argv[1])
		if (Pnum < 1024) or (Pnum > 65535):
		    usage()
	    except:
		usage()
	    continue

	# Drive assignment
	# dX=<file>
	# rX=<file> (read-only)
	if k[0] in ('d', 'r'):
	    # Read-only
	    rdonly = k[0] == 'r'

	    # Get drive #
	    try:
		dnum = int(k[1:])
	    except:
		usage()
	    if (dnum < 0) or (dnum > 255):
		usage()
	    if dnum in disk.Drives:
		sys.stderr.write("Drive %d specified twice\n" % (dnum,))
		sys.exit(1)

	    # Get file access
	    try:
		dfile = open(v, "r" if rdonly else "r+")
	    except:
		sys.stderr.write("Can't open '%s'\n" % (v,))
		sys.exit(1)

	    # Register drive & mode
	    disk.Drives[dnum] = (rdonly, dfile)

	    continue

	usage()

    print "DriveWire server, port", Pnum, "with", \
	len(disk.Drives), "drives"

# DW Init
def dwinit(conn):
    # Read their "abilities" byte; send it right back
    arg0 = conn.recv(1)
    print " DW abilities", arg0

    # Just echo back what they ask?
    # This kinda means we do DW4?
    conn.send(arg0)

# No-op
def nop(conn):
    return

# Time
def gettime(conn):
    print " DW time"
    tm = time.localtime()
    res = chr(tm.tm_year - 1900) + chr(tm.tm_mon) + \
	chr(tm.tm_mday) + chr(tm.tm_hour) + \
	chr(tm.tm_min) + chr(tm.tm_sec)
    conn.send(res)

# Map from opcode to logging string and function
Ops = {
    0x00: ("nop", nop),
    0xFF: ("reset", nop),
    0xFE: ("reset", nop),
    0xF8: ("reset", nop),
    0x49: ("init", nop),
    0x54: ("terminate", nop),
    0x5A: ("dwinit", dwinit),
    0x23: ("time", gettime),
    0x52: ("read", disk.read),
    0x72: ("re-read", disk.read),
    0x57: ("write", disk.write),
    0x77: ("re-write", disk.write),
    0xD2: ("readx", disk.readx),
    0xF2: ("re-readx", disk.readx),
    0x45: ("serinit", tty.init),
    0x43: ("serread", tty.read),
    0x63: ("serreadm", tty.readm),
    0xC3: ("serrite", tty.write),
}

# A DriveWire client has connected
def serve(conn):
    global Noise, Ops

    while True:

	# Get op
	b = conn.recv(1)
	op = ord(b)

	# Special case; hit bit on, low bits are channel #
	if (op & 0x80) and ((op & 0x7F) < 48):
	    tty.fwrite(conn, op & 0x7F)
	    continue

	# Get dispatch
	tup = Ops.get(op)
	if tup is None:
	    print " DW unknown op", op
	    continue

	# Log it?
	if op not in Noisy:
	    print " DW", tup[0]

	# Run it
	tup[1](conn)


if __name__ == "__main__":

    # Parse args, open drive files
    startup()

    # Set up the network
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind( ("", Pnum) )
    s.listen(5)

    # Bring it
    while True:
	conn,tup = s.accept()
	print "DW client", tup

	# Talk to them
	# (TBD if we need multiple threads of service.)
	serve(NetIO(conn))
	try:
	    # serve(conn)
	    pass
	except:
	    print "DW connection abort"
	    try:
		conn.close()
	    except:
		pass

Added tty.py.



































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#
# tty.py
#	Virtual serial ports
#

# State for any activated TTY
#
# chan -- Integer, channel we're serving
# writeq -- String of bytes to be sent
class IOQ(object):
    def __init__(self, chan):
	self.chan = chan
	self.writeq = ""

# Current TTY we're typing into
curIN = None

# Current TTY with output
curOUT = None

# Initialise a TTY
def init(conn):
    chan = ord(conn.recv(1))
    print "  init", chan

# Read/poll a TTY
def read(conn):
    # TBD, actual input from typing
    res = b"\x00" + b"\x00"
    conn.send(res)

# Read bytes
def readm(conn):
    # TBD, typing
    return

# Writing output, encoded channel
def fwrite(conn, chan):
    global curOUT

    if chan != curOUT:
	print "  output", chan
	curOUT = chan
    sys.stdout.write(conn.recv(1))

# Writing output
def write(conn):
    chan = ord(conn.recv(1))
    fwrite(conn, chan)