OXYGENHUB
◆ main openmsx / share / scripts / _tcl_bridge.tcl
4.2 KiB · Tcl · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
# tcl_bridge v1.0 (c) 2025 Pedro de Medeiros
2
# There is an example program that consumes this script at:
3
# https://github.com/pvmm/openmsx-tcl-bridge
4
#
5
# To execute a Tcl command from the MSX side, you need to perform the following
6
# steps:
7
#
8
# 1) Make sure the 'tcl_bridge' is active.
9
#
10
# To do this execute the command 'tcl_bridge start' in the console.
11
# The `tcl_bridge' is *intentionally* not active by default.
12
#
13
# 2) The MSX program needs to setup a control block.
14
#
15
# That control block has 6 fields
16
#
17
#   cmd_addr:      +0 (output, 2 bytes): pointer to the command string (plain string, no terminator needed)
18
#   cmd_len:       +2 (output, 2 bytes): length of the command string
19
#   res_addr:      +4 (output, 2 bytes): pointer to the result buffer
20
#   res_max_len:   +6 (output, 2 bytes): length of the result buffer
21
#
22
#   res_real_len:  +8 (input,  2 bytes): actual length of the result
23
#   status:       +10 (input,  1 byte ): success (0) or failure (1)
24
#
25
# Only the first 4 fields need to be filled in by the MSX program, the last 2
26
# fields are filled in by the 'tcl_bridge' (see below).
27
#
28
# 3) Next the MSX program needs to send the address (2 bytes) of this control
29
# block to I/O port '6'. This will trigger the 'tcl_bridge'.
30
#
31
# 4) The 'tcl_bridge' then executes the given Tcl command (located at 'cmd_addr').
32
#
33
# If the command was executed successful, a '0' is written to the 'status' field
34
# in the control block'. If there was an error a '1' is written.
35
#
36
# The result string (successful result or the error message) is written into the
37
# result buffer located at 'res_addr'.
38
#
39
# The 'tcl_bridge' only writes at most 'res_max_len' bytes into that buffer. So
40
# if the result buffer is too small the result gets truncated. The result is
41
# written WITHOUT terminator characters. The full (non-truncated) length is
42
# written to the 'res_real_len' field of the control block.
43
#
44
# 5) The MSX program should then check the result.
45
#
46
# It should check success / failure by examining the 'status' field. Possibly
47
# check for overflow by comparing the actual length (field 'res_real_len') with
48
# the given buffer length (field 'res_max_len').
49
#
50
# If the MSX want to detect whether the 'tcl_bridge' is active, it can e.g.
51
# write 0xff to the 'status' field and then check if it was changed into '0'
52
# or '1'.
53
#
54
namespace eval tcl_bridge {
55
56
variable wp {}
57
variable latch 0
58
variable address
59
60
set_help_text tcl_bridge \
61
{tcl_bridge -- allows guest/host communication through a MSX-to-Tcl bridge.
62
63
Usage:
64
  tcl_bridge start      Create communication channel
65
  tcl_bridge stop       Destroy communication channel
66
}
67
68
proc tcl_bridge {cmd} {
69
    switch -- $cmd {
70
        start   { tcl_bridge::start }
71
        stop    { tcl_bridge::stop }
72
        default { error "Unknown sub-command \"$cmd\"." }
73
    }
74
}
75
76
proc start {} {
77
    variable wp
78
    if {$wp eq {}} {
79
        set wp [debug watchpoint create -type write_io -address 6 -command {tcl_bridge::read_command $::wp_last_value}]
80
    }
81
}
82
83
proc stop {} {
84
    variable wp
85
    if {$wp ne {}} {
86
        debug watchpoint remove $wp
87
        set wp {}
88
    }
89
}
90
91
proc min {x y} {
92
    expr {$x < $y ? $x : $y}
93
}
94
95
proc execute_command {base} {
96
    set cmd_addr     [peek16 $base]
97
    set cmd_len      [peek16 [expr {$base + 2}]]
98
    set res_addr     [peek16 [expr {$base + 4}]]
99
    set res_max_len  [peek16 [expr {$base + 6}]]
100
    set res_real_len [expr {$base + 8}]
101
    set status       [expr {$base + 10}]
102
    set cmd [debug read_block memory $cmd_addr $cmd_len]
103
    # execute command
104
    set response [catch {set output [eval $cmd]} output]
105
    # write command result and output data
106
    debug write_block memory $res_real_len [binary format s [min [string length $output] 0xFFFF]]
107
    debug write_block memory $res_addr [string range $output 0 [min [string length $output] $res_max_len]-1]]
108
    debug write memory $status $response
109
}
110
111
proc read_command {byte} {
112
    variable latch
113
    variable address
114
    switch -- $latch {
115
        0 {
116
            set address $byte
117
            set latch 1
118
        }
119
        1 {
120
            incr address [expr {$byte << 8}]
121
            set latch 0
122
            execute_command $address
123
        }
124
    }
125
}
126
127
namespace export tcl_bridge
128
129
} ;# namespace tcl_bridge
130
131
namespace import tcl_bridge::*