Index: ChangeLog.md
==================================================================
--- ChangeLog.md
+++ ChangeLog.md
@@ -1,6 +1,55 @@
# PiDP-8/I Changes
+
+
+## Version 2020.04.xx
+
+* Raspberry Pi 4 support.
+
+* [Configurable screen manager][rmsm], allowing either tmux or
+ "none" as an alternative to GNU screen. Initial work on this
+ feature done by Ryan Finnie.
+
+* Added udev rules to allow mounting media from disks on USB
+ floppy drives. (Thanks to Ryan Finnie for this feature.)
+
+* Updated SIMH. The primary user-visible changes from the perspective
+ of a PiDP-8/I user are:
+
+ * Better IPS rate calibration for the PDP-8 simulator when
+ throttling. The simulator now does a precalibration pass to
+ achieve a good initial guess at the host's IPS rate rather
+ than drop sharply into a calibrated level some seconds past
+ the simulator startup time, as in the prior release.
+
+ * Improvements to SCP, the command shell / script interpreter:
+
+ * Add the `RENAME/MOVE/MV`, `MKDIR`, and `RMDIR` commands.
+
+ * The `SAVE` command can now overwrite existing files.
+
+ * Several improvements to power-of-2 unit handling in command
+ output and parameter input.
+
+ * Regular expressions in SIMH `EXPECT` commands now use
+ PCRE syntax if available instead of the POSIX regex
+ library.
+
+ * Many improvements to tape device handling. (Nothing PDP-8
+ specific, just generic SIMH improvements.)
+
+ * Portability and documentation improvements.
+
+* The build system now detects the availability of Python 3 and
+ prefers it if available.
+
+* Updated Autosetup to v0.6.9+. Allows it to work under Tcl 8.7.
+
+* Portability and documentation improvements.
+
+[rmsm]: https://tangentsoft.com/pidp8i/doc/trunk/README.md#rc-screen-manager
+
## Version 2019.04.25 — The "OS/8 V3F and os8-run" release
* The banner feature in this release is that Bill Cattey transformed
Index: HACKERS.md
==================================================================
--- HACKERS.md
+++ HACKERS.md
@@ -12,11 +12,11 @@
The PiDP-8/I software project is hosted using the [Fossil][fossil]
[distributed version control system][dvcs], which provides most of the
features of GitHub without [the complexities of Git][fvg].
Those new to Fossil should at least read its [Quick Start Guide][fqsg].
-If you want to go deeper, the [the Schimpf book][fbook] is somewhat
+If you want to go deeper, [the Schimpf book][fbook] is somewhat
outdated, but it is still the best single coherent tutorial on Fossil.
[The official Fossil docs][fdoc] are much more up to date, but they take
a piecemeal approach to topics, rather than the linear tutorial approach
of a book, so it is not my first recommendation for learning Fossil.
Those docs are better for polishing your skills and for reference after
@@ -68,12 +68,12 @@
$ cd ~/src/pidp8i/trunk
$ fossil open ~/museum/pidp8i.fossil
The `clone` command gets you a file called `pidp8i.fossil` containing
the full history of the PiDP-8/I software project from the upstream
-2015.12.15 release onward. You can call that clone file anything you
-like and put it in any directory you like. Even the `.fossil` extension
+2015.12.15 release onward. You can call that clone file anything you
+like and put it in any directory you like. Even the `.fossil` extension
is largely a convention, not a requirement.
You only need to clone the repository once per machine. Thereafter, you
will just be working with that same clone.
@@ -85,27 +85,27 @@
If you have a developer account on the `tangentsoft.com/pidp8i` Fossil
instance, just add your username to the URL like so:
$ fossil clone https://USERNAME@tangentsoft.com/pidp8i pidp8i.fossil
-If you've already cloned anonymously, simply tell Fossil about the new
+If you’ve already cloned anonymously, simply tell Fossil about the new
sync URL instead:
$ cd ~/src/pidp8i/trunk
$ fossil sync https://USERNAME@tangentsoft.com/pidp8i
Either way, Fossil will ask you for the password for `USERNAME` on the
-remote Fossil instance, and it will offer to remember it for you. If
+remote Fossil instance, and it will offer to remember it for you. If
you let it remember the password, operation from then on is scarcely
different from working with an anonymous clone, except that on checkin,
your changes will be sync’d back to the repository on tangentsoft.com if
-you're online at the time, and you'll get credit under your developer
+you’re online at the time, and you’ll get credit under your developer
account name for the checkin.
-If you're working offline, Fossil will still do the checkin locally, and
+If you’re working offline, Fossil will still do the checkin locally, and
it will sync up with the central repository after you get back online.
-It is best to work on a branch when unable to use Fossil's autosync
+It is best to work on a branch when unable to use Fossil’s autosync
feature, as you are less likely to have a sync conflict when attempting
to send a new branch to the central server than in attempting to merge
your changes to the tip of trunk into the current upstream trunk, which
may well have changed since you went offline.
@@ -112,14 +112,14 @@
You can purposely work offline by disabling autosync mode:
$ fossil set autosync 0
Until you re-enable it (`autosync 1`) Fossil will stop trying to sync
-your local changes back to the central repo. In this mode, Fossil works
-more like Git's default mode, buying you many of the same problems that
-go along with that working style. I recommend disabling autosync mode
-only when you are truly going to be offline and don't want Fossil
+your local changes back to the central repo. In this mode, Fossil works
+more like Git’s default mode, buying you many of the same problems that
+go along with that working style. I recommend disabling autosync mode
+only when you are truly going to be offline and don’t want Fossil
attempting to sync when you know it will fail.
Getting Developer Access
@@ -137,15 +137,15 @@
The directory structure shown in the commands above is more complicated
than strictly necessary, but it has a number of nice properties.
First, it collects other software projects under a common top-level
-directory, which I'm calling `~/src`, but you are free to use any scheme
+directory, which I’m calling `~/src`, but you are free to use any scheme
you like.
Second, the project directory (`~/src/pidp8i`) stores multiple separate
-checkouts, one for each version I'm actively working with at the moment.
+checkouts, one for each version I’m actively working with at the moment.
So, to add a few other checkouts, you could say:
$ cd ~/src/pidp8i
$ mkdir -p release # another branch
$ mkdir -p v20151215 # a tag this time, not a branch
@@ -172,12 +172,12 @@
When you say `fossil update` in a check-out directory, you get the “tip”
state of that version’s branch. This means that if you created your
“`release`” check-out while version 2017.01.23 was current and you say
“`fossil update`” today, you’ll get the release version 2019.04.25 or
-later. But, since the `v20151215` tag was made on trunk, saying “`fossil
-update`” in that check-out directory will fast-forward you to the tip of
+later. But, since the `v20151215` tag was made on trunk, saying
+“`fossil update`” in that check-out directory will fast-forward you to the tip of
trunk; you won’t remain pinned to that old version.
The PiDP-8/I project uses tags for [each released version][tags], and it
has [many working branches][brlist]. You can use any of those names in
“`fossil open`” and “`fossil update`” commands, and you can also use any
@@ -193,12 +193,12 @@
Creating Branches
----
Creating a branch in Fossil is scary-simple, to the point that those
-coming from other version control systems may ask, "Is that really all
-there is to it?" Yes, really, this is it:
+coming from other version control systems may ask, “Is that really all
+there is to it?” Yes, really, this is it:
$ fossil ci --branch new-branch-name
That is to say, you make your changes as you normally would; then when
you go to check them in, you give the `--branch` option to the
@@ -205,58 +205,57 @@
`ci/checkin` command to put the changes on a new branch, rather than add
them to the same branch the changes were made against.
While developers with login rights to the PiDP-8/I Fossil instance are
allowed to check in on the trunk at any time, we recommend using
-branches whenever you're working on something experimental, or where you
-can't make the necessary changes in a single coherent checkin.
+branches whenever you’re working on something experimental, or where you
+can’t make the necessary changes in a single coherent checkin.
-One of this project's core principles is that `trunk` should always
-build without error, and it should always function correctly. That's an
+One of this project’s core principles is that `trunk` should always
+build without error, and it should always function correctly. That’s an
ideal we have not always achieved, but we do always *try* to achieve it.
Contrast branches, which PiDP-8/I developers may use to isolate work
-until it is ready to merge into the trunk. It is okay to check work in
-on a branch that doesn't work, or doesn't even *build*, so long as the
+until it is ready to merge into the trunk. It is okay to check work in
+on a branch that doesn’t work, or doesn’t even *build*, so long as the
goal is to get it to a point that it does build and work properly before
merging it into trunk.
Here again we have a difference with Git: because Fossil normally syncs
your work back to the central repository, this means we get to see the
-branches you are still working on. This is a *good thing*. Do not fear
-committing broken or otherwise bad code to a branch. [You are not your
-code.][daff] We are software developers, too: we understand that
+branches you are still working on. This is a *good thing*. Do not fear
+committing broken or otherwise bad code to a branch. [You are not your
+code.][daff] We are software developers, too: we understand that
software development is an iterative process, and that not all ideas
spring forth perfect and production-ready from the fingers of its
-developers. These public branches let your collaborators see what
-you're up to, and maybe lend advice or a hand in the work; mostly,
-public branches let your collaborators see what you're up to, so they're
-not surprised when the change finally lands in trunk.
+developers. These public branches let your collaborators see what
+you’re up to; they may be able to lend advice, to help with the work, or
+to at least be unsurprised when your change finally lands in trunk.
This is part of what I mean about Fossil fostering close cooperation
rather than fostering wild tangents.
Jim McCarthy (author of [Dynamics of Software Development][dosd]) has a
presentation on YouTube that touches on this topic at a couple of
points:
-* [Don't go dark](https://www.youtube.com/watch?v=9OJ9hplU8XA)
-* [Beware of a guy in a room](https://www.youtube.com/watch?v=oY6BCHqEbyc)
+* [Don’t go dark](https://www.youtube.com/watch?v=9OJ9hplU8XA)
+* [Beware of a guy in a room](https://www.youtube.com/watch?v=oY6BCHqEbyc)
-Fossil's sync-by-default behavior fights these negative tendencies.
+Fossil’s sync-by-default behavior fights these negative tendencies.
PiDP-8/I project developers are welcome to create branches at will. The
main rule is to follow the branch naming scheme: all lowercase with
hyphens separating words. See the [available branch list][brlist] for
examples to emulate.
If you have checkin rights on the repository, it is generally fine to
-check things in on someone else's feature branch, as long as you do so
-in a way that cooperates with the purpose of that branch. The same is
+check things in on someone else’s feature branch, as long as you do so
+in a way that cooperates with the purpose of that branch. The same is
true of `trunk`: you should not check something in directly on the trunk
that changes the nature of the software in a major way without
-discussing the idea first. This is yet another use for branches: to
+discussing the idea first. This is yet another use for branches: to
make a possibly-controversial change so that it can be discussed before
being merged into the trunk.
[daff]: http://www.hanselman.com/blog/YouAreNotYourCode.aspx
[dosd]: http://amzn.to/2iEVoBL
@@ -266,47 +265,47 @@
Special Branches
----
Most of the branches in the PiDP-8/I project are feature branches of the
sort described in the previous section: an isolated line of development
-by one or more of the project's developers to work towards some new
+by one or more of the project’s developers to work towards some new
feature, with the goal of merging that feature into the `trunk` branch.
There are a few branches in the project that are special, which are
subject to different rules than other branches:
-* **release
** - One of the steps in the
+* **release
** — One of the steps in the
[release process][relpr] is to merge the stabilized `trunk` into the
`release` branch, from which the release tarballs and binary OS
- images are created. Only the project's release manager — currently
+ images are created. Only the project’s release manager — currently
Warren Young — should make changes to this branch.
* **bogus
** or **BOGUS
** — Because a branch
is basically just a label for a specific checkin, Fossil allows the tip
- of one branch to be "moved" to another branch by applying a branch
- label to that checkin. We use this label when someone makes a
- checkin on the tip of a branch that should be "forgotten." Fossil
+ of one branch to be “moved” to another branch by applying a branch
+ label to that checkin. We use this label when someone makes a
+ checkin on the tip of a branch that should be “forgotten.” Fossil
makes destroying project history very difficult, on purpose, so
- things moved to the "bogus" branch are not actually destroyed;
+ things moved to the “bogus” branch are not actually destroyed;
instead, they are merely moved out of the way so that they do not
- interfere with that branch's normal purpose.
+ interfere with that branch’s normal purpose.
If you find yourself needing to prune the tip of a branch this way,
the simplest way is to do it via the web UI, using the checkin
- description page's "edit" link. You can instead do it from the
+ description page’s “edit” link. You can instead do it from the
command line with the `fossil amend` command.
-[relpr]: https://tangentsoft.com/pidp8i/doc/trunk/doc/RELEASE-PROCESS.md
+[relpr]: https://tangentsoft.com/pidp8i/doc/trunk/doc/RELEASE-PROCESS.md
Developer Discussion Forum
----
-The "[Forum][pfor]" link at the top of the Fossil web interface is for
+The “[Forum][pfor]” link at the top of the Fossil web interface is for
discussing the development of the PiDP-8/I software only. All other
-traffic should go to [the mailing list][ggml] instead. We're not trying
+traffic should go to [the mailing list][ggml] instead. We’re not trying
to split the community by providing a second discussion forum; we just
think many development-related discussions are too low-level to be of
any interest to most of the people on the mailing list.
You can sign up for the forums without having a developer login, and you
@@ -313,17 +312,17 @@
can even post anonymously. If you have a login, you can [sign up for
email alerts][alert] if you like.
Keep in mind that posts to the Fossil forum are treated much the same
way as ticket submissions and wiki articles. They are permanently
-archived with the project. The "edit" feature of Fossil forums just
+archived with the project. The “edit” feature of Fossil forums just
creates a replacement record for a post, but the old post is still
-available in the repository. Don't post anything you wouldn't want made
+available in the repository. Don’t post anything you wouldn’t want made
part of the permanent record of the project!
-[ggml]: https://groups.google.com/forum/#!forum/pidp-8
-[pfor]: https://tangentsoft.com/pidp8i/forum
+[ggml]: https://groups.google.com/forum/#!forum/pidp-8
+[pfor]: https://tangentsoft.com/pidp8i/forum
[alert]: https://tangentsoft.com/pidp8i/alerts
Debug Builds
@@ -331,68 +330,68 @@
By default, the build system creates a release build, but you can force
it to produce a binary without as much optimization and with debug
symbols included:
- $ ./configure --debug-mode
- $ make clean
- $ tools/mmake
+ $ ./configure --debug-mode
+ $ make clean
+ $ tools/mmake
Manipulating the Build System Source Files
----
The [autosetup build system][asbs] is composed of these files and
directories:
- auto.def
- autosetup/*
- configure
- Makefile.in
+ auto.def
+ autosetup/*
+ configure
+ Makefile.in
Unlike with GNU Autoconf, which you may be familiar with, the
-`configure` script is not output from some other tool. It is just a
+`configure` script is not output from some other tool. It is just a
driver for the generic Tcl and C code under the `autosetup` directory,
which in turn runs the project-specific `auto.def` Tcl script to
-configure the software. Some knowledge of [Tcl syntax][tcldoc] will
+configure the software. Some knowledge of [Tcl syntax][tcldoc] will
therefore be helpful in modifying `auto.def`.
If you have to modify any of the files in `autosetup/` to get some
needed effect, you should try to get that change into the upstream
[Autosetup][asbs] project, then merge that change down into the local
copy when it lands upstream.
If you do not have Tcl installed on your system, `configure` builds a
minimal Tcl interpreter called `jimsh0`, based on the [Jim Tcl][jim]
-project. Developers working on the build system are encouraged to use
-this stripped-down version of Tcl rather than "real" Tcl because Jim Tcl
+project. Developers working on the build system are encouraged to use
+this stripped-down version of Tcl rather than “real” Tcl because Jim Tcl
is mostly-pure subset of Tcl, and `jimsh0` is a subset of the complete
-Jim Tcl distribution, so any changes you make that work with the
-`jimsh0` interpreter should also work with "real" Tcl, but not vice
-versa. If you have Tcl installed and don't really need it, consider
+Jim Tcl distribution, so any changes you make that work with the
+`jimsh0` interpreter should also work with “real” Tcl, but not vice
+versa. If you have Tcl installed and don’t really need it, consider
uninstalling it to force Autosetup to build and use `jimsh0`.
The `Makefile.in` file is largely a standard [GNU `make`][gmake] file
excepting only that it has variables substituted into it by Autosetup
-using its `@VARIABLE@` syntax. At this time, we do not attempt to
+using its `@VARIABLE@` syntax. At this time, we do not attempt to
achieve compatibility with other `make` programs, though in the future
we may need it to work with [BSD `make`][bmake] as well, so if you are
adding features, you might want to stick to the common subset of
-features implemented by both the GNU and BSD flavors of `make`. We do
+features implemented by both the GNU and BSD flavors of `make`. We do
not anticipate any need to support any other `make` flavors.
-This, by the way, is why we're not using some heavy-weight build system
-such as the GNU Autotools, CMake, etc. The primary advantage of GNU
+This, by the way, is why we’re not using some heavy-weight build system
+such as the GNU Autotools, CMake, etc. The primary advantage of GNU
Autotools is that you can generate source packages that will configure
-and build on weird and ancient flavors of Unix; we don't need that.
+and build on weird and ancient flavors of Unix; we don’t need that.
Cross-platform build systems such as CMake ease building the same
software on multiple disparate platforms straightforward, but the
PiDP-8/I software is built primarily on and for a single operating
-system, Raspbian Linux. It also happens to build and run on [several
+system, Raspbian Linux. It also happens to build and run on [several
other OSes][oscomp], for which we also do not need the full power of
-something like CMake. Autosetup and GNU `make` suffice for our purposes
+something like CMake. Autosetup and GNU `make` suffice for our purposes
here.
[asbs]: http://msteveb.github.io/autosetup/
[bmake]: https://www.freebsd.org/doc/en/books/developers-handbook/tools-make.html
[gmake]: https://www.gnu.org/software/make/
@@ -405,180 +404,180 @@
Directory Structure
----
The directory structure of the PiDP-8/I project is as follows:
-* `.` - Top level, occupied only by the few files the end user
+* `.` — Top level, occupied only by the few files the end user
of the source code needs immediately at hand on first unpacking the
project: the top level build system files, key documentation, and
licensing information. If a given file *can* be buried deeper, it
*should* be buried to reduce clutter at this most precious level of
the hierarchy.
-* `.fossil-settings` - Versioned settings for the Fossil build
+* `.fossil-settings` — Versioned settings for the Fossil build
system which Fossil applies as defaults everywhere you check out a
- Fossil version. Settings made here are intended to be correct for
+ Fossil version. Settings made here are intended to be correct for
all users of the system; think of these not as expressing defaults
- but as expressing *policy*. It is possible to override these
+ but as expressing *policy*. It is possible to override these
settings, but we do not make settings here if we expect that some
users may quibble with our choices here.
Any setting whose value may vary between users of the Fossil
repository should be done locally with a `fossil set` command.
Say `fossil help set` at the command line for more on this.
-* `autosetup` - The bulk of the [Autosetup build system][asbs].
+* `autosetup` — The bulk of the [Autosetup build system][asbs].
These are generic files, not modified by the project itself. We
occasionally run `tools/autosetup-update` to merge in upstream
changes.
-* `bin` - Programs installed to `$prefix/bin`, which may also
+* `bin` — Programs installed to `$prefix/bin`, which may also
be run during development, if only to test changes to those
- programs. Some scripts stored here are written in place by the
- project's developers, while other files in this directory are
+ programs. Some scripts stored here are written in place by the
+ project’s developers, while other files in this directory are
outputs of the build system.
The content of this directory is copied to `$prefix/bin` at
- installation time, which is added to the user's `PATH` by the
+ installation time, which is added to the user’s `PATH` by the
`make install` script.
-* `boot` - SIMH initialization scripts. The `*.script.in`
+* `boot` — SIMH initialization scripts. The `*.script.in`
files are written by the project developers but have local
configuration values substituted in by the `configure` script to
- produce a `*.script` version. Scripts which need no config-time
- values substituted in are checked in directly as `*.script`. The
+ produce a `*.script` version. Scripts which need no config-time
+ values substituted in are checked in directly as `*.script`. The
`*.script` files in this directory which do not fall into either of
those categories are outputs of `tools/mkbootscript`, which produces
them from `palbart` assembly listings.
All of these `*.script` files are copied to `$prefix/share/boot` by
`make mediainstall` which runs automatically from `make install`
when we detect that the binary media and SIMH boot scripts have
- never been installed at this site before. On subsequent installs,
+ never been installed at this site before. On subsequent installs,
the user chooses whether to run `make mediainstall` by hand to
overwrite all of this.
-* `doc` - Documentation files sufficiently unimportant to a new
+* `doc` — Documentation files sufficiently unimportant to a new
user of the software that they need not be at the top level of the
- project tree. Such files can wait for new users to discover them.
+ project tree. Such files can wait for new users to discover them.
- Fossil's [embedded documentation][edoc] feature allows us to present
+ Fossil’s [embedded documentation][edoc] feature allows us to present
the contents of `doc` to web site users all but indistinguishably
- from a wiki page. Why are there two different ways to achieve the
+ from a wiki page. Why are there two different ways to achieve the
same end, and how do we decide which mechanism to use?
- The rule is simple: if a given document's change history is tied to
+ The rule is simple: if a given document’s change history is tied to
the history of the PiDP-8/I project itself, it goes in `doc`, else
- it goes in the wiki. When checking out older versions of the
+ it goes in the wiki. When checking out older versions of the
PiDP-8/I software, you expect to roll back to contemporaneous
versions of the project documentation, which is what happens to all
files stored in the repository, including those in `doc`, but this
- does not happen to the wiki documents. The wiki always presents the
+ does not happen to the wiki documents. The wiki always presents the
most current version, no matter what version you have locally
checked out.
- (Fossil's wiki feature behaves much like Wikipedia: it keeps change
+ (Fossil’s wiki feature behaves much like Wikipedia: it keeps change
history for wiki documents, but it always presents the most recent
version unless you manually go poking around in the history to pull
- up old versions. If you check out a historical version of the
+ up old versions. If you check out a historical version of the
software and then say `fossil ui` within that checkout directory,
the resulting web view still shows the most recent locally-available
version of each wiki document, not versions of the wiki documents
contemporaneous with the historical version of the Fossil tree you
have checked out.)
The `doc/graphics` subdirectory holds JPEGs and SVGs displayed
inline within wiki articles.
-* `etc` - Files which get copied to `/etc` or one of its
+* `etc` — Files which get copied to `/etc` or one of its
subdirectories at installation time.
There is an exception: `pidp8i.service.in` does not get installed to
- `/etc` at install time, but only because systemd's [unit file load
+ `/etc` at install time, but only because systemd’s [unit file load
path scheme][uflp] is screwy: *some* unit files go in `/etc`, while
- others do not. The systemd docs claim we can put user units in
+ others do not. The systemd docs claim we can put user units in
`/etc/systemd/user` but this does not appear to work on a Raspberry
- Pi running Raspbian Stretch at least. We've fallen back to another
+ Pi running Raspbian Stretch at least. We’ve fallen back to another
directory that *does* work, which feels more right to us anyway, but
- which happens not to be in `/etc`. If systemd were designed sanely,
- we'd install such files to `$HOME/etc/systemd` but noooo....
+ which happens not to be in `/etc`. If systemd were designed sanely,
+ we’d install such files to `$HOME/etc/systemd` but noooo…
Since none of the above actually argues for creating another
- top-level repository directory to hold this one file, we've chosen
+ top-level repository directory to hold this one file, we’ve chosen
to store it in `etc`.
-* `examples` - Example programs for the end user's edification.
+* `examples` — Example programs for the end user’s edification.
Many of these are referenced by documentation files and therefore
should not be renamed or moved, since there may be public web links
referring to these examples.
-* `hardware` - Schematics and such for the PiDP-8/I board or
+* `hardware` — Schematics and such for the PiDP-8/I board or
associated hardware.
-* `labels` - Graphics intended to be printed out and used as
+* `labels` — Graphics intended to be printed out and used as
labels for removable media.
-* `lib` - Library routines used by other programs, installed to
+* `lib` — Library routines used by other programs, installed to
`$prefix/lib`.
-* `libexec` - A logical extension of `lib`, these are
+* `libexec` — A logical extension of `lib`, these are
standalone programs that nevertheless are intended to be run
- primarily by other programs. Whereas a file in `lib` might have its
- interface described by a programmer's reference manual, the
+ primarily by other programs. Whereas a file in `lib` might have its
+ interface described by a programmer’s reference manual, the
interface of a program in `libexec` is described by its usage
- message. Example:
+ message. Example:
- * `scanswitch` - Run by `etc/pidp8i`.
-
+ * `scanswitch` — Run by `etc/pidp8i`.
+
It is run by hand only by developers modifying its behavior.
Programs in `libexec` are installed to `$prefix/libexec`, which is
- *not* put into the user's `PATH`, on purpose. If a program should
- end up in the user's `PATH`, it belongs in `bin`. Alternately, a
+ *not* put into the user’s `PATH`, on purpose. If a program should
+ end up in the user’s `PATH`, it belongs in `bin`. Alternately, a
wrapper may be put in `bin` which calls a `libexec` program as a
helper.
-* `media` - Binary media images used either by SIMH directly or
+* `media` — Binary media images used either by SIMH directly or
by tools like `os8-run` to produce media used by SIMH.
The contents of this tree are installed to `$prefix/share/media`.
-* `obj` - Intermediate output directory used by the build
- system. It is safe to remove this directory at any time, as its
- contents may be recreated by `make`. No file checked into Fossil
+* `obj` — Intermediate output directory used by the build
+ system. It is safe to remove this directory at any time, as its
+ contents may be recreated by `make`. No file checked into Fossil
should be placed here.
(Contrast `bin` which does have some files checked into Fossil; all
of the *other* files that end up in `bin` can be recreated by
`make`, but not these few hand-written programs.)
-* `src` - Source code for the project's programs, especially
- those that cannot be used until they are built. The great majority
- of these programs are written in C. The build system's output
+* `src` — Source code for the project’s programs, especially
+ those that cannot be used until they are built. The great majority
+ of these programs are written in C. The build system’s output
directories are `bin`, `boot`, `libexec`, and `obj`.
- Programs that can be used without being "built," example programs,
+ Programs that can be used without being “built,” example programs,
and single-file scripts are placed elsewhere: `bin`, `examples`,
- `libexec`, `tools`, etc. Basically, we place such files where the
+ `libexec`, `tools`, etc. Basically, we place such files where the
build system *would* place them if they were built from something
under `src`.
- There are no program sources in the top level of `src`. The file
+ There are no program sources in the top level of `src`. The file
`src/config.h` may appear to be an exception to that restriction,
- but it is *generated output* of the `configure` script, not "source
- code" *per se*.
+ but it is *generated output* of the `configure` script, not “source
+ code” *per se*.
Multi-module programs each have their own subdirectory of `src`,
each named after the program contained within.
Single module programs live in `src/misc` or `src/asm`, depending on
whether they are host-side C programs or PAL8 assembly programs.
-* `test` - Output directory used by `tools/test-*`.
+* `test` — Output directory used by `tools/test-*`.
-* `tools` - Programs run only during development and not
+* `tools` — Programs run only during development and not
installed.
If a program is initially created here but we later decide that it
should be installed for use by end users of the PiDP-8/I system, we
move it to either `bin` or `libexec`, depending on whether it is run
@@ -602,20 +601,20 @@
$ fossil diff > my-changes.patch
Then either upload that file somewhere (e.g. Pastebin) and point to it
from a [forum post][pfor] or attach the patch to a new [PiDP-8/I mailing
list][ggml] message. Either way, include a declaration of the license
-you wish to contribute your changes under. We suggest using the [SIMH
+you wish to contribute your changes under. We suggest using the [SIMH
license][simhl], but any [non-viral][viral] [OSI-approved license][osil]
should suffice. We’re willing to tolerate viral licenses for standalone
products; for example, CC8 is under the GPL, but it’s fine because it
isn’t hard-linked into any other part of the PiDP-8/I software system.
If your change is more than a small patch, `fossil diff` might not
-incorporate all of the changes you have made. The old unified `diff`
-format can't encode branch names, file renamings, file deletions, tags,
-checkin comments, and other Fossil-specific information. For such
+incorporate all of the changes you have made. The old unified `diff`
+format can’t encode branch names, file renamings, file deletions, tags,
+checkin comments, and other Fossil-specific information. For such
changes, it is better to send a Fossil bundle:
$ fossil set autosync 0 # disable autosync
$ fossil checkin --branch my-changes
...followed by more checkins on that branch...
@@ -622,12 +621,12 @@
$ fossil bundle export --branch my-changes my-changes.bundle
After that first `fossil checkin --branch ...` command, any subsequent
`fossil ci` commands will check your changes in on that branch without
needing a `--branch` option until you explicitly switch that checkout
-directory to some other branch. This lets you build up a larger change
-on a private branch until you're ready to submit the whole thing as a
+directory to some other branch. This lets you build up a larger change
+on a private branch until you’re ready to submit the whole thing as a
bundle.
Because you are working on a branch on your private copy of the
PiDP-8/I Fossil repository, you are free to make as many checkins as
you like on the new branch before giving the `bundle export` command.
@@ -634,15 +633,15 @@
Once you are done with the bundle, send it to the developers the same
way you should a patch file.
If you provide a quality patch, we are likely to offer you a developer
-login on [the repository][repo] so you don't have to continue with the
+login on [the repository][repo] so you don’t have to continue with the
patch or bundle methods.
Please make your patches or experimental branch bundles against the tip
-of the current trunk. PiDP-8/I often drifts enough during development
+of the current trunk. PiDP-8/I often drifts enough during development
that a patch against a stable release may not apply to the trunk cleanly
otherwise.
[osil]: https://opensource.org/licenses
[repo]: https://tangentsoft.com/pidp8i/
@@ -652,37 +651,90 @@
The PiDP-8/I Software Project Code Style Rules
----
-Every code base should have a common code style. Love it or
-hate it, here are PiDP-8/I's current code style rules:
+Every code base should have a common code style. Love it or
+hate it, here are PiDP-8/I’s current code style rules:
**C Source Code**
-File types: `c`, `h`, `c.in`
+File types: `*.c`, `*.h`, `*.c.in`
-We follow the SIMH project's pre-existing code style when modifying one
+We follow the SIMH project’s pre-existing code style when modifying one
of its source files:
* Spaces for indents, size 4; tabs are rendered size 8 in HTML output,
- since that's how a PDP-8 terminal would likely interpret it!
+ since that’s how a PDP-8 terminal would likely interpret it!
-* DOS line endings. (Yes, even though this is a Linux-based project!
+* DOS line endings. (Yes, even though this is a Linux-based project!
All decent Linux text editors can cope with this.)
* Function, structure, type, and variable names are all lowercase,
with underscores separating words
* Macro names are in `ALL_UPPERCASE_WITH_UNDERSCORES`
* Whitespace in the SIMH C files is of a style I have never seen
- anywhere else in my decades of software development. This example
+ anywhere else in my decades of software development. This example
shows the important features:
- int some_function (char some_parameter)
- {
+ ```C
+ int some_function (char some_parameter)
+ {
+ int some_variable = 0;
+
+ if (some_parameter != '\0') {
+ int nbytes = sizeof (some_parameter);
+ char *buffer = malloc (4 * nbytes);
+
+ switch (some_parameter) {
+ case 'a':
+ do_something_with_buffer ((char *)buffer);
+ default:
+ do_something_else ();
+ }
+ }
+ else {
+ some_other_function (with_a_large, "number of parameters",
+ wraps_with_a_single, "indent level");
+ printf (stderr, "Failed to allocate buffer.\n");
+ }
+ }
+ ```
+
+ It is vaguely like K&R C style except that:
+
+ * The top level of statements in a function are not indented
+
+ * The closing curly brace is indented to the same level as the
+ statement(s) it contains
+
+ * There is a space before all opening parentheses, not just those
+ used in `if`, `while`, and similar flow control statements.
+
+ Nested open parentheses do not have extra spaces, however. Only
+ the outer opening parenthesis has a space separating it from
+ what went before.
+
+ * Multiple variables declared together don’t have their types and
+ variable names aligned in columns.
+
+ I find that this style is mostly sensible, but with two serious problems:
+ I find the indented closing curly braces confusing, and I find that the
+ loss of the first indent level for the statements inside a function makes
+ functions all visually run together in a screenful of code. Therefore,
+ when we have the luxury to be working on a file separate from SIMH,
+ we use a variant of its style with these two changes, which you can
+ produce with the `tools/restyle` command. See its internal comments for
+ details.
+
+ That gives the following, when applied to the above example:
+
+ ```C
+ int some_function (char some_parameter)
+ {
int some_variable = 0;
if (some_parameter != '\0') {
int nbytes = sizeof (some_parameter);
char *buffer = malloc (4 * nbytes);
@@ -690,96 +742,47 @@
switch (some_parameter) {
case 'a':
do_something_with_buffer ((char *)buffer);
default:
do_something_else ();
- }
- }
- else {
- some_other_function (with_a_large, "number of parameters",
- wraps_with_a_single, "indent level");
- printf (stderr, "Failed to allocate buffer.\n");
- }
- }
-
- It is vaguely like K&R C style except that:
-
- - The top level of statements in a function are not indented
-
- - The closing curly brace is indented to the same level as the
- statement(s) it contains
-
- - There is a space before all opening parentheses, not just those
- used in `if`, `while`, and similar flow control statements.
-
- Nested open parentheses do not have extra spaces, however. Only
- the outer opening parenthesis has a space separating it from
- what went before.
-
- - Multiple variables declared together don't have their types and
- variable names aligned in columns.
-
-I find that this style is mostly sensible, but with two serious problems:
-I find the indented closing curly braces confusing, and I find that the
-loss of the first indent level for the statements inside a function makes
-functions all visually run together in a screenful of code. Therefore,
-when we have the luxury to be working on a file separate from SIMH,
-we use a variant of its style with these two changes, which you can
-produce with the `tools/restyle` command. See its internal comments for
-details.
-
-That gives the following, when applied to the above example:
-
- int some_function (char some_parameter)
- {
- int some_variable = 0;
-
- if (some_parameter != '\0') {
- int nbytes = sizeof (some_parameter);
- char *buffer = malloc (4 * nbytes);
-
- switch (some_parameter) {
- case 'a':
- do_something_with_buffer ((char *)buffer);
- default:
- do_something_else ();
- }
- }
- else {
- some_other_function (with_a_large, "number of parameters",
- wraps_with_a_single, "indent level");
- printf (stderr, "Failed to allocate buffer.\n");
- }
- }
-
-If that looks greatly different, realize that it is just two indenting
-level differences: add one indent at function level, except for the
-closing braces, which we leave at their previous position.
-
-SIMH occasionally exceeds 100-column lines. I recommend breaking
-long lines at 72 columns. Call me an 80-column traditionalist.
-
-When in doubt, mimic what you see in the current code. When still in
-doubt, ask on the [project forum][pfor].
+ }
+ }
+ else {
+ some_other_function (with_a_large, "number of parameters",
+ wraps_with_a_single, "indent level");
+ printf (stderr, "Failed to allocate buffer.\n");
+ }
+ }
+ ```
+
+ If that looks greatly different, realize that it is just two indenting
+ level differences: add one indent at function level, except for the
+ closing braces, which we leave at their previous position.
+
+ SIMH occasionally exceeds 100-column lines. I recommend breaking
+ long lines at 72 columns. Call me an 80-column traditionalist.
+
+ When in doubt, mimic what you see in the current code. When still in
+ doubt, ask on the [project forum][pfor].
[indent]: http://linux.die.net/man/1/indent
**Plain Text Files**
-File types: `md`, `txt`
+File types: `*.md`, `*.txt`
* Spaces for indents, size 4.
-* Unix line endings. The only common text editor I'm aware of that
+* Unix line endings. The only common text editor I’m aware of that
has a problem with this is Notepad, and people looking at these
files anywhere other than unpacked on a Raspberry Pi box are
probably looking at them through the Fossil web interface on
tangentsoft.com.
* Markdown files must follow the syntax flavor understood by
- [Fossil's Markdown interpreter][fmd].
+ [Fossil’s Markdown interpreter][fmd].
[fmd]: https://tangentsoft.com/pidp8i/md_rules
@@ -815,23 +818,23 @@
before you resolve a ticket.
There is a process interlock between the Resolution and Status settings
for a ticket: while Status is not Closed, Resolution should be Open.
When Resolution changes from Open, Status should change to either Review
-or, preferentially, Closed. A resolution is an end state, not an
+or, preferentially, Closed. A resolution is an end state, not an
expression of changeable intent: no more ceremony than setting a
ticket’s Resolution state *away* from Open and its Status *to* Closed is
required.
If you do not intend to close a ticket but wish to advocate for a
particular resolution, just add a comment to the ticket and let someone
else choose whether to close the ticket or not. Don’t change the
Resolution value until the issue has been *resolved* for good.
-For example, the resolution "Works as Designed" does not merely mean,
-“Yes, we know it works that way,” it also implies “...and we have no
-intention to change that behavior, ever.” If there is a chance that the
+For example, the resolution “Works as Designed” does not merely mean,
+“Yes, we know it works that way,” it also implies “…and we have no
+intention to change that behavior, ever.” If there is a chance that the
behavior described in the ticket could change, you should not assign any
resolution. Just leave it open until someone decides to do something
final with the ticket.
This is not to say that a ticket can never be re-opened once it’s had a
@@ -850,9 +853,9 @@
> marked Implemented, but still Closed.
## License
-Copyright © 2016-2019 by Warren Young. This document is licensed under
+Copyright © 2016-2020 by Warren Young. This document is licensed under
the terms of [the SIMH license][sl].
-[sl]: https://tangentsoft.com/pidp8i/doc/trunk/SIMH-LICENSE.md
+[sl]: https://tangentsoft.com/pidp8i/doc/trunk/SIMH-LICENSE.md
Index: Makefile.in
==================================================================
--- Makefile.in
+++ Makefile.in
@@ -5,11 +5,11 @@
# If you are seeing this at the top of a file called Makefile and you
# intend to make edits, do that in Makefile.in. Saying "make" will then
# re-build Makefile from that modified Makefile.in before proceeding to
# do the "make" operation.
#
-# Copyright © 2015 by Oscar Vermeulen, © 2016-2019 by Warren Young
+# Copyright © 2015 by Oscar Vermeulen, © 2016-2020 by Warren Young
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
@@ -34,22 +34,32 @@
# authorization from those authors.
########################################################################
# Git commit ID and time of the latest version of the SIMH 4 project on
# GitHub that has been merged into this source base.
-SGCID=4e0450cff96830c5aced36928a4427adfc5314f8
-SGCTM=2019-04-18T20:03:17-07:00
+SGCID=cabd3784bc7e33d63906602e33a5d01b60c1dee1
+SGCTM=2020-04-17T18:20:27-07:00
# C build flags for the PDP-8 simulator and its PiDP-8/I extensions.
SIM_CFLAGS := @CFLAGS@ @PI_CFLAGS@ @BUILDMODE@ \
-Wno-unused-result -Wno-parentheses \
-DUSE_READER_THREAD -DHAVE_DLOPEN=$(subst .,,@SH_SOEXT@) -DPIDP8I \
- -DSIM_ASYNCH_IO -DHAVE_REGEX_H -DHAVE_GLOB \
+ -DSIM_ASYNCH_IO -DHAVE_GLOB \
-DSIM_GIT_COMMIT_ID=$(SGCID) -DSIM_GIT_COMMIT_TIME=$(SGCTM) \
+ -DSIM_BUILD_TOOL='autosetup+gmake' \
-D_GNU_SOURCE \
-U__STRICT_ANSI__ \
-I @srcdir@/src/SIMH -I @srcdir@/src/pidp8i -I src -I src/SIMH -I src/pidp8i
+ifneq "@HAVE_PCRE_H@" ""
+SIM_CFLAGS += -DHAVE_PCRE_H
+SIM_LFLAGS := -lpcre
+else
+SIM_CFLAGS += -DHAVE_REGEX_H
+endif
+ifneq "@HAVE_PCREPOSIX_H@" ""
+SIM_CFLAGS += -DHAVE_PCREPOSIX_H
+endif
PIDP8I_CFLAGS = $(SIM_CFLAGS)
SIMH_CFLAGS = $(SIM_CFLAGS)
SIMH_PDP8_CFLAGS = $(SIM_CFLAGS)
# Greatly stripped-down build options for the cc8 cross-compiler
@@ -249,10 +259,13 @@
@srcdir@/src/cc8/Makefile.in \
@srcdir@/src/SIMH/Makefile.in \
@srcdir@/src/SIMH/PDP8/Makefile.in
INFILES = \
@srcdir@/bin/os8-cp.in \
+ @srcdir@/bin/os8-run.in \
+ @srcdir@/bin/teco-pi-demo.in \
+ @srcdir@/bin/txt2os8.in \
@srcdir@/bin/pidp8i.in \
@srcdir@/boot/0.script.in \
@srcdir@/boot/2.script.in \
@srcdir@/boot/3.script.in \
@srcdir@/boot/4.script.in \
@@ -262,12 +275,15 @@
@srcdir@/boot/run-v3f.script.in \
@srcdir@/boot/tss8.script.in \
@srcdir@/etc/pidp8i.service.in \
@srcdir@/etc/sudoers.in \
@srcdir@/etc/usb-mount@.service.in \
+ @srcdir@/lib/os8script.py.in \
+ @srcdir@/lib/simh.py.in \
@srcdir@/src/pidp8i/gpio-common.c.in \
@srcdir@/tools/simh-update.in \
+ @srcdir@/tools/test-os8-send-file.in \
$(OS8RUN_INFILES)
OS8RUN_OUTFILES := $(subst @srcdir@/,,$(OS8RUN_INFILES))
OS8RUN_OUTFILES := $(subst .in,,$(OS8RUN_OUTFILES))
PRECIOUS_OUTFILES := $(subst @srcdir@/,,$(PRECIOUS_INFILES))
PRECIOUS_OUTFILES := $(subst .in,,$(PRECIOUS_OUTFILES))
@@ -303,10 +319,12 @@
V3F_TC08_TU56 = bin/v3f-tc08.tu56
V3F_TD12K_TU56 = bin/v3f-td12k.tu56
V3F_BOOT_TAPE= bin/v3f-@OS8_TAPE_DEVICE@.tu56
CC8_TU56_SCRIPT = $(OS8_SCRIPTS_DIR)/cc8-tu56.os8
+
+RCFILE := @ABSPREFIX@/etc/pidp8i.rc
CLTXT = /boot/cmdline.txt
ADF := adrules.mk
@@ -425,10 +443,13 @@
@( test -d /etc/sudoers.d -a -w /etc/sudoers.d -a -x /bin/systemctl && \
@INSTALL@ -m 440 -o root -g root @srcdir@/etc/sudoers \
/etc/sudoers.d/099_pidp8i \
) || true
+ @# Install runtime config file if there isn't one there already.
+ @test -f $(RCFILE) || @INSTALL@ -m 644 -o @INSTUSR@ @srcdir@/etc/pidp8i.rc $(RCFILE)
+
@# Add installation bin dir to the non-root user's PATH unless it's
@# already in there or we aren't running under sudo.
@(for p in .profile .bash_profile ; do \
test -n "$$SUDO_USER" -a -w "/home/$$SUDO_USER/$$p" && \
! grep -qF "@ABSPREFIX@/bin" "/home/$$SUDO_USER/$$p" && \
@@ -476,10 +497,13 @@
@sed -e 's#^build =.*#build = "@ABSPREFIX@"#' \
-e 's#^media =.*#media = os.path.join (build, "share/media/")#' \
-e 's#^os8mo =.*#os8mo = os8mi#' \
< $(PIDP8I_DIRS) > @prefix@/$(PIDP8I_DIRS)
@chgrp @INSTGRP@ @prefix@/$(PIDP8I_DIRS)
+
+ @# We need a directory for PIDs and such in some cases
+ @@INSTALL@ -d -m 755 @prefix@/run
instdirs:
@echo "Creating installation directory tree..."
@for d in $(INSTDIRS) ; do @INSTALL@ -m 755 -o @INSTUSR@ -g @INSTGRP@ -d @prefix@/$$d ; done
@@ -688,11 +712,11 @@
$(BUILDDIRS):
mkdir -p $@
$(PIDP8I_SIM): $(SIM_OBJS) obj/pidp8i/gpio-@LED_DRIVER_MODULE@ls.o
- $(CC) -o $@ @PI_LFLAGS@ $^ $(LIBS) @PI_LIBS@
+ $(CC) -o $@ @PI_LFLAGS@ $^ $(LIBS) $(SIM_LFLAGS) @PI_LIBS@
ln -f bin/pidp8i-sim bin/pdp8
bin/cc8: $(CC8_OBJS)
$(CC) -o $@ $^ $(LIBS)
Index: README.md
==================================================================
--- README.md
+++ README.md
@@ -734,32 +734,61 @@
## Runtime Configuration
-The `pidp8i` command may be configured by the optional `pidp8i.rc` file,
+The `pidp8i` command may be configured by the `pidp8i.rc` file,
located by default in `/opt/pidp8i/etc/`. This is a Bourne shell script
-which is sourced by `pidp8i` if it exists, and recognizes the following
-variables:
+which is sourced by `pidp8i` if it exists which may set the following
+variables for the `pidp8i` script to affect how it works:
-### SCREEN_MANAGER=screen
-
-By default, pidp8i installs and uses [GNU screen(1)][gnuscreen] to
-manage screen sessions. However, if you prefer to use [tmux(1)][tmux]
-as a screen manager for the pidp8i session, you may set
-`SCREEN_MANAGER=tmux`. Note that if you make this change, you are
-responsible for installing tmux; on Raspbian, this is done by:
-
- $ sudo apt-get install tmux
-
-Switching between configured screen managers must be done while pidp8i
-is stopped.
-
-[gnuscreen]: https://www.gnu.org/software/screen/
-[tmux]: https://tmux.github.io/
+### `SCREEN_MANAGER=screen`
+
+By default, the PiDP-8/I software distribution installs and uses [GNU
+`screen(1)`][gscr] to allow the simulator to run in the background yet be
+reattached from a later terminal session, then possibly later to be
+backgrounded once again. Without the intermediation of something like
+`screen`, the simulator would either forever be in the background and
+we’d have to export the console [another way][scons] or you’d have to
+fire it up interactively any time you wanted to use it. This scheme lets
+us have it both ways.
+
+The `SCREEN_MANAGER` setting is for use by those that need something
+other than GNU `screen`. There are several alternatives:
+
+* **screen**: The default, per above.
+
+* [**tmux**][tmux]: A popular alternative to `screen`, especially on
+ on BSD platforms. Note that the "attention" character for `tmux`
+ is Ctrl-B by default, not Ctrl-A as with
+ `screen`.
+
+* **none**: This mode is for interactive use, allowing you to
+ run the installed simulator with the installed media without any
+ screen manager at all.
+
+ In this mode, the `pidp8i` and `pidp8i start` commands do the
+ same thing: run the simulator directly attached to your current
+ interactive terminal. The `pidp8i stop` command becomes a no-op,
+ since stopping the simulator is then done in the standard SIMH way:
+ Ctrl-E, quit .
+
+Note that the alternative screen managers are not installed by default.
+If you set `SCREEN_MANAGER=tmux`, you must then ensure that `tmux` is in
+fact installed before the `pidp8i` script goes to try and use it. On
+Raspbian, this is done by:
+
+ $ sudo apt install tmux
+
+Switching between configured screen managers must be done while the
+simulator is stopped.
+
+[gscr]: https://www.gnu.org/software/screen/
+[scons]: /wiki?name=Serial+or+Telnet+PDP-8+Console
+[tmux]: https://tmux.github.io/
## The OS/8 RK05 Disk Image
For the first several years of the PiDP-8/I project, the OS/8 RK05 disk
@@ -989,11 +1018,11 @@
`sudo` permissions.
## License
-Copyright © 2016-2019 by Warren Young. This document is licensed under
+Copyright © 2016-2020 by Warren Young. This document is licensed under
the terms of [the SIMH license][sl].
[cprj]: https://tangentsoft.com/pidp8i/
[sm1]: http://obsolescence.wixsite.com/obsolescence/2016-pidp-8-building-instructions
Index: auto.def
==================================================================
--- auto.def
+++ auto.def
@@ -339,10 +339,11 @@
}
# Check for headers, functions, etc. whose absence we can work around
cc-check-decls __progname
cc-check-includes time.h
+cc-check-includes pcre.h pcreposix.h
cc-check-function-in-lib clock_gettime rt
cc-check-functions clock_nanosleep nanosleep usleep
cc-check-functions sched_yield
cc-with {-includes signal.h} {
cc-check-types sighandler_t sig_t
@@ -467,36 +468,56 @@
catch {exec hostname} host
set user $::env(USER)
define BUILDUSER "$user@$host"
define BUILDTS [clock format [clock seconds] -format "%Y.%m.%d at %T %Z"]
-# The os8-run script requires Python and some non-core modules.
-set status [catch {exec python -c exit} result]
-if {$status != 0} {
- user-error "Python 2 does not appear to be installed here. It is required."
+# The os8-run script requires Python 2 or 3 and some non-core modules.
+set status [catch {exec python3 -c exit} result]
+if {$status == 0} {
+ set pyver 3
+ set pycmd "python3"
+} else {
+ set status [catch {exec python2 -c exit} result]
+ if {$status == 0} {
+ set pyver 2
+ set pycmd "python2"
+ } else {
+ set status [catch {exec python -c exit} result]
+ if {$status == 0} {
+ set status [catch {exec python --version | grep -q 'Version 2'} result]
+ set pyver [expr $status == 0 ? 2 : 3]
+ set pycmd "python"
+ }
+ }
+}
+if {$pyver == ""} {
+ user-error "Python does not appear to be installed here. It is required."
}
-msg-result "Python 2 is installed here."
-set status [catch {exec python -c "import pexpect" 2> /dev/null} result]
+define PYCMD $pycmd
+define PYVER $pyver
+msg-result "Python $pyver is installed here as '$pycmd'."
+set status [catch {exec $pycmd -c "import pexpect" 2> /dev/null} result]
if {$status != 0} {
set msg "The Python pexpect module is not installed here. Fix with\n\n"
- append msg " sudo apt install python-pip\n"
- append msg " sudo pip install pexpect\n"
+ append msg " sudo apt install $pycmd-pip\n"
+ append msg "\nTHEN:\n"
+ append msg " sudo pip$pyver install pexpect\n"
append msg "\nOR:\n"
append msg "\n sudo easy_install pexpect\n"
append msg "\nOR:\n"
- append msg "\n sudo apt install python-pexpect\n"
+ append msg "\n sudo apt install $pycmd-pexpect\n"
user-error $msg
}
msg-result "Python module pexpect is installed here."
-set status [catch {exec python -c "import pkg_resources" 2> /dev/null} result]
+set status [catch {exec $pycmd -c "import pkg_resources" 2> /dev/null} result]
if {$status != 0} {
set msg "The Python pkg_resources module is not installed here. Fix with\n"
- append msg "\n sudo pip install pkg_resources\n"
+ append msg "\n sudo pip$pyver install pkg_resources\n"
append msg "\nOR:\n"
append msg "\n sudo easy_install pkg_resources\n"
append msg "\nOR:\n"
- append msg "\n sudo apt install python-pkg-resources\n"
+ append msg "\n sudo apt install $pycmd-pkg-resources\n"
user-error $msg
}
msg-result "Python module pkg_resources is installed here."
# Check for Perl and that it can run the test corpus builder. Not fatal
@@ -565,10 +586,13 @@
make-config-header src/config.h \
-auto {ENABLE_* HAVE_* PACKAGE_* SIZEOF_*} \
-bare {ILS_MODE PCB_*}
make-template bin/pidp8i.in
make-template bin/os8-cp.in
+make-template bin/os8-run.in
+make-template bin/teco-pi-demo.in
+make-template bin/txt2os8.in
make-template boot/common.script.in
make-template boot/0.script.in
make-template boot/2.script.in
make-template boot/3.script.in
make-template boot/4.script.in
@@ -579,13 +603,15 @@
make-template boot/tss8.script.in
make-template etc/pidp8i.service.in
make-template etc/sudoers.in
make-template etc/usb-mount@.service.in
make-template examples/Makefile.in
+make-template lib/os8script.py.in
make-template lib/pidp8i/__init__.py.in
make-template lib/pidp8i/dirs.py.in
make-template lib/pidp8i/ips.py.in
+make-template lib/simh.py.in
make-template media/os8/init.tx.in
make-template media/os8/3finit.tx.in
make-template src/Makefile.in
make-template src/cc8/Makefile.in
make-template src/cc8/os8/Makefile.in
@@ -592,7 +618,10 @@
make-template src/pidp8i/gpio-common.c.in
make-template src/pidp8i/main.c.in
make-template src/SIMH/Makefile.in
make-template src/SIMH/PDP8/Makefile.in
make-template tools/simh-update.in
+make-template tools/test-os8-send-file.in
make-template Makefile.in
-exec chmod +x "$builddir/bin/pidp8i" "$builddir/tools/simh-update" "$builddir/bin/os8-cp"
+foreach f [concat [glob "$builddir/bin/*"] [glob "$builddir/tools/*"]] {
+ file attributes $f -permissions +x
+}
Index: autosetup/README.autosetup
==================================================================
--- autosetup/README.autosetup
+++ autosetup/README.autosetup
@@ -1,6 +1,6 @@
-README.autosetup created by autosetup v0.6.9
+README.autosetup created by autosetup v0.6.9+
This is the autosetup directory for a local install of autosetup.
It contains autosetup, support files and loadable modules.
*.tcl files in this directory are optional modules which
Index: autosetup/autosetup
==================================================================
--- autosetup/autosetup
+++ autosetup/autosetup
@@ -4,11 +4,11 @@
# vim:se syntax=tcl:
# \
dir=`dirname "$0"`; exec "`$dir/autosetup-find-tclsh`" "$0" "$@"
# Note that the version has a trailing + on unreleased versions
-set autosetup(version) 0.6.9
+set autosetup(version) 0.6.9+
# Can be set to 1 to debug early-init problems
set autosetup(debug) [expr {"--debug" in $argv}]
##################################################################
@@ -91,17 +91,17 @@
# We simply parse anything that looks like an option
set autosetup(getopt) [getopt argv]
#"=Core Options:"
options-add {
- help:=local => "display help and options. Optionally specify a module name, such as --help=system"
+ help:=all => "display help and options. Optional: module name, such as --help=system"
licence license => "display the autosetup license"
- version => "display the version of autosetup"
+ version => "display the version of autosetup"
ref:=text manual:=text
reference:=text => "display the autosetup command reference. 'text', 'wiki', 'asciidoc' or 'markdown'"
- debug => "display debugging output as autosetup runs"
- install:=. => "install autosetup to the current or given directory"
+ debug => "display debugging output as autosetup runs"
+ install:=. => "install autosetup to the current or given directory"
}
if {$autosetup(installed)} {
# hidden options so we can produce a nice error
options-add {
sysinstall:path
@@ -202,24 +202,30 @@
}
}
autosetup_add_dep $autosetup(autodef)
- define CONFIGURE_OPTS ""
+ # Add $argv to CONFIGURE_OPTS, but ignore duplicates and quote if needed
+ set configure_opts {}
foreach arg $autosetup(argv) {
- define-append CONFIGURE_OPTS [quote-if-needed $arg]
+ set quoted [quote-if-needed $arg]
+ # O(n^2), but n will be small
+ if {$quoted ni $configure_opts} {
+ lappend configure_opts $quoted
+ }
}
+ define CONFIGURE_OPTS [join $configure_opts]
define AUTOREMAKE [file-normalize $autosetup(exe)]
define-append AUTOREMAKE [get-define CONFIGURE_OPTS]
# Log how we were invoked
configlog "Invoked as: [getenv WRAPPER $::argv0] [quote-argv $autosetup(argv)]"
configlog "Tclsh: [info nameofexecutable]"
- # Note that auto.def is *not* loaded in the global scope
- source $autosetup(autodef)
+ # Load auto.def as module "auto.def"
+ autosetup_load_module auto.def source $autosetup(autodef)
# Could warn here if options {} was not specified
show-notices
@@ -340,12 +346,12 @@
}
if {![info exists result]} {
# No user-specified value. Has options-defaults been set?
foreach opt $names {
- if {[dict exists $::autosetup(options-defaults) $opt]} {
- set result [dict get $autosetup(options-defaults) $opt]
+ if {[dict exists $::autosetup(optdefault) $opt]} {
+ set result [dict get $autosetup(optdefault) $opt]
}
}
}
if {[info exists result]} {
@@ -373,11 +379,11 @@
}
# Parse the option definition in $opts and update
# ::autosetup(setoptions) and ::autosetup(optionhelp) appropriately
#
-proc options-add {opts {header ""}} {
+proc options-add {opts} {
global autosetup
# First weed out comment lines
set realopts {}
foreach line [split $opts \n] {
@@ -389,12 +395,11 @@
for {set i 0} {$i < [llength $opts]} {incr i} {
set opt [lindex $opts $i]
if {[string match =* $opt]} {
# This is a special heading
- lappend autosetup(optionhelp) $opt ""
- set header {}
+ lappend autosetup(optionhelp) [list $opt $autosetup(module)]
continue
}
unset -nocomplain defaultvalue equal value
#puts "i=$i, opt=$opt"
@@ -451,12 +456,12 @@
}
} else {
# String option.
lappend autosetup(options) $name
- if {$colon eq ":"} {
- # Was ":name=default" given?
+ if {$equal ne "="} {
+ # Was the option given as "name:value=default"?
# If so, set $value to the display name and $defaultvalue to the default
# (This is the preferred way to set a default value for a string option)
if {[regexp {^([^=]+)=(.*)$} $value -> value defaultvalue]} {
dict set autosetup(optdefault) $name $defaultvalue
}
@@ -466,13 +471,13 @@
if {[dict exists $autosetup(options-defaults) $name]} {
# A default was specified with options-defaults, so use it
set defaultvalue [dict get $autosetup(options-defaults) $name]
dict set autosetup(optdefault) $name $defaultvalue
} elseif {![info exists defaultvalue]} {
- # For backward compatibility, if ":name" was given, use name as both
- # the display text and the default value, but only if the user
- # specified the option without the value
+ # No default value was given by value=default or options-defaults
+ # so use the value as the default when the plain option with no
+ # value is given (.e.g. just --opt instead of --opt=value)
set defaultvalue $value
}
if {$equal eq "="} {
# String option with optional value
@@ -507,39 +512,22 @@
if {[lindex $opts $i+1] eq "=>"} {
set desc [lindex $opts $i+2]
if {[info exists defaultvalue]} {
set desc [string map [list @default@ $defaultvalue] $desc]
}
- #string match \n* $desc
- if {$header ne ""} {
- lappend autosetup(optionhelp) $header ""
- set header ""
- }
# A multi-line description
- lappend autosetup(optionhelp) $opthelp $desc
+ lappend autosetup(optionhelp) [list $opthelp $autosetup(module) $desc]
incr i 2
}
}
}
# @module-options optionlist
#
-# Like 'options', but used within a module.
+# Deprecated. Simply use 'options' from within a module.
proc module-options {opts} {
- set header ""
- if {$::autosetup(showhelp) > 1 && [llength $opts]} {
- set header "Module Options:"
- }
- options-add $opts $header
-
- if {$::autosetup(showhelp)} {
- # Ensure that the module isn't executed on --help
- # We are running under eval or source, so use break
- # to prevent further execution
- #return -code break -level 2
- return -code break
- }
+ options $opts
}
proc max {a b} {
expr {$a > $b ? $a : $b}
}
@@ -564,14 +552,21 @@
if {$len} {
puts ""
}
}
-proc options-show {} {
+# Display options (from $autosetup(optionhelp)) for modules that match
+# glob pattern $what
+proc options-show {what} {
+ set local 0
# Determine the max option width
set max 0
- foreach {opt desc} $::autosetup(optionhelp) {
+ foreach help $::autosetup(optionhelp) {
+ lassign $help opt module desc
+ if {![string match $what $module]} {
+ continue
+ }
if {[string match =* $opt] || [string match \n* $desc]} {
continue
}
set max [max $max [string length $opt]]
}
@@ -580,17 +575,27 @@
catch {
lassign [exec stty size] rows cols
}
incr cols -1
# Now output
- foreach {opt desc} $::autosetup(optionhelp) {
+ foreach help $::autosetup(optionhelp) {
+ lassign $help opt module desc
+ if {![string match $what $module]} {
+ continue
+ }
+ if {$local == 0 && $module eq "auto.def"} {
+ puts "Local Options:"
+ incr local
+ }
if {[string match =* $opt]} {
+ # Output a special heading line"
puts [string range $opt 1 end]
continue
}
puts -nonewline " [format %-${max}s $opt]"
if {[string match \n* $desc]} {
+ # Output a pre-formatted help description as-is
puts $desc
} else {
options-wrap-desc [string trim $desc] $cols " " $indent [expr $max + 2]
}
}
@@ -608,16 +613,20 @@
#
# The default is 'name=0', meaning that the option is disabled by default.
# If 'name=1' is used to make the option enabled by default, the description should reflect
# that with text like "Disable support for ...".
#
-# An argument option (one which takes a parameter) is of the form:
+# An argument option (one which takes a parameter) is of one of the following forms:
#
-## name:[=]value => "Description of this option"
+## name:value => "Description of this option"
+## name:value=default => "Description of this option with a default value"
+## name:=value => "Description of this option with an optional value"
#
# If the 'name:value' form is used, the value must be provided with the option (as '--name=myvalue').
-# If the 'name:=value' form is used, the value is optional and the given value is used as the default
+# If the 'name:value=default' form is used, the option has the given default value even if not
+# specified by the user.
+# If the 'name:=value' form is used, the value is optional and the given value is used
# if it is not provided.
#
# The description may contain '@default@', in which case it will be replaced with the default
# value for the option (taking into account defaults specified with 'options-defaults'.
#
@@ -627,23 +636,26 @@
# For example, '--disable-lfs' is an alias for '--disable=largefile':
#
## lfs=1 largefile=1 => "Disable large file support"
#
proc options {optlist} {
- # Allow options as a list or args
- options-add $optlist "Local Options:"
+ global autosetup
- if {$::autosetup(showhelp)} {
- options-show
- exit 0
+ options-add $optlist
+
+ if {$autosetup(showhelp)} {
+ # If --help, stop now to show help
+ return -code break
}
- # Check for invalid options
- if {[opt-bool option-checking]} {
- foreach o [dict keys $::autosetup(getopt)] {
- if {$o ni $::autosetup(options)} {
- user-error "Unknown option --$o"
+ if {$autosetup(module) eq "auto.def"} {
+ # Check for invalid options
+ if {[opt-bool option-checking]} {
+ foreach o [dict keys $::autosetup(getopt)] {
+ if {$o ni $::autosetup(options)} {
+ user-error "Unknown option --$o"
+ }
}
}
}
}
@@ -1171,12 +1183,13 @@
foreach m $args {
if {[info exists libmodule($m)]} {
continue
}
set libmodule($m) 1
+
if {[info exists modsource(${m}.tcl)]} {
- automf_load eval $modsource(${m}.tcl)
+ autosetup_load_module $m eval $modsource(${m}.tcl)
} else {
set locs [list ${m}.tcl ${m}/init.tcl]
set found 0
foreach dir $dirs {
foreach loc $locs {
@@ -1192,11 +1205,11 @@
}
if {$found} {
# For the convenience of the "use" source, point to the directory
# it is being loaded from
set ::usedir [file dirname $source]
- automf_load source $source
+ autosetup_load_module $m source $source
autosetup_add_dep $source
} else {
autosetup-error "use: No such module: $m"
}
}
@@ -1205,23 +1218,28 @@
proc autosetup_load_auto_modules {} {
global autosetup modsource
# First load any embedded auto modules
foreach mod [array names modsource *.auto] {
- automf_load eval $modsource($mod)
+ autosetup_load_module $mod eval $modsource($mod)
}
# Now any external auto modules
foreach file [glob -nocomplain $autosetup(libdir)/*.auto $autosetup(libdir)/*/*.auto] {
- automf_load source $file
+ autosetup_load_module [file tail $file] source $file
}
}
# Load module source in the global scope by executing the given command
-proc automf_load {args} {
+proc autosetup_load_module {module args} {
+ global autosetup
+ set prev $autosetup(module)
+ set autosetup(module) $module
+
if {[catch [list uplevel #0 $args] msg opts] ni {0 2 3}} {
autosetup-full-error [error-dump $msg $opts $::autosetup(debug)]
}
+ set autosetup(module) $prev
}
# Initial settings
set autosetup(exe) $::argv0
set autosetup(istcl) 1
@@ -1229,10 +1247,11 @@
set autosetup(installed) 0
set autosetup(sysinstall) 0
set autosetup(msg-checking) 0
set autosetup(msg-quiet) 0
set autosetup(inittypes) {}
+set autosetup(module) autosetup
# Embedded modules are inserted below here
set autosetup(installed) 1
set autosetup(sysinstall) 0
# ----- @module asciidoc-formatting.tcl -----
@@ -1434,26 +1453,26 @@
puts "Usage: [file tail $::autosetup(exe)] \[options\] \[settings\]\n"
puts "This is [autosetup_version], a build environment \"autoconfigurator\""
puts "See the documentation online at http://msteveb.github.com/autosetup/\n"
- if {$what eq "local"} {
- if {[file exists $::autosetup(autodef)]} {
- # This relies on auto.def having a call to 'options'
- # which will display options and quit
- source $::autosetup(autodef)
- } else {
- options-show
- }
- } else {
- incr ::autosetup(showhelp)
- if {[catch {use $what}]} {
- user-error "Unknown module: $what"
- } else {
- options-show
- }
- }
+ if {$what in {all local}} {
+ # Need to load auto.def now
+ if {[file exists $::autosetup(autodef)]} {
+ # Load auto.def as module "auto.def"
+ autosetup_load_module auto.def source $::autosetup(autodef)
+ }
+ if {$what eq "all"} {
+ set what *
+ } else {
+ set what auto.def
+ }
+ } else {
+ use $what
+ puts "Options for module $what:"
+ }
+ options-show $what
exit 0
}
proc autosetup_show_license {} {
global modsource autosetup
Index: autosetup/autosetup-find-tclsh
==================================================================
--- autosetup/autosetup-find-tclsh
+++ autosetup/autosetup-find-tclsh
@@ -3,11 +3,11 @@
# If not found, builds a bootstrap jimsh from source
# Prefer $autosetup_tclsh if is set in the environment
d=`dirname "$0"`
{ "$d/jimsh0" "$d/autosetup-test-tclsh"; } 2>/dev/null && exit 0
PATH="$PATH:$d"; export PATH
-for tclsh in $autosetup_tclsh jimsh tclsh tclsh8.5 tclsh8.6; do
+for tclsh in $autosetup_tclsh jimsh tclsh tclsh8.5 tclsh8.6 tclsh8.7; do
{ $tclsh "$d/autosetup-test-tclsh"; } 2>/dev/null && exit 0
done
echo 1>&2 "No installed jimsh or tclsh, building local bootstrap jimsh0"
for cc in ${CC_FOR_BUILD:-cc} gcc; do
{ $cc -o "$d/jimsh0" "$d/jimsh0.c"; } 2>/dev/null || continue
Index: autosetup/cc-db.tcl
==================================================================
--- autosetup/cc-db.tcl
+++ autosetup/cc-db.tcl
@@ -6,10 +6,10 @@
# The 'cc-db' module provides a knowledge-base of system idiosyncrasies.
# In general, this module can always be included.
use cc
-module-options {}
+options {}
# openbsd needs sys/types.h to detect some system headers
cc-include-needs sys/socket.h sys/types.h
cc-include-needs netinet/in.h sys/types.h
Index: autosetup/cc-lib.tcl
==================================================================
--- autosetup/cc-lib.tcl
+++ autosetup/cc-lib.tcl
@@ -5,12 +5,10 @@
#
# Provides a library of common tests on top of the 'cc' module.
use cc
-module-options {}
-
# @cc-check-lfs
#
# The equivalent of the 'AC_SYS_LARGEFILE' macro.
#
# defines 'HAVE_LFS' if LFS is available,
Index: autosetup/cc-shared.tcl
==================================================================
--- autosetup/cc-shared.tcl
+++ autosetup/cc-shared.tcl
@@ -18,11 +18,11 @@
## SH_LINKRPATH Format for setting the rpath when linking an executable, %s = path
## SH_LINKFLAGS Flags to use linking an executable which will load shared objects
## LD_LIBRARY_PATH Environment variable which specifies path to shared libraries
## STRIPLIBFLAGS Arguments to strip a dynamic library
-module-options {}
+options {}
# Defaults: gcc on unix
define SHOBJ_CFLAGS -fPIC
define SHOBJ_LDFLAGS -shared
define SH_CFLAGS -fPIC
Index: autosetup/cc.tcl
==================================================================
--- autosetup/cc.tcl
+++ autosetup/cc.tcl
@@ -27,11 +27,11 @@
## CC_FOR_BUILD
## LD
use system
-module-options {}
+options {}
# Checks for the existence of the given function by linking
#
proc cctest_function {function} {
cctest -link 1 -declare "extern void $function\(void);" -code "$function\();"
@@ -678,15 +678,15 @@
}
define CPP [get-env CPP "[get-define CC] -E"]
# XXX: Could avoid looking for a C++ compiler until requested
-# Note that if CXX isn't found, we just set it to "false". It might not be needed.
+# If CXX isn't found, it is set to the empty string.
if {[env-is-set CXX]} {
define CXX [find-an-executable -required [get-env CXX ""]]
} else {
- define CXX [find-an-executable [get-define cross]c++ [get-define cross]g++ false]
+ define CXX [find-an-executable [get-define cross]c++ [get-define cross]g++]
}
# CXXFLAGS default to CFLAGS if not specified
define CXXFLAGS [get-env CXXFLAGS [get-define CFLAGS]]
Index: autosetup/pkg-config.tcl
==================================================================
--- autosetup/pkg-config.tcl
+++ autosetup/pkg-config.tcl
@@ -13,11 +13,11 @@
#
# 'PKG_CONFIG' may be set to use an alternative to 'pkg-config'.
use cc
-module-options {
+options {
sysroot:dir => "Override compiler sysroot for pkg-config search path"
}
# @pkg-config-init ?required?
#
@@ -71,10 +71,13 @@
set sysroot [get-define SYSROOT]
# XXX: It's possible that these should be set only when invoking pkg-config
global env
set env(PKG_CONFIG_DIR) ""
+ # Supposedly setting PKG_CONFIG_LIBDIR means that PKG_CONFIG_PATH is ignored,
+ # but it doesn't seem to work that way in practice
+ set env(PKG_CONFIG_PATH) ""
# Do we need to try /usr/local as well or instead?
set env(PKG_CONFIG_LIBDIR) $sysroot/usr/lib/pkgconfig:$sysroot/usr/share/pkgconfig
set env(PKG_CONFIG_SYSROOT_DIR) $sysroot
}
}
@@ -106,22 +109,34 @@
if {!$ok} {
msg-result "no pkg-config"
return 0
}
- if {[catch {exec [get-define PKG_CONFIG] --modversion "$module $args"} version]} {
+ set pkgconfig [get-define PKG_CONFIG]
+
+ set ret [catch {exec $pkgconfig --modversion "$module $args"} version]
+ configlog "$pkgconfig --modversion $module $args: $version"
+ if {$ret} {
msg-result "not found"
- configlog "pkg-config --modversion $module $args: $version"
+ return 0
+ }
+ # Sometimes --modversion succeeds but because of dependencies it isn't usable
+ # This seems to show up with --cflags
+ set ret [catch {exec $pkgconfig --cflags $module} cflags]
+ if {$ret} {
+ msg-result "unusable ($version - see config.log)"
+ configlog "$pkgconfig --cflags $module"
+ configlog $cflags
return 0
}
msg-result $version
set prefix [feature-define-name $module PKG_]
define HAVE_${prefix}
define ${prefix}_VERSION $version
- define ${prefix}_LIBS [exec pkg-config --libs-only-l $module]
- define ${prefix}_LDFLAGS [exec pkg-config --libs-only-L $module]
- define ${prefix}_CFLAGS [exec pkg-config --cflags $module]
+ define ${prefix}_CFLAGS $cflags
+ define ${prefix}_LIBS [exec $pkgconfig --libs-only-l $module]
+ define ${prefix}_LDFLAGS [exec $pkgconfig --libs-only-L $module]
return 1
}
# @pkg-config-get module setting
#
@@ -131,5 +146,22 @@
# the value of 'PKG_PANGO_CFLAGS', or '""' if not defined.
proc pkg-config-get {module name} {
set prefix [feature-define-name $module PKG_]
get-define ${prefix}_${name} ""
}
+
+# @pkg-config-get-var module variable
+#
+# Return the value of the given variable from the given pkg-config module.
+# The module must already have been successfully detected with pkg-config.
+# e.g.
+#
+## if {[pkg-config harfbuzz >= 2.5]} {
+## define harfbuzz_libdir [pkg-config-get-var harfbuzz libdir]
+## }
+#
+# Returns the empty string if the variable isn't defined.
+proc pkg-config-get-var {module variable} {
+ set pkgconfig [get-define PKG_CONFIG]
+ set prefix [feature-define-name $module HAVE_PKG_]
+ exec $pkgconfig $module --variable $variable
+}
Index: autosetup/system.tcl
==================================================================
--- autosetup/system.tcl
+++ autosetup/system.tcl
@@ -25,11 +25,11 @@
if {[is-defined defaultprefix]} {
user-notice "Note: defaultprefix is deprecated. Use options-defaults to set default options"
options-defaults [list prefix [get-define defaultprefix]]
}
-module-options [subst -noc -nob {
+options {
host:host-alias => {a complete or partial cpu-vendor-opsys for the system where
the application will run (defaults to the same value as --build)}
build:build-alias => {a complete or partial cpu-vendor-opsys for the system
where the application will be built (defaults to the
result of running config.guess)}
@@ -50,11 +50,11 @@
localstatedir:
runstatedir:
maintainer-mode=0
dependency-tracking=0
silent-rules=0
-}]
+}
# @check-feature name { script }
#
# defines feature '$name' to the return value of '$script',
# which should be 1 if found or 0 if not found.
Index: autosetup/tmake.tcl
==================================================================
--- autosetup/tmake.tcl
+++ autosetup/tmake.tcl
@@ -9,11 +9,11 @@
#
## CONFIGURED - to indicate that the project is configured
use system
-module-options {}
+options {}
define CONFIGURED
# @make-tmake-settings outfile patterns ...
#
Index: bin/os8-cp.in
==================================================================
--- bin/os8-cp.in
+++ bin/os8-cp.in
@@ -1,14 +1,14 @@
-#!/usr/bin/env python
+#!/usr/bin/env @PYCMD@
# -*- coding: utf-8 -*-
########################################################################
# Generalized facility to manipulate os8 device images from the POSIX
# (host) side using OS/8 system programs under SIMH.
#
# See USAGE message below for details.
#
-# Copyright © 2018 by Bill Cattey and Warren Young
+# Copyright © 2018-2019 by Bill Cattey and Warren Young
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
@@ -110,11 +110,11 @@
#### abort_prog ########################################################
# Print err_string and exit with -1 return status.
def abort_prog (err_str):
- print "Abort: " + err_str
+ print("Abort: " + err_str)
sys.exit(-1)
#### parse_attach ######################################################
# Parser for OS/8 attach spec.
@@ -124,11 +124,11 @@
abort_prog ("Need unit number for: " + match.group(1) + ".")
image_spec = [match.group(1), match.group(2), imagename]
if match.group(3) == 's':
if action_plan ["sys"] != None:
- print ("Already specified system device. Ignoring sys mount of: " + imagename)
+ print("Already specified system device. Ignoring sys mount of: " + imagename)
else:
action_plan["sys"] = image_spec
else:
action_plan["mount"].append(image_spec)
@@ -194,11 +194,11 @@
def parse_action_file(fname):
try:
manifest = open(fname, "r")
except IOError:
- print fname + " not found. Skipping."
+ print(fname + " not found. Skipping.")
return None
ioline_re = re.compile("(\S+)\s+(\S+)\s+(\S+)")
action_plan = {}
@@ -206,37 +206,38 @@
action_plan["mount"] = []
action_plan["copy"] = []
for line in manifest:
ioline = line.strip()
- if DEBUG: print "parse_action_file: ioline: " + ioline
+ if DEBUG: print("parse_action_file: ioline: " + ioline)
if ioline == "": continue
if ioline[0] == '#': continue # Allow comments
m=re.match(ioline_re, ioline)
if m== None:
- print "Ignoring line: " + ioline
+ print("Ignoring line: " + ioline)
continue
option = m.group(1)
source = m.group(2)
destination = m.group(3)
if option == "att":
m = re.match(_dev_actfile_re, source)
- if m== None:
+ if m == None:
abort_prog ("Could not parse attach spec: " + source)
parse_attach (action_plan, m, path_expand(destination))
else:
if len(option) != 1:
- print "Format options are only 1 letter in size. Ignoring line: " + ioline
+ print("Format options are only 1 letter in size. " + \
+ "Ignoring line: " + ioline)
elif option[0] not in _valid_pip_options:
- print "Unrecognize option in line: " + ioline
+ print("Unrecognize option in line: " + ioline)
elif source == None:
- print "Null value of source. Ignoring line: " + ioline
+ print("Null value of source. Ignoring line: " + ioline)
elif destination == None:
- print "Null value of destination. Ignoring line: " + ioline
+ print("Null value of destination. Ignoring line: " + ioline)
else:
append_copy(action_plan, option, source, destination)
return action_plan
@@ -254,16 +255,16 @@
def is_directory(path):
if DEBUG: "is_directory (" + path + ")"
m = re.match(_os8_file_re, path)
if m != None:
- if DEBUG: print "OS/8 Match: DEV: " + m.group(1) + ", File: " + str(m.group(2))
+ if DEBUG: print("OS/8 Match: DEV: " + m.group(1) + ", File: " + str(m.group(2)))
if m.group(2) == None or m.group(2) == "":
return True # Just a device so yes it's a directory.
else: return False
if has_os8_wildcards(path):
- if DEBUG: print "Has wildcards."
+ if DEBUG: print("Has wildcards.")
return False
return os.path.isdir(path)
#### has_os8_wildcards #################################################
@@ -286,11 +287,11 @@
for line in lines[1:]: # First line is our command. Skip it.
line = line.strip()
if line == "": continue
m = re.match("(\S+)\s*\.(\S+)", line)
if m == None: continue
- # if DEBUG: print "file_list_from_expect: group 1: " + m.group(1) + ", group 2: " + m.group(2)
+ # if DEBUG: print("file_list_from_expect: group 1: " + m.group(1) + ", group 2: " + m.group(2))
fname = m.group(1) + "." + m.group(2)
file_list.append(fname)
return file_list
@@ -310,11 +311,12 @@
if copy_type == "from":
copy_type = "within"
else:
copy_type = "into"
if "/" in destination:
- print "append_copy, into: Illegal OS/8 file spec containing a slash:" + destination
+ print("append_copy, into: Illegal OS/8 file spec containing " + \
+ "a slash:" + destination)
sys.exit(-1)
destination = destination.upper()
if copy_type == "":
abort_prog ("append_copy: No OS/8 file spec found with source: " + source + ", destination: " + destination)
@@ -513,13 +515,13 @@
# First the simple bit set options
if arg == "-d":
DEBUG = True
elif arg == "-h":
if VERBOSE:
- print VERBOSE_USAGE
+ print(VERBOSE_USAGE)
else:
- print USAGE
+ print(USAGE)
sys.exit(0)
elif arg == "-q":
QUIET = True
elif arg == "-v":
VERBOSE = 1
@@ -526,12 +528,12 @@
# look for option args.
elif arg in _arg_to_option:
new_opt = _arg_to_option[arg]
if mode_opt == new_opt:
- print "Warning redundant reset of mode option to " + \
- _pip_option_info[new_opt]
+ print("Warning redundant reset of mode option to " + \
+ _pip_option_info[new_opt])
mode_opt = new_opt
# Not a simple bit set option.
elif arg == "--action-file":
@@ -553,29 +555,34 @@
parse_attach (action_plan, m, sys.argv[idx])
# Do file parser if we didn't get an OS/8 attach spec.
else:
- if DEBUG: print "File parsing of: " + arg
+ if DEBUG: print("File parsing of: " + arg)
# Need to know if arg is Linux. If so, we need to do globbing.
# If you want OS/8 globbing, specify a device to prevent globbing
# from being run.
m = re.match(_os8_file_re, arg)
if m == None: # Yup, it's POSIX. Glob it.
- if DEBUG: print arg + " is POSIX."
+ if DEBUG: print(arg + " is POSIX.")
more_files = glob.glob(arg)
if more_files == []:
- if DEBUG: print "No more files in POSIX Glob. Our file is: " + arg
+ if DEBUG:
+ print("No more files in POSIX Glob. Our file is: " + arg)
more_files.append(arg) # If file not found may be an OS/8 internal xfer.
for new_file in more_files:
if filespec_seen == 0:
source = new_file
first_mode = mode_opt
- if DEBUG: print "Globber: Setting initial source: " + source + " and mode: " + first_mode
+ if DEBUG:
+ print("Globber: Setting initial source: " + source + \
+ " and mode: " + first_mode)
elif filespec_seen == 1:
- if DEBUG: print "Globber: Setting initial destination: " + destination
+ if DEBUG:
+ print("Globber: Setting initial destination: " + \
+ destination)
destination = new_file
else:
if DEBUG: "Globber: Appending destination to list. New file is: " + new_file
file_and_mode_list.append([mode_opt,destination])
destination = new_file
@@ -582,14 +589,16 @@
filespec_seen += 1
else:
if filespec_seen == 0:
source = arg
first_mode = mode_opt
- if DEBUG: print "Setting initial source: " + source + " and mode: " + first_mode
+ if DEBUG:
+ print("Setting initial source: " + source + \
+ " and mode: " + first_mode)
elif filespec_seen == 1:
destination = arg
- if DEBUG: print "Setting initial destination: " + destination
+ if DEBUG: print("Setting initial destination: " + destination)
else:
file_and_mode_list.append([mode_opt, destination])
destination = arg
if DEBUG: "Appending destination to list. New file is: " + new_file
filespec_seen += 1
@@ -603,11 +612,11 @@
# Now it gets a little complicated...
# If neither source nor destination is OS/8, pretend they both were OS/8 "DSK:"
# If source is OS/8, and has OS/8 wild cards, the destination must be a directory.
else:
# If more than 2 files, the destination must be either an OS/8 device or a Linux directory.
- if DEBUG: print "Destination: " + destination
+ if DEBUG: print("Destination: " + destination)
if filespec_seen > 2 and is_directory(destination) == False:
abort_prog ("Destination must be a Linux directory or OS/8 device for multiple source files.")
m1 = re.match(_os8_file_re, source)
m2 = re.match(_os8_file_re, destination)
@@ -638,20 +647,20 @@
def main ():
action_plan = parse_args()
if action_plan == None:
abort_prog ("No action plan was parsed.")
- if DEBUG: print str(action_plan)
+ if DEBUG: print(str(action_plan))
# Create the SIMH child instance and tell it where to send log output
try:
s = simh (dirs.build, True)
except (RuntimeError) as e:
- print "Could not start simulator: " + e.message + '!'
+ print("Could not start simulator: " + e.message + '!')
exit (1)
- # s.set_logfile (os.fdopen (sys.stdout.fileno (), 'w', 0))
- s.set_logfile (open ("logfile.txt", 'w'))
+ # s.set_logfile (os.fdopen (sys.stdout.fileno (), 'wb', 0))
+ s.set_logfile (open ("logfile.txt", 'wb'))
if VERBOSE: s.verbose = True
# Perform sys attach
att_spec = action_plan["sys"]
if att_spec == None: att_spec = _default_att_spec
@@ -658,42 +667,48 @@
simh_boot_dev = att_spec[0] + att_spec[1] # Compose simh dev from name and unit.
imagename = att_spec[2]
if not os.path.exists (imagename):
abort_prog ("Requested boot image file: " + imagename + " not found.")
if VERBOSE or DEBUG:
- print "Attaching " + simh_boot_dev + " to " + imagename
+ print("Attaching " + simh_boot_dev + " to " + imagename)
s.send_cmd ("att " + simh_boot_dev + " " + imagename)
images_to_zero = []
# Attach other mounts
for att_spec in action_plan["mount"]:
simh_dev = att_spec[0] + att_spec[1] # Compose simh dev from name and unit.
imagename = att_spec[2]
if os.path.exists (imagename):
- if VERBOSE or DEBUG: print "Modifying existing " + simh_dev + " image " + imagename
+ if VERBOSE or DEBUG:
+ print("Modifying existing " + simh_dev + " image " + imagename)
else:
- if VERBOSE or DEBUG: print "Will create a new image file named: " + imagename
+ if VERBOSE or DEBUG:
+ print("Will create a new image file named: " + imagename)
# Save this att_spec so we can zero it later.
images_to_zero.append (att_spec)
if VERBOSE or DEBUG:
- print "Attaching " + simh_dev + " to " + imagename
+ print("Attaching " + simh_dev + " to " + imagename)
s.send_cmd ("att " + simh_dev + " " + imagename)
- if VERBOSE or DEBUG: print "Booting " + simh_boot_dev + "..."
+ if VERBOSE or DEBUG: print("Booting " + simh_boot_dev + "...")
s.send_cmd ("boot " + simh_boot_dev)
for att_spec in images_to_zero:
os8dev = _os8_from_simh_dev[att_spec[0]]
if os8dev in _os8_partitions:
for partition in _os8_partitions[os8dev]:
os8name = os8dev + partition + att_spec[1] + ":"
- if VERBOSE or DEBUG: print "Initializing directory of " + os8name + " in " + imagename
+ if VERBOSE or DEBUG:
+ print("Initializing directory of " + os8name + " in " + \
+ imagename)
s.os8_send_cmd ('\\.', "ZERO " + os8name)
else:
os8name = os8dev + att_spec[1] + ":"
- if VERBOSE or DEBUG: print "Initializing directory of " + os8name + " in " + imagename
+ if VERBOSE or DEBUG:
+ print("Initializing directory of " + os8name + " in " + \
+ imagename)
s.os8_send_cmd ('\\.', "ZERO " + os8name)
# Perform copy operations
for do_copy in action_plan["copy"]:
mode_opt = do_copy[0]
@@ -703,11 +718,13 @@
if mode_opt in _option_to_pip:
pip_option = _option_to_pip[mode_opt]
else:
abort_prog ("Unrecognized mode option: " + mode_opt)
- if DEBUG: print "Source: " + source + ", Destination: " + destination + ", Mode: " + mode_opt + "."
+ if DEBUG:
+ print("Source: " + source + ", Destination: " + destination + \
+ ", Mode: " + mode_opt + ".")
# Is this "from" OS/8 to POSIX, "into" OS/8 from POSIX or "within" OS/8?
# "into" -- Attach source to simh ptr
# If we are operating "from" and source has wild cards,
# Use DIRECT to create list of files.
# "from" -- Attach destination to ptp. We've already done POSIX globing.
@@ -717,48 +734,50 @@
s.os8_pip_to(source, destination, pip_option)
elif copy_type == "from":
if has_os8_wildcards(source):
# Split off device from source:
os8dev = source[0:source.index(":")+1]
- if DEBUG: print "Wild card dev: " + os8dev
+ if DEBUG: print("Wild card dev: " + os8dev)
# Use OS/8 Direct to enumerate our input files.
- if DEBUG: print "Calling OS/8 DIRECT on wild card filespec: " + source
+ if DEBUG:
+ print("Calling OS/8 DIRECT on wild card filespec: " + source)
s.os8_send_cmd ('\\.', "DIR " + source + "/F=1")
# Now harvest direct output. One file per line. Ignore blank lines.
# Maybe parse the FREE BLOCKS Output.
# Done when we see a dot.
s._child.expect("\d+\s+FREE BLOCKS")
files = file_list_from_expect(s._child.before)
for filename in files:
if VERBOSE or DEBUG:
- print "Wildcard call os8_pip_from: copy from: {" + os8dev + "}{" + filename + "}" + \
- " to: " + destination + ", mode: " + pip_option
+ print("Wildcard call os8_pip_from: copy from: " + \
+ "{" + os8dev + "}{" + filename + "}" + \
+ " to: " + destination + ", mode: " + pip_option)
s.os8_pip_from(os8dev + filename, destination, pip_option)
else:
if VERBOSE or DEBUG:
- print "Call os8_pip_from: copy from: " + source + " to " + destination + \
- ", mode: " + pip_option
+ print("Call os8_pip_from: copy from: " + source + " to " + \
+ destination + ", mode: " + pip_option)
s.os8_pip_from(source, destination, pip_option)
elif copy_type == "within":
if VERBOSE or DEBUG:
- print "Call COPY of: " + source + " to " + destination
+ print("Call COPY of: " + source + " to " + destination)
s.os8_send_cmd ('\\.', "COPY " + destination + "< " + source)
else:
abort_prog ("Unrecognized copy type: " + copy_type) # Should never happen.
# Detach all mounts and then sys.
s.back_to_cmd ('\\.')
for att_spec in action_plan["mount"]:
simh_dev = att_spec[0] + att_spec[1] # Compose simh dev from name and unit.
if VERBOSE or DEBUG:
- print "Detaching " + simh_dev
+ print("Detaching " + simh_dev)
s.send_cmd ("det " + simh_dev)
if VERBOSE or DEBUG:
- print "Detaching " + simh_boot_dev
+ print("Detaching " + simh_boot_dev)
s.send_cmd ("det " + simh_boot_dev)
# And shut down the simulator.
if VERBOSE or DEBUG:
- print "Quitting simh."
+ print("Quitting simh.")
s.send_cmd ('quit')
if __name__ == "__main__": main()
DELETED bin/os8-run
Index: bin/os8-run
==================================================================
--- bin/os8-run
+++ /dev/null
@@ -1,228 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-########################################################################
-# Script runner for OS/8 under SIMH.
-# The library module os8script.py does the heavy lifting.
-#
-# See USAGE message below for details.
-#
-# Copyright © 2018 by Bill Cattey and Warren Young
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM,
-# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
-# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
-# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-#
-# Except as contained in this notice, the names of the authors above
-# shall not be used in advertising or otherwise to promote the sale,
-# use or other dealings in this Software without prior written
-# authorization from those authors.
-########################################################################
-
-# Bring in just the basics so we can bring in our local modules
-import os
-import sys
-
-sys.path.insert (0, os.path.dirname (__file__) + '/../lib')
-sys.path.insert (0, os.getcwd () + '/lib')
-
-# Remaining Python core modules
-import subprocess
-import string
-import re
-import shutil
-import argparse
-from itertools import chain
-
-# Our local modules
-from pidp8i import *
-from simh import *
-from os8script import *
-
-
-#### GLOBALS AND CONSTANTS #############################################
-
-DEBUG = False
-VERBOSE = False
-VERY_VERBOSE = False
-QUIET = False
-SCRIPT_FILE = ""
-
-USAGE = "usage: " + os.path.basename (__file__) + \
- " [-h] [-d] [-v] [-vv] [--enable enable_option] ... \n\t[--disable disable_option] ... " + \
- "\n \tscript [script] ... "
-
-_en_dis_arg_re = re.compile("^(enable|disable)_(\S+)$")
-
-
-def opt_set(en_dis, option, options_enabled, options_disabled):
- if option == None or en_dis == None: return
- # print en_dis + " " + option
- if en_dis == "enable":
- if option not in options_enabled:
- options_enabled.append(option)
- if option in options_disabled:
- options_disabled.remove(option)
- elif en_dis == "disable":
- if option not in options_disabled:
- options_disabled.append(option)
- if option in options_enabled:
- options_enabled.remove(option)
- else: return
-
-
-def add_bool (self, *args, **kwargs):
- kwargs['action'] = 'store_true'
- kwargs['default'] = False
- self.add_argument (*args, **kwargs)
-
-
-#### parse_args ########################################################
-
-def parse_args (script_files, options_enabled, options_disabled):
- global DEBUG
- global VERBOSE
- global VERY_VERBOSE
- global USAGE
-
- enable_usage = ""
- disable_usage = ""
-
- idx = 1
- numargs = len(sys.argv)
-
- if numargs < 2:
- print USAGE
- sys.exit(-1)
-
- # Add arguments corresponding to --*-os8-* configure script options
- max_obn_len = 0
- for obn, vals in os8opts.opts.iteritems():
- max_obn_len = max(max_obn_len, len(obn))
- for obn, vals in os8opts.opts.iteritems():
- if vals[0]:
- # Enable option
- pad_str = (max_obn_len - len (obn)) * " "
- new_usage = " " + obn + ": " + pad_str + vals[1] + "\n"
- disable_usage += new_usage
- else:
- # Disable option
- pad_str = (max_obn_len - len (obn)) * " "
- new_usage = " " + obn + ": " + pad_str + vals[1] + "\n"
- enable_usage += new_usage
-
- if enable_usage != "":
- USAGE += "\n Known enable options: \n"
- USAGE += enable_usage
-
- if disable_usage != "":
- USAGE += "\n Known disable options: \n"
- USAGE += disable_usage
-
- while idx < numargs:
- arg = sys.argv[idx]
- # print "idx: " + str(idx) + ", arg: " + arg
- # print "Files: " + str(script_files)
- # print "Options: " + str(options_enabled)
- if arg == "-d" or arg == "--debug":
- DEBUG = True
- elif arg == "-h" or arg == "--help":
- print USAGE
- sys.exit(0)
- elif arg == "-v" or arg == "--verbose":
- VERBOSE = 1
- elif arg == "-vv" or arg == "--very-verbose":
- VERY_VERBOSE = 1
- elif arg == "--enable":
- idx +=1
- if idx == numargs:
- print "expecting an option but got none."
- else:
- option = sys.argv[idx]
- # Only add the option once
- if option not in options_enabled: options_enabled.append(option)
- elif arg == "--disable":
- idx +=1
- if idx == numargs:
- print "expecting an option but got none."
- else:
- option = sys.argv[idx]
- # Only add the option once
- if option not in options_disabled: options_disabled.append(option)
- else:
- script_files.append(arg)
- idx += 1
-
-
-
-#### main ##############################################################
-# Program entry point. Parses the command line and drives the above.
-
-def main ():
- script_files = []
- options_enabled = []
- options_disabled = []
-
- parse_args (script_files, options_enabled, options_disabled)
- if len(script_files) == 0:
- print "Need a script file to run."
- sys.exit(-1)
-
- if VERBOSE:
- print "script_files: " + str(script_files)
- if DEBUG:
- print "options_enabled" + str(options_enabled)
- print "options_disabled" + str(options_disabled)
-
- # Append SIMH and OS/8 output to a file by default.
- #
- # We append because we're run twice in each test directory via the
- # os8-sys Makefile target called by test-os8-run, once for the "dist"
- # media and once for the actual RK05 bootable media. So, the second
- # run must append its logs to the first run's log file.
- #
- # Send the log info to the console instead of the progress messages if
- # -v was given. See https://stackoverflow.com/questions/21239338
- s = simh (dirs.build, True)
- if VERBOSE: s.verbose = True
- s.set_logfile (open (dirs.log + 'os8-run' + '.log', 'a') \
- if not VERY_VERBOSE else os.fdopen (sys.stdout.fileno (), 'w', 0))
-
- os8 = os8script (s, options_enabled, options_disabled, verbose=VERBOSE, debug=DEBUG)
-
- for script_file in script_files:
- if VERBOSE:
- print os.path.basename (__file__) + " -- Language Version: " + os8.lang_version
-
- print "Running script file: " + script_file
- os8.run_script_file (script_file)
-
- # After all scripts are done, we remove any scratch files,
- # detach any mounted devices, and shut down simh gracefully.
-
- for filename in os8.scratch_list:
- if os8.verbose: print "Deleting scratch_copy: " + filename
- os.remove(filename)
-
- s.send_cmd ("detach all")
-
- s.quit ()
- if VERBOSE: print "Done!"
-
-
-if __name__ == "__main__":
- main()
ADDED bin/os8-run.in
Index: bin/os8-run.in
==================================================================
--- /dev/null
+++ bin/os8-run.in
@@ -0,0 +1,228 @@
+#!/usr/bin/env @PYCMD@
+# -*- coding: utf-8 -*-
+########################################################################
+# Script runner for OS/8 under SIMH.
+# The library module os8script.py does the heavy lifting.
+#
+# See USAGE message below for details.
+#
+# Copyright © 2018-2019 by Bill Cattey and Warren Young
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM,
+# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
+# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
+# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+# Except as contained in this notice, the names of the authors above
+# shall not be used in advertising or otherwise to promote the sale,
+# use or other dealings in this Software without prior written
+# authorization from those authors.
+########################################################################
+
+# Bring in just the basics so we can bring in our local modules
+import os
+import sys
+
+sys.path.insert (0, os.path.dirname (__file__) + '/../lib')
+sys.path.insert (0, os.getcwd () + '/lib')
+
+# Remaining Python core modules
+import subprocess
+import string
+import re
+import shutil
+import argparse
+from itertools import chain
+
+# Our local modules
+from pidp8i import *
+from simh import *
+from os8script import *
+
+
+#### GLOBALS AND CONSTANTS #############################################
+
+DEBUG = False
+VERBOSE = False
+VERY_VERBOSE = False
+QUIET = False
+SCRIPT_FILE = ""
+
+USAGE = "usage: " + os.path.basename (__file__) + \
+ " [-h] [-d] [-v] [-vv] [--enable enable_option] ... \n\t[--disable disable_option] ... " + \
+ "\n \tscript [script] ... "
+
+_en_dis_arg_re = re.compile("^(enable|disable)_(\S+)$")
+
+
+def opt_set(en_dis, option, options_enabled, options_disabled):
+ if option == None or en_dis == None: return
+ # print(en_dis + " " + option)
+ if en_dis == "enable":
+ if option not in options_enabled:
+ options_enabled.append(option)
+ if option in options_disabled:
+ options_disabled.remove(option)
+ elif en_dis == "disable":
+ if option not in options_disabled:
+ options_disabled.append(option)
+ if option in options_enabled:
+ options_enabled.remove(option)
+ else: return
+
+
+def add_bool (self, *args, **kwargs):
+ kwargs['action'] = 'store_true'
+ kwargs['default'] = False
+ self.add_argument (*args, **kwargs)
+
+
+#### parse_args ########################################################
+
+def parse_args (script_files, options_enabled, options_disabled):
+ global DEBUG
+ global VERBOSE
+ global VERY_VERBOSE
+ global USAGE
+
+ enable_usage = ""
+ disable_usage = ""
+
+ idx = 1
+ numargs = len(sys.argv)
+
+ if numargs < 2:
+ print(USAGE)
+ sys.exit(-1)
+
+ # Add arguments corresponding to --*-os8-* configure script options
+ max_obn_len = 0
+ for obn, vals in os8opts.opts.items():
+ max_obn_len = max(max_obn_len, len(obn))
+ for obn, vals in os8opts.opts.items():
+ if vals[0]:
+ # Enable option
+ pad_str = (max_obn_len - len (obn)) * " "
+ new_usage = " " + obn + ": " + pad_str + vals[1] + "\n"
+ disable_usage += new_usage
+ else:
+ # Disable option
+ pad_str = (max_obn_len - len (obn)) * " "
+ new_usage = " " + obn + ": " + pad_str + vals[1] + "\n"
+ enable_usage += new_usage
+
+ if enable_usage != "":
+ USAGE += "\n Known enable options: \n"
+ USAGE += enable_usage
+
+ if disable_usage != "":
+ USAGE += "\n Known disable options: \n"
+ USAGE += disable_usage
+
+ while idx < numargs:
+ arg = sys.argv[idx]
+ # print("idx: " + str(idx) + ", arg: " + arg)
+ # print("Files: " + str(script_files))
+ # print("Options: " + str(options_enabled))
+ if arg == "-d" or arg == "--debug":
+ DEBUG = True
+ elif arg == "-h" or arg == "--help":
+ print(USAGE)
+ sys.exit(0)
+ elif arg == "-v" or arg == "--verbose":
+ VERBOSE = 1
+ elif arg == "-vv" or arg == "--very-verbose":
+ VERY_VERBOSE = 1
+ elif arg == "--enable":
+ idx +=1
+ if idx == numargs:
+ print("expecting an option but got none.")
+ else:
+ option = sys.argv[idx]
+ # Only add the option once
+ if option not in options_enabled: options_enabled.append(option)
+ elif arg == "--disable":
+ idx +=1
+ if idx == numargs:
+ print("expecting an option but got none.")
+ else:
+ option = sys.argv[idx]
+ # Only add the option once
+ if option not in options_disabled: options_disabled.append(option)
+ else:
+ script_files.append(arg)
+ idx += 1
+
+
+
+#### main ##############################################################
+# Program entry point. Parses the command line and drives the above.
+
+def main ():
+ script_files = []
+ options_enabled = []
+ options_disabled = []
+
+ parse_args (script_files, options_enabled, options_disabled)
+ if len(script_files) == 0:
+ print("Need a script file to run.")
+ sys.exit(-1)
+
+ if VERBOSE:
+ print("script_files: " + str(script_files))
+ if DEBUG:
+ print("options_enabled" + str(options_enabled))
+ print("options_disabled" + str(options_disabled))
+
+ # Append SIMH and OS/8 output to a file by default.
+ #
+ # We append because we're run twice in each test directory via the
+ # os8-sys Makefile target called by test-os8-run, once for the "dist"
+ # media and once for the actual RK05 bootable media. So, the second
+ # run must append its logs to the first run's log file.
+ #
+ # Send the log info to the console instead of the progress messages if
+ # -v was given. See https://stackoverflow.com/questions/21239338
+ s = simh (dirs.build, True)
+ if VERBOSE: s.verbose = True
+ s.set_logfile (open (dirs.log + 'os8-run' + '.log', 'ab') \
+ if not VERY_VERBOSE else os.fdopen (sys.stdout.fileno (), 'wb', 0))
+
+ os8 = os8script (s, options_enabled, options_disabled, verbose=VERBOSE, debug=DEBUG)
+
+ for script_file in script_files:
+ if VERBOSE:
+ print(os.path.basename (__file__) + " -- Language Version: " + os8.lang_version)
+
+ print("Running script file: " + script_file)
+ os8.run_script_file (script_file)
+
+ # After all scripts are done, we remove any scratch files,
+ # detach any mounted devices, and shut down simh gracefully.
+
+ for filename in os8.scratch_list:
+ if os8.verbose: print("Deleting scratch_copy: " + filename)
+ os.remove(filename)
+
+ s.send_cmd ("detach all")
+
+ s.quit ()
+ if VERBOSE: print("Done!")
+
+
+if __name__ == "__main__":
+ main()
Index: bin/pidp8i.in
==================================================================
--- bin/pidp8i.in
+++ bin/pidp8i.in
@@ -1,11 +1,11 @@
#!/bin/bash
########################################################################
# pidp8i.in - Collection of scriptlets for dealing with the pidp8i
# systemd user service and its associated screen manager session.
#
-# Copyright © 2015-2019 Oscar Vermeulen and Warren Young
+# Copyright © 2015-2020 Oscar Vermeulen and Warren Young
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
@@ -40,23 +40,34 @@
if [ -e "$prefix/etc/pidp8i.rc" ]
then
. "$prefix/etc/pidp8i.rc"
fi
[ -n "$SCREEN_MANAGER" ] || SCREEN_MANAGER=screen
+if [ ! -t 1 ] && [ "$SCREEN_MANAGER" = "none" ]
+then
+ echo "The 'none' screen manager mode is meant for interactive use only!"
+ exit 1
+fi
is_running() {
- if [ "$SCREEN_MANAGER" = "tmux" ]
+ if [ "$SCREEN_MANAGER" = "none" ]
+ then
+ return 1
+ elif [ "$SCREEN_MANAGER" = "tmux" ]
then
tmux has-session -t pidp8i 2>/dev/null
else
procs=`screen -list pidp8i | $ggrep -Pc '\d\.pidp8i'`
test -n "$procs" && test $procs -gt 0 && return 0 || return 1
fi
}
sim_child_pid() {
- if [ "$SCREEN_MANAGER" = "tmux" ]
+ if [ "$SCREEN_MANAGER" = "none" ]
+ then
+ echo ""
+ elif [ "$SCREEN_MANAGER" = "tmux" ]
then
tmux list-sessions -F '#{session_name} #{pid}' | awk '/^pidp8i / {print $2}'
else
screen -ls pidp8i | grep -Eo '[[:digit:]]+\.pidp8i' | grep -Eo '^[[:digit:]]+'
fi
@@ -63,12 +74,15 @@
}
# Were we given a command line argument?
if [ -z "$1" ]
then
- # No, so just try to attach to the running simulator
- if is_running
+ # No, so try to run or attach to the running simulator
+ if [ "$SCREEN_MANAGER" = "none" ]
+ then
+ exec $0 start
+ elif is_running
then
echo Joining simulator session already in progress...
if [ "$SCREEN_MANAGER" = "tmux" ]
then
exec tmux attach-session -d -t pidp8i
@@ -145,20 +159,31 @@
# commands involving file paths. This default is chosen because it
# satisfies both criteria.
# If you change the default here, change that script as well.
set -e
cd "$prefix/share/media"
- if [ "$SCREEN_MANAGER" = "tmux" ]
+
+ # Start the simulator
+ if [ "$SCREEN_MANAGER" = "none" ]
+ then
+ exec "$sim" "$bscript"
+ elif [ "$SCREEN_MANAGER" = "tmux" ]
then
tmux new-session -s pidp8i -d "$sim" "$bscript"
else
screen -dm -S pidp8i "$sim" "$bscript"
fi
- if [ -x $systemctl ]
+
+ # Tell systemd where to find the backgrounded simulator so it can
+ # stop it via systemctl. Skipped on non-systemd systems and where
+ # SCREEN_MANAGER=none.
+ scpid=$(sim_child_pid)
+ if [ -x $systemctl ] && [ -n "$scpid" ] && [ $scpid -gt 0 ]
then
- systemd-notify --ready --pid=$(sim_child_pid)
+ systemd-notify --ready --pid=$scpid
fi
+
exit 0
elif [ "$verb" = "stop" ]
then
# Someone (maybe the user via "pidp8i stop", maybe systemd via
# "systemctl pidp8i stop") is telling us to stop the background
@@ -177,21 +202,21 @@
echo -n "Stopping $sim simulator..."
for sig in TERM KILL ; do pkill -$sig $sim ; sleep 1 ; done
# The screen manager might still be running despite its
# only child dying. Nuke it hard if so.
- if is_running
- then
- if [ "$SCREEN_MANAGER" = "tmux" ]
- then
- tmux kill-session -t pidp8i
- else
- screen -S pidp8i -X quit
- fi
+ if ! is_running || [ "$SCREEN_MANAGER" = "none" ]
+ then
+ echo "The PiDP-8/I simulator is stopped."
+ elif [ "$SCREEN_MANAGER" = "tmux" ]
+ then
+ tmux kill-session -t pidp8i
+ else
+ screen -S pidp8i -X quit
fi
exit 0
fi
done
exit 1
fi
DELETED bin/teco-pi-demo
Index: bin/teco-pi-demo
==================================================================
--- bin/teco-pi-demo
+++ /dev/null
@@ -1,185 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-########################################################################
-# teco-pi-demo - Starts the simulator with the OS/8, sends one of the
-# famous TECO "calculate pi" program to it, and starts it running at
-# a very slow rate of speed to act as a blinkenlights demo.
-#
-# Copyright © 2017-2019 by Warren Young.
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM,
-# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
-# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
-# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-#
-# Except as contained in this notice, the names of the authors above
-# shall not be used in advertising or otherwise to promote the sale,
-# use or other dealings in this Software without prior written
-# authorization from those authors.
-########################################################################
-
-# Bring in just the basics so we can bring in our local modules
-import os
-import sys
-sys.path.insert (0, os.path.dirname (__file__) + '/../lib')
-sys.path.insert (0, os.getcwd () + '/lib')
-
-# Other core modules we need
-from datetime import datetime
-import time
-
-# Our local modules
-from pidp8i import *
-from simh import *
-
-
-#### main ##############################################################
-
-def main ():
- # Check for command line flags
- benchmark = len (sys.argv) > 1 and sys.argv[1] == '-b'
-
- # Create the SIMH child instance and tell it where to send log output
- try:
- s = simh (dirs.build)
- except (RuntimeError) as e:
- print "Could not start simulator: " + e.message + '!'
- exit (1)
- s.set_logfile (os.fdopen (sys.stdout.fileno (), 'w', 0))
-
- # Find and boot the built OS/8 bin disk
- rk = os.path.join (dirs.os8mo, 'v3d.rk05')
- if not os.path.isfile (rk):
- print "Could not find " + rk + "; OS/8 media not yet built?"
- exit (1)
- print "Booting " + rk + "..."
- s.send_cmd ("att rk0 " + rk)
- s.send_cmd ("boot rk0")
-
- # Start TECO8 in the simulator under OS/8
- s.os8_send_cmd ('\\.', "R TECO")
-
- # The macro comes from http://www.iwriteiam.nl/HaPi_TECO_macro.html
- # and it was created by Stanley Rabinowitz.
- #
- # The 248 preceding "UN" in the first line of the macro is the number
- # of digits of pi to calculate. That value was reached by experiment
- # as the largest value that runs without crashing TECO with a
- #
- # ?MEM STORAGE CAPACITY EXCEEDED
- #
- # error. You can see that by increasing the value below, commenting
- # out the throttle setting below, and running the demo. On a Pi 3, it
- # should take a bit over an hour to complete, if it doesn't error out.
- #
- # With the simulator throttled, generating 248 digits takes 17 years!
- #
- # That is based on generating 1 digit every ~16 seconds on a Pi 3 when
- # running unthrottled, roughly 8 MIPS. When throttled to 59 IPS — or
- # 17ms per instruction, as below — you multiply the seconds by the
- # factor 8 MIPS / 59 IPS = ~136000, giving about 2.2 million seconds
- # per digit. Multiplying that by 248 gives ~17 years.
- macro = [
- 'GZ0J\UNQN"E 248UN \' BUH BUV HK',
- 'QN< J BUQ QN*10/3UI',
- 'QI< \+2*10+(QQ*QI)UA B L K QI*2-1UJ QA/QJUQ',
- 'QA-(QQ*QJ)-2\ 10@I// -1%I >',
- 'QQ/10UT QH+QT+48UW QW-58"E 48UW %V \' QV"N QV^T \' QWUV QQ-(QT*10)UH >',
- 'QV^T @^A/',
- '/HKEX',
- ]
-
- # First and last lines are handled specially, so slice them off.
- first = macro.pop (0)
- last = macro.pop ()
-
- # Send the first line of the macro; implicitly awaits 1st TECO prompt
- s.os8_send_cmd ('\\*', first)
-
- # Blindly send core lines of the macro; TECO gives no prompts for 'em.
- for line in macro:
- s.os8_send_line (line)
-
- # Send last line of macro sans CR, followed by two Esc characters to
- # start it running.
- s.os8_send_str (last) # not os8_send_line!
- s.os8_send_ctrl ('[')
- s.os8_send_ctrl ('[')
-
- if benchmark:
- # Run demo long enough to get a good sense of the simulator's
- # execution rate while unthrottled on this host hardware. If
- # you don't run it long enough, the IPS value is untrustworthy.
- try:
- s.spin (10)
- except pexpect.TIMEOUT:
- # Explicitly shift back from OS/8 context to SIMH command context.
- # We cannot rely on class simh to do this automatically because it
- # expects to see a . prompt from the prior command, but we're
- # still in TECO here, so we must be explicit.
- s.os8_send_ctrl ('e')
-
- # Ask the simulator what IPS rate we ran that benchmark at.
- s.send_cmd ('show clocks')
- line = s.read_tail ('Execution Rate:')
- curr_ips = int (line.strip().replace(',', '').split(' ')[0])
- pf = open ('lib/pidp8i/ips.py', 'a')
- pf.write ('current = ' + str (curr_ips) + ' # ' + \
- str (datetime.today ()) + '\n')
- pf.close ()
- s.send_cmd ('quit')
- pdp_ratio = float (curr_ips) / ips.pdp8i
- rpi_ratio = float (curr_ips) / ips.raspberry_pi_b_plus
- print "\nYour system is " + format (rpi_ratio, '.1f') + \
- " times faster than a Raspberry Pi Model B+"
- print "or " + format (pdp_ratio, '.1f') + \
- " times faster than a PDP-8/I.\n"
- else:
- # Normal mode. Tell SIMH and throttle down to a rate suitable for a
- # blinkenlights demo. 1/17 means SIMH runs one instruction then
- # waits for 17ms, yielding ~59 IPS.
- time.sleep (0.02) # FIXME: simulator chokes on 'cont' without this
- s.os8_send_ctrl ('e') # same justification as above
- s.send_cmd ('set throttle 1/17')
-
- # You can't hit Ctrl-E while running this script in the foreground
- # since pexpect takes over stdio. Therefore, if you want to be able
- # to send commands to the simulator while the demo is running,
- # uncomment the line below, which will let you send commands to the
- # simulator via telnet. From another terminal or SSH session:
- #
- # $ telnet localhost 3141
- #
- # or from a remote machine:
- #
- # $ telnet 192.168.1.2 3141
- #
- # It's disabled by default because SIMH can't be made to listen only
- # on localhost, so doing this may be a security risk. SIMH disables
- # obviously-unsafe commands like ! on the remote console, but it is
- # possible some mischief may be possible via this path anyway. It
- # could be used to exfiltrate a sensitive file via ATTACH, for one
- # thing. For another, it's a potential DoS vector.
- #s.send_cmd ('set remote telnet=3141')
-
- # Let it run. Never exits.
- s.send_cmd ('cont')
- s.spin ()
-
-
-if __name__ == "__main__":
- main()
ADDED bin/teco-pi-demo.in
Index: bin/teco-pi-demo.in
==================================================================
--- /dev/null
+++ bin/teco-pi-demo.in
@@ -0,0 +1,185 @@
+#!/usr/bin/env @PYCMD@
+# -*- coding: utf-8 -*-
+########################################################################
+# teco-pi-demo - Starts the simulator with the OS/8, sends one of the
+# famous TECO "calculate pi" program to it, and starts it running at
+# a very slow rate of speed to act as a blinkenlights demo.
+#
+# Copyright © 2017-2019 by Warren Young.
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM,
+# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
+# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
+# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+# Except as contained in this notice, the names of the authors above
+# shall not be used in advertising or otherwise to promote the sale,
+# use or other dealings in this Software without prior written
+# authorization from those authors.
+########################################################################
+
+# Bring in just the basics so we can bring in our local modules
+import os
+import sys
+sys.path.insert (0, os.path.dirname (__file__) + '/../lib')
+sys.path.insert (0, os.getcwd () + '/lib')
+
+# Other core modules we need
+from datetime import datetime
+import time
+
+# Our local modules
+from pidp8i import *
+from simh import *
+
+
+#### main ##############################################################
+
+def main ():
+ # Check for command line flags
+ benchmark = len (sys.argv) > 1 and sys.argv[1] == '-b'
+
+ # Create the SIMH child instance and tell it where to send log output
+ try:
+ s = simh (dirs.build)
+ except (RuntimeError) as e:
+ print "Could not start simulator: " + e.message + '!'
+ exit (1)
+ s.set_logfile (os.fdopen (sys.stdout.fileno (), 'wb', 0))
+
+ # Find and boot the built OS/8 bin disk
+ rk = os.path.join (dirs.os8mo, 'v3d.rk05')
+ if not os.path.isfile (rk):
+ print "Could not find " + rk + "; OS/8 media not yet built?"
+ exit (1)
+ print "Booting " + rk + "..."
+ s.send_cmd ("att rk0 " + rk)
+ s.send_cmd ("boot rk0")
+
+ # Start TECO8 in the simulator under OS/8
+ s.os8_send_cmd ('\\.', "R TECO")
+
+ # The macro comes from http://www.iwriteiam.nl/HaPi_TECO_macro.html
+ # and it was created by Stanley Rabinowitz.
+ #
+ # The 248 preceding "UN" in the first line of the macro is the number
+ # of digits of pi to calculate. That value was reached by experiment
+ # as the largest value that runs without crashing TECO with a
+ #
+ # ?MEM STORAGE CAPACITY EXCEEDED
+ #
+ # error. You can see that by increasing the value below, commenting
+ # out the throttle setting below, and running the demo. On a Pi 3, it
+ # should take a bit over an hour to complete, if it doesn't error out.
+ #
+ # With the simulator throttled, generating 248 digits takes 17 years!
+ #
+ # That is based on generating 1 digit every ~16 seconds on a Pi 3 when
+ # running unthrottled, roughly 8 MIPS. When throttled to 59 IPS — or
+ # 17ms per instruction, as below — you multiply the seconds by the
+ # factor 8 MIPS / 59 IPS = ~136000, giving about 2.2 million seconds
+ # per digit. Multiplying that by 248 gives ~17 years.
+ macro = [
+ 'GZ0J\UNQN"E 248UN \' BUH BUV HK',
+ 'QN< J BUQ QN*10/3UI',
+ 'QI< \+2*10+(QQ*QI)UA B L K QI*2-1UJ QA/QJUQ',
+ 'QA-(QQ*QJ)-2\ 10@I// -1%I >',
+ 'QQ/10UT QH+QT+48UW QW-58"E 48UW %V \' QV"N QV^T \' QWUV QQ-(QT*10)UH >',
+ 'QV^T @^A/',
+ '/HKEX',
+ ]
+
+ # First and last lines are handled specially, so slice them off.
+ first = macro.pop (0)
+ last = macro.pop ()
+
+ # Send the first line of the macro; implicitly awaits 1st TECO prompt
+ s.os8_send_cmd ('\\*', first)
+
+ # Blindly send core lines of the macro; TECO gives no prompts for 'em.
+ for line in macro:
+ s.os8_send_line (line)
+
+ # Send last line of macro sans CR, followed by two Esc characters to
+ # start it running.
+ s.os8_send_str (last) # not os8_send_line!
+ s.os8_send_ctrl ('[')
+ s.os8_send_ctrl ('[')
+
+ if benchmark:
+ # Run demo long enough to get a good sense of the simulator's
+ # execution rate while unthrottled on this host hardware. If
+ # you don't run it long enough, the IPS value is untrustworthy.
+ try:
+ s.spin (10)
+ except pexpect.TIMEOUT:
+ # Explicitly shift back from OS/8 context to SIMH command context.
+ # We cannot rely on class simh to do this automatically because it
+ # expects to see a . prompt from the prior command, but we're
+ # still in TECO here, so we must be explicit.
+ s.os8_send_ctrl ('e')
+
+ # Ask the simulator what IPS rate we ran that benchmark at.
+ s.send_cmd ('show clocks')
+ line = s.read_tail ('Execution Rate:')
+ curr_ips = int (line.strip().replace(',', '').split(' ')[0])
+ pf = open ('lib/pidp8i/ips.py', 'a')
+ pf.write ('current = ' + str (curr_ips) + ' # ' + \
+ str (datetime.today ()) + '\n')
+ pf.close ()
+ s.send_cmd ('quit')
+ pdp_ratio = float (curr_ips) / ips.pdp8i
+ rpi_ratio = float (curr_ips) / ips.raspberry_pi_b_plus
+ print "\nYour system is " + format (rpi_ratio, '.1f') + \
+ " times faster than a Raspberry Pi Model B+"
+ print "or " + format (pdp_ratio, '.1f') + \
+ " times faster than a PDP-8/I.\n"
+ else:
+ # Normal mode. Tell SIMH and throttle down to a rate suitable for a
+ # blinkenlights demo. 1/17 means SIMH runs one instruction then
+ # waits for 17ms, yielding ~59 IPS.
+ time.sleep (0.02) # FIXME: simulator chokes on 'cont' without this
+ s.os8_send_ctrl ('e') # same justification as above
+ s.send_cmd ('set throttle 1/17')
+
+ # You can't hit Ctrl-E while running this script in the foreground
+ # since pexpect takes over stdio. Therefore, if you want to be able
+ # to send commands to the simulator while the demo is running,
+ # uncomment the line below, which will let you send commands to the
+ # simulator via telnet. From another terminal or SSH session:
+ #
+ # $ telnet localhost 3141
+ #
+ # or from a remote machine:
+ #
+ # $ telnet 192.168.1.2 3141
+ #
+ # It's disabled by default because SIMH can't be made to listen only
+ # on localhost, so doing this may be a security risk. SIMH disables
+ # obviously-unsafe commands like ! on the remote console, but it is
+ # possible some mischief may be possible via this path anyway. It
+ # could be used to exfiltrate a sensitive file via ATTACH, for one
+ # thing. For another, it's a potential DoS vector.
+ #s.send_cmd ('set remote telnet=3141')
+
+ # Let it run. Never exits.
+ s.send_cmd ('cont')
+ s.spin ()
+
+
+if __name__ == "__main__":
+ main()
DELETED bin/txt2os8
Index: bin/txt2os8
==================================================================
--- bin/txt2os8
+++ /dev/null
@@ -1,151 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-########################################################################
-# Create a tu56 or rk05 image and fill it with ASCII files, i.e. source code.
-#
-# It is intended to be be called manually when we have a POSIX
-# directory full of ASCII files we want to bulk-copy into SIMH.
-#
-# The argument is taken both as the name of the image to create
-# and the list of files to copy in.
-#
-# For now, it takes all input and produces all output in the
-# current working directory.
-#
-# IMPORTANT: Currently all input files are mindlessly passed through
-# txt2ptp which transforms POSIX ASCII files to OS/8 ASCII files.
-# It WILL mutilate non-ASCII files.
-#
-# This program is based on the old cc8-tu56-update program, last
-# shipped by this project in release v20171222.
-#
-# Copyright © 2017 by Warren Young and Bill Cattey
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM,
-# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
-# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
-# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-#
-# Except as contained in this notice, the names of the authors above
-# shall not be used in advertising or otherwise to promote the sale,
-# use or other dealings in this Software without prior written
-# authorization from those authors.
-########################################################################
-
-# Bring in just the basics so we can bring in our local modules
-import os
-import sys
-import argparse
-
-sys.path.insert (0, os.path.dirname (__file__) + '/../lib')
-sys.path.insert (0, os.getcwd () + '/lib')
-
-# Our local modules
-from pidp8i import *
-from simh import *
-
-# Other global Python modules
-import glob
-import subprocess
-
-
-#### GLOBALS AND CONSTANTS #############################################
-
-progmsg = True
-
-
-#### main ##############################################################
-
-def main ():
- global progmsg
-
- # Set up the arg parser and use it to parse the command line.
- parser = argparse.ArgumentParser()
- parser.add_argument("name",
- help="Create an OS/8 image from a list of ASCII files.")
- group = parser.add_mutually_exclusive_group(required=True)
- group.add_argument("--tu56", "-t", action="store_true")
- group.add_argument("--rk05a", "-ra", action="store_true")
- group.add_argument("--rk05b", "-rb", action="store_true")
-
- args = parser.parse_args()
-
- print "Filename: " + args.name
- if args.tu56:
- sdev = "dt0"
- os8dev = "DTA0:"
- imagename = args.name + ".tu56"
- stat_str = "DECtape"
-
- if args.rk05a:
- sdev = "rk1"
- os8dev = "RKA1:"
- imagename = args.name + ".rk05"
- stat_str = "partition A of"
-
- if args.rk05b:
- sdev = "rk1"
- os8dev = "RKB1:"
- imagename = args.name + ".rk05"
- stat_str = "partition B of"
-
- listname = args.name + ".list"
-
- # Create the SIMH child instance and tell it where to send log output
- try:
- s = simh (dirs.build, True)
- except (RuntimeError) as e:
- print "Could not start simulator: " + e.message + '!'
- exit (1)
- s.set_logfile (os.fdopen (sys.stdout.fileno (), 'w', 0))
-
- # Attach a clean version of the image to the simulator
- if os.path.exists (imagename):
- print "Overwriting old " + stat_str + " image " + imagename
-
- s.send_cmd ("att " + sdev + " " + imagename)
-
- # Find and boot the bootable OS/8 disk. Use the "patched" version
- # because that is what "make run" uses; we use that command to
- # inspect this script's work.
- rk = os.path.join (dirs.os8mo, 'os8v3d-patched.rk05')
- if not os.path.isfile (rk):
- print "Could not find " + rk + "; OS/8 media not yet built?"
- exit (1)
- print "Booting " + rk + "..."
- s.send_cmd ("att rk0 " + rk)
- s.send_cmd ("boot rk0")
-
- s.os8_send_cmd ('\\.', "ZERO " + os8dev)
-
- manifest = open (listname, "r")
-
- for line in manifest:
- src = line.strip()
- if src == "": continue
- if src[0] == '#': continue # Allow commenting out files
-
- dest = src.upper ()
- s.os8_send_file (src, os8dev + dest)
-
- # Exit simulator nicely so that image detaches cleanly
- s.back_to_cmd ('\\.')
- s.send_cmd ("det " + sdev)
- s.send_cmd ('quit')
-
-
-if __name__ == "__main__": main()
ADDED bin/txt2os8.in
Index: bin/txt2os8.in
==================================================================
--- /dev/null
+++ bin/txt2os8.in
@@ -0,0 +1,151 @@
+#!/usr/bin/env @PYCMD@
+# -*- coding: utf-8 -*-
+########################################################################
+# Create a tu56 or rk05 image and fill it with ASCII files, i.e. source code.
+#
+# It is intended to be be called manually when we have a POSIX
+# directory full of ASCII files we want to bulk-copy into SIMH.
+#
+# The argument is taken both as the name of the image to create
+# and the list of files to copy in.
+#
+# For now, it takes all input and produces all output in the
+# current working directory.
+#
+# IMPORTANT: Currently all input files are mindlessly passed through
+# txt2ptp which transforms POSIX ASCII files to OS/8 ASCII files.
+# It WILL mutilate non-ASCII files.
+#
+# This program is based on the old cc8-tu56-update program, last
+# shipped by this project in release v20171222.
+#
+# Copyright © 2017-2019 by Warren Young and Bill Cattey
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM,
+# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
+# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
+# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+# Except as contained in this notice, the names of the authors above
+# shall not be used in advertising or otherwise to promote the sale,
+# use or other dealings in this Software without prior written
+# authorization from those authors.
+########################################################################
+
+# Bring in just the basics so we can bring in our local modules
+import os
+import sys
+import argparse
+
+sys.path.insert (0, os.path.dirname (__file__) + '/../lib')
+sys.path.insert (0, os.getcwd () + '/lib')
+
+# Our local modules
+from pidp8i import *
+from simh import *
+
+# Other global Python modules
+import glob
+import subprocess
+
+
+#### GLOBALS AND CONSTANTS #############################################
+
+progmsg = True
+
+
+#### main ##############################################################
+
+def main ():
+ global progmsg
+
+ # Set up the arg parser and use it to parse the command line.
+ parser = argparse.ArgumentParser()
+ parser.add_argument("name",
+ help="Create an OS/8 image from a list of ASCII files.")
+ group = parser.add_mutually_exclusive_group(required=True)
+ group.add_argument("--tu56", "-t", action="store_true")
+ group.add_argument("--rk05a", "-ra", action="store_true")
+ group.add_argument("--rk05b", "-rb", action="store_true")
+
+ args = parser.parse_args()
+
+ print "Filename: " + args.name
+ if args.tu56:
+ sdev = "dt0"
+ os8dev = "DTA0:"
+ imagename = args.name + ".tu56"
+ stat_str = "DECtape"
+
+ if args.rk05a:
+ sdev = "rk1"
+ os8dev = "RKA1:"
+ imagename = args.name + ".rk05"
+ stat_str = "partition A of"
+
+ if args.rk05b:
+ sdev = "rk1"
+ os8dev = "RKB1:"
+ imagename = args.name + ".rk05"
+ stat_str = "partition B of"
+
+ listname = args.name + ".list"
+
+ # Create the SIMH child instance and tell it where to send log output
+ try:
+ s = simh (dirs.build, True)
+ except (RuntimeError) as e:
+ print "Could not start simulator: " + e.message + '!'
+ exit (1)
+ s.set_logfile (os.fdopen (sys.stdout.fileno (), 'wb', 0))
+
+ # Attach a clean version of the image to the simulator
+ if os.path.exists (imagename):
+ print "Overwriting old " + stat_str + " image " + imagename
+
+ s.send_cmd ("att " + sdev + " " + imagename)
+
+ # Find and boot the bootable OS/8 disk. Use the "patched" version
+ # because that is what "make run" uses; we use that command to
+ # inspect this script's work.
+ rk = os.path.join (dirs.os8mo, 'os8v3d-patched.rk05')
+ if not os.path.isfile (rk):
+ print "Could not find " + rk + "; OS/8 media not yet built?"
+ exit (1)
+ print "Booting " + rk + "..."
+ s.send_cmd ("att rk0 " + rk)
+ s.send_cmd ("boot rk0")
+
+ s.os8_send_cmd ('\\.', "ZERO " + os8dev)
+
+ manifest = open (listname, "r")
+
+ for line in manifest:
+ src = line.strip()
+ if src == "": continue
+ if src[0] == '#': continue # Allow commenting out files
+
+ dest = src.upper ()
+ s.os8_send_file (src, os8dev + dest)
+
+ # Exit simulator nicely so that image detaches cleanly
+ s.back_to_cmd ('\\.')
+ s.send_cmd ("det " + sdev)
+ s.send_cmd ('quit')
+
+
+if __name__ == "__main__": main()
Index: doc/class-simh.md
==================================================================
--- doc/class-simh.md
+++ doc/class-simh.md
@@ -84,17 +84,17 @@
## Logging
The next step is to tell the `s` object where to send its logging
output:
- s.set_logfile (os.fdopen (sys.stdout.fileno (), 'w', 0))
+ s.set_logfile (os.fdopen (sys.stdout.fileno (), 'wb', 0))
Contrast the corresponding line in `os8-run` which chooses whether to send
logging output to the console or to a log file:
- s.set_logfile (open (dirs.log + 'os8-run' + '.log', 'a') \
- if not VERY_VERBOSE else os.fdopen (sys.stdout.fileno (), 'w', 0))
+ s.set_logfile (open (dirs.log + 'os8-run' + '.log', 'ab') \
+ if not VERY_VERBOSE else os.fdopen (sys.stdout.fileno (), 'wb', 0))
Note that this more complicated scheme appends to the log file instead
of overwriting it because there are cases where `os8-run` gets run
more than once with different script inputs, so we want to preserve
the prior script outputs, not keep only the latest.
ADDED etc/pidp8i.rc
Index: etc/pidp8i.rc
==================================================================
--- /dev/null
+++ etc/pidp8i.rc
@@ -0,0 +1,5 @@
+# PiDP-8/I runtime configuration file. See it README.md file or
+# https://tangentsoft.com/pidp8i/doc/trunk/README.md#runtime
+
+# Alternatives: none, tmux
+SCREEN_MANAGER=screen
DELETED lib/os8script.py
Index: lib/os8script.py
==================================================================
--- lib/os8script.py
+++ /dev/null
@@ -1,1887 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-########################################################################
-# simh-os8-script.py Library for scripting OS/8 under SIMH
-# Contains validators and callers for os8 and simh commands to make
-# it easier to create scripts.
-#
-# Copyright © 2017 by Jonathan Trites, William Cattey, and Warren Young.
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM,
-# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
-# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
-# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-#
-# Except as contained in this notice, the names of the authors above
-# shall not be used in advertising or otherwise to promote the sale,
-# use or other dealings in this Software without prior written
-# authorization from those authors.
-########################################################################
-
-# Bring in just the basics so we can bring in our local modules
-import os
-import sys
-import tempfile
-sys.path.insert (0, os.path.dirname (__file__) + '/../lib')
-sys.path.insert (0, os.getcwd () + '/lib')
-
-# Python core modules we use
-import re
-from string import Template
-import shutil
-import subprocess
-
-# Our local modules
-from pidp8i import *
-from simh import *
-
-# Script Language Version
-# Update this version number as the language evolves.
-# Version 1.0 is the first public version.
-LANG_VERSION = "1.0"
-
-# Error Class Definitions ##############################################
-# Enables us to use exceptions from within this module.
-
-class Error(Exception):
- """Base Class for exceptions in this module."""
- pass
-
-class InputError(Error):
- """Exception raised for errors in the input.
-
- Attributes:
- expr -- input expression in which the error occurred
- msg -- explanation of the error
- """
-
- def __init__(self, msg):
- self.msg = msg
-
- def __str__(self):
- return self.msg
-
-
-# Private globals ######################################################
-# Visible within this file, but not to the outside.
-
-# Identify a begin enabled/not_disabled command. group(1) contains either the enabled or
-# disabled flag. Put the rest of the line in group(2)
-_begin_en_dis_comm_re = re.compile ("^begin\s+(enabled|default|version)\s+(.+)$")
-
-# Identify an end enabled/not_disabled command. group(1) contains either the enabled or
-# disabled flag. Put the rest of the line in group(2)
-_end_en_dis_comm_re = re.compile ("^end\s+(enabled|default|version)\s+(.+)$")
-
-# Identify an end comm and put the rest of the line in group(1)
-_end_comm_re = re.compile ("^end\s+(.+)?$")
-
-# Identify an end option command and put the rest of the line in group(1)
-_end_option_comm_re = re.compile ("^end\s+option\s+(.+)$")
-
-# A valid version spec
-_version_parse_re = re.compile ("^((\d+\.)*)?(\d+)?$")
-
-# Name of the DECtape image file we create
-_new_sys_tape_prefix = "system"
-
-# Parser regexps used in patcher
-_com_os8_parse_str = "^\.([a-zA-Z]+)\s*(.*)$"
-_com_os8_parse = re.compile(_com_os8_parse_str)
-_com_split_str = "^([a-zA-Z]+)\s*(.*)$"
-_com_split_parse = re.compile(_com_split_str)
-_odt_parse_str = "^([0-7]+)\s*/\s*(\S+)\s+([0-7;]+)"
-_odt_parse = re.compile(_odt_parse_str)
-
-# Put command keyword in group(1) and the rest is in group(3)
-_comm_re_str = "^(\S+)(\s+(.+))?$"
-_comm_re = re.compile(_comm_re_str)
-
-# Identify an end comm and put the rest of the line in group(1)
-_end_comm_re = re.compile ("^end\s+(.+)?$")
-
-# Identify an end option command and put the rest of the line in group(1)
-_end_option_comm_re = re.compile ("^end\s+option\s+(.+)$")
-
-# Identify a begin command and put the rest of the line in group(1)
-_begin_option_comm_re = re.compile ("^begin\s+option\s+(.+)$")
-
-# Parse an argument string into a sys device with
-# device name in group(1), unit number in group(2)
-# We put all bootable devices into this string so that when
-# we add more devices, for example rl for RL01, we change one
-# string not many.
-_simh_boot_dev_str = "(rk|td|dt|rx)(\d*)"
-_simh_boot_re = re.compile("^" + _simh_boot_dev_str + "$")
-
-# Parse an argument string for mount into SIMH device
-# device name in group(1), unit number in group(2)
-# And the rest in group (3)
-_mount_regex_str = "^" + _simh_boot_dev_str + "\s+(.+)$"
-_mount_re = re.compile(_mount_regex_str)
-
-# Map of SIMH device names to OS/8 device name prefixes.
-_os8_from_simh_dev = {"rk" : "RK", "td" : "DTA", "dt" : "DTA", "rx" : "RX"}
-
-_os8_partitions = {"RK": ["A", "B"]}
-
-# OS/8 file name matching regex
-_os8_file_re = re.compile("(\S+):(\S+)?")
-
-# Regular expression for syntax checking inside FOTP
-# Destination is in group(1), Source is in group(3)
-_fotp_re = re.compile ("^((\S+:)?\S+)<((\S+:)?\S+)$")
-
-# Regular expression for detecting the 2 arg and 3 arg forms
-# of the "pal8" script command.
-
-# OS/8 name regex template:
-# Optional device spec, i.e. DTA0:
-# File spec with a specific extension or no extension.
-
-_os8_fspec = Template ("((\S+:)?([A-Z0-9]{1,6}|[A-Z0-9]{1,6}\.$ext))")
-_os8_BN_fspec = _os8_fspec.substitute(ext="BN")
-_os8_PA_fspec = _os8_fspec.substitute(ext="PA")
-_os8_LS_fspec = _os8_fspec.substitute(ext="LS")
-
-# For the two arg form:
-# The full destination spec is in group(1), The full source spec is in group(4).
-# The device components, if any, are in group(2) for destination, and
-# group(5) for source.
-# The file components are in group(3) for destination, and group (6) for source.
-# The destination file must either end in ".BN" or have no extension.
-# The source must file either end in ".PA" or have no extension.
-_two_arg_pal_re = re.compile ("^" + _os8_BN_fspec + "\s*<\s*" + _os8_PA_fspec + "$")
-
-# For the 3 arg form:
-# The full destination spec is in group(1), The full source spec is in group(7).
-# The full listing spec is in group(4)
-# The device components, if any, are in group(2) for destination, group(5)
-# for listing, and group(8) for source.
-# The file components are in group(3) for destination, and group(9) for source,
-# and group(6) for listing.
-# The destination file must either end in ".BN" or have no extension.
-# The source must file either end in ".PA" or have no extension.
-# The listing must either end in "LS" or have no extension.
-
-_three_arg_pal_re = re.compile ("^" + _os8_BN_fspec + "\s*,\s*" + _os8_LS_fspec + "\s*<\s*" + _os8_PA_fspec + "$")
-
-# Regular expression for syntax checking inside ABSLDR
-# One or more OS/8 binary files and optional args beginning with a slash.
-
-_absldr_re = re.compile ("^" + _os8_BN_fspec + "(," + _os8_BN_fspec + ")*(/\S)*$")
-
-# Regular expressions for syntax checking for cpto and cpfrom.
-# May be where destination and default option /A is implied.
-# Or where destination is implied and option is set.
-# Or where option /A is implied.
-# Or are explicit.
-# Valid options are "/A", "/I", and "/B"
-# Use two regex's in order:
-# in group 1, in group 2.
-# Option is one of /I /B /A in group 4.
-
-# source in group 1, option in group 3.
-_from_to_re_1 = re.compile ("^(\S+)(\s+(/[AIB]))?$")
-# source in group 1, destination in group 2, option in group 4.
-_from_to_re_2 = re.compile ("^(\S+)\s+(\S+)(\s+(/[AIB]))?$")
-
-# Array of regular expressions for syntax checking inside BUILD
-_build_comm_regs = {"LOAD" : re.compile("^(\S+:)?\S+(.BN)?$"),
- "UNLOAD": re.compile("^\S+(,\S+)?$"),
- "INSERT": re.compile("^\S+,\S+(,\S+)?$"),
- "DELETE": re.compile("^\S+(,\S+)?$"),
- "SYSTEM": re.compile("^\S+$"),
- "DSK" : re.compile("^(\S+:)?\S+$"),
- "BUILD" : re.compile("^(\S+(.BN)?)\s+(\S+(.BN)?)$"),
- "PRINT" : None,
- "BOOT" : None,
- "end" : None}
-
-_build_replies = ["\\$", "SYS BUILT", "WRITE ZERO DIRECT\\?", "\\?BAD ARG",
- "\\?BAD INPUT", "\\?BAD LOAD",
- "\\?BAD ORIGIN", "\\?CORE", "\\?DSK", "\\?HANDLERS",
- "I/O ERR", "\\?NAME", "NO ROOM", "SYS NOT FOUND",
- "\\?PLAT", "\\?SYNTAX", "\\?SYS", "SYS ERR",
- "\S+ NOT FOUND"]
-
-# Parse two whitspace separated arguments into group(1) and group(2)
-_two_args_re = re.compile("^(\S+)\s+(\S+)$")
-
-_rx_settings = ["rx01", "rx02", "RX8E", "RX28"]
-_tape_settings = ["td", "dt"]
-_tti_settings = ["KSR", "7b"]
-_configurables = {"rx": _rx_settings, "tape": _tape_settings,
- "tti": _tti_settings}
-
-# Matches if the string begins with a dollar sign, and has at least
-# one slash, returning the string between the dollar sign and the
-# first slash in group 1 and the rest in group 2.
-# No whitespace in the string.
-_expandable_re = re.compile ("^\$([^/\s]+)/(\S*)$")
-
-# Parse an exit arg for an integer or an integer in parentheses
-_exit_arg_re = re.compile ("^(\s*[+-]?\s*\d+)|\s*\(\s*([+-]?\s*\d+)\s*\)\s*$")
-
-# Options enabled/not_disabled for conditional execution in scripts.
-#
-# Earlier code allowed --enable and --disable. We interface to it.
-# We maintain two arrays: options_enable and options_disabled for those
-# two argument constructs.
-#
-# Argument parsing of repeated enable and disable arguments is as follows:
-# --enable looks on options_disabled and if present removes it, then adds
-# to options_enabled.
-# --disable looks on options_enabled and if present removes it, then adds
-# to options_enabled.
-#
-# Last seen enable/disable command line or executed command for a
-# particular option wins.
-#
-# Scripts have enable/disable commands that are run-time
-# changers of the contents of options_enabled and options_disabled.
-#
-# When we run a script we have begin/end blocks for enabled/not_disabled options:
-# "begin enabled " ... "end enabled
-# "begin not_disabled " ... "end not_disabled
-#
-# The enabled block looks for an explicit enablement on the options_enabled
-# list. If none is found we default to ignoring the contents of the block.
-#
-# The not_disabled block looks for an explicit disablement on the
-# options_disabled lis. If found, the block is ignored. Otherwise
-# the block defaults to being executed.
-#
-# begin/end blocks can be nested. We track the nesting with options_stack.
-# Testing for options happens when the begin command is evaluated.
-# So changing an enable/disable option inside a begin/end block
-# takes effect at the next begin statement.
-# You can write a script as follows:
-# enable foo
-# begin enabled foo
-# # Commands to executed
-# disable foo
-# # Commands still being executed.
-# begin enabled foo
-# # Commands to ignore
-# end enabled foo
-# end enabled foo
-
-# Local routine to perform a save of a pre-existing file
-# because we do this in a couple places
-
-def save_if_needed(path):
- if os.path.isfile(path):
- save_path = path + ".save"
- print "Pre-existing " + path + " found. Saving as " + path + ".save"
- if os.path.isfile(save_path):
- print "Overwriting old " + path + ".save"
- os.remove(save_path)
- os.rename(path, save_path)
-
-def version_to_array (version):
- vers_array = []
- this_str = ""
-
- for c in version:
- if c != ".":
- this_str += c
- else:
- vers_array.append(this_str)
- this_str = ""
- if this_str != "": vers_array.append(this_str)
- return vers_array
-
-
-class os8script:
- # Contains a simh object, other global state and methods
- # for running OS/8 scripts under simh.
- #### globals and constants ###########################################
-
-
- def __init__ (self, simh, enabled_options, disabled_options, verbose=False, debug=True):
- self.lang_version = LANG_VERSION
- self.verbose = verbose
- self.debug = debug
- self.simh = simh
- self.options_enabled = enabled_options
- self.options_disabled = disabled_options
- # Do we need separate stacks for enabled/disabled options?
- self.options_stack = []
- # List of scratch files to delete when we are done with all script runs.
- self.scratch_list = []
- self.booted = False
- self.line_ct_stack = []
-
-
- #### path_expand #######################################################
- # Simple minded variable substitution in a path.
- # A path beginning with a dollar sign parses the characters between
- # the dollar sign and the first slash seen becomes a name to
- # expand with a couple local names: $home and the anchor directories
- # defined in lib/pidp8i/dirs.py.
- # Returns None if the expansion fails. That signals the caller to fail.
-
- def path_expand (self, path):
- m = re.match(_expandable_re, path)
- if m == None: return path
- var = m.group(1)
-
- val = getattr (dirs, var, None)
- if val != None:
- return os.path.join(val,m.group(2))
- else:
- print "At line " + str(self.line_ct_stack[0]) + \
- ": {$" + var + "} is not a valid path expansion in " + path
- return None
-
-
- #### print_expand ######################################################
- # Close kin to path_expand. Takes a string that may name a path
- # substitution or the magic $version value and performs the appropriate
- # value substitution.
-
- def print_expand (self,str):
- end = str.find("$")
- if end == -1: return str
-
- m = re.findall("\$\S+",str)
- if m == None: return str
-
- outstr = ""
- start = 0
-
- for name in m:
- end = str.index(name, start)
- outstr += str[start:end]
-
- sub = getattr (dirs, name[1:], None)
- if sub == None:
- if name == "$version": sub = self.lang_version
- else: sub = name
-
- outstr += sub
- start = end + len(name)
-
- return outstr
-
-
- #### version_test ######################################################
- # Compare each component of the version test agains the actual version
- # Return true if actual version is greater than or equal to the test
- # version.
- # Caller validates test with _version_parse_re so we only
- # need to return True or False, not error.
-
- def version_test (self, test):
- test_array = version_to_array(test)
- version_array = version_to_array(self.lang_version)
-
- idx = 0
- endpoint = len(test_array)
-
- while idx < endpoint:
- # If version has more digits than test, the greater than test succeeds.
- if idx >= len(version_array):
- vers_item = "0"
- else:
- vers_item = version_array[idx]
- test_item = test_array[idx]
- if self.debug: print "version_test: vers_item: " + vers_item + \
- ", test_item: " + test_item
-
- vers_num = int(vers_item)
- test_num = int(test_item)
-
- # First time version componet greater than test -> success.
- if vers_num > test_num:
- if self.debug: print "version_test: Success: version greater than test."
- return True
- # First time version component less than test -> failure.
- elif test_num > vers_num:
- if self.debug: print "version_test: Fails on sub compare."
- return False
- #Otherwise is equal. Keep going.
-
- idx += 1
- # Made it all the way through. Test succeeds.
- if self.debug: print "version_test: Success. Made it thru test string."
- return True
-
-
- #### basic_line_parse ################################################
- # Returns stripped line and any other cleanup we want.
- # Returns None if we should just 'continue' on to the next line.
- # Filters out comments.
- # Processes the option begin/end blocks.
-
- def basic_line_parse (self, line, script_file):
- self.line_ct_stack[0] += 1
- retval = line.strip()
- if retval == "": return None
- elif retval[0] == "#": return None
- # First test if we are in a begin option block
- m = re.match (_begin_en_dis_comm_re, retval)
- if m != None:
- en_dis = m.group(1)
- rest = m.group(2)
- if self.verbose: print "Line " + str(self.line_ct_stack[0]) + \
- ": doing_begin_option: " + en_dis + " " + rest
- if self.debug: print "options_enabled: " + str (self.options_enabled)
- if self.debug: print "options_disabled: " + str (self.options_disabled)
- if self.debug: print "options_stack: " + str(self.options_stack)
-
- vers_match = False
- if en_dis == "version":
- # Check for mal-formed version match first
- if re.match (_version_parse_re, rest) == None:
- print "Mal-formed version match string {" + rest + "} at line " + \
- str(self.line_ct_stack[0]) + ". Ignoring this block."
- self.ignore_to_subcomm_end (retval, script_file, en_dis + " " + rest)
- return None
- vers_match = self.version_test (rest)
- if vers_match:
- # Block is active. We push it onto the stack
- if self.debug: print "Pushing version enabled block " + rest + " onto options_stack"
- self.options_stack.insert(0, rest)
- if self.debug: print " new options_stack: " + str(self.options_stack)
- else:
- # Option is inactive. Ignore all subseqent lines
- # until we get to an end command that matches our option.
- self.ignore_to_subcomm_end (retval, script_file, en_dis + " " + rest)
- return None
-
- elif en_dis == "enabled":
- if rest in self.options_enabled:
- # Block is active. We push it onto the stack
- if self.debug: print "Pushing enabled block " + rest + " onto options_stack"
- self.options_stack.insert(0, rest)
- if self.debug: print "new options_stack: " + str(self.options_stack)
- else:
- # Option is inactive. Ignore all subseqent lines
- # until we get to an end command that matches our option.
- self.ignore_to_subcomm_end (retval, script_file, en_dis + " " + rest)
-
- return None
- # only other choice is disabled because of our regex.
- else:
- if rest not in self.options_disabled:
- # Block defaults to active. We push it onto the stack
- if self.debug: print "Pushing not_disabled block " + rest + " onto options_stack"
- self.options_stack.insert(0, rest)
- if self.debug: print "new options_stack: " + str(self.options_stack)
- else:
- # Block is inactive. Ignore all subseqent lines
- # until we get to an end command that matches our option.
- self.ignore_to_subcomm_end (retval, script_file, en_dis + " " + rest)
- return None
-
- m = re.match(_end_en_dis_comm_re, retval)
- if m != None:
- rest = m.group(2)
- if self.verbose: print "Line " + str(self.line_ct_stack[0]) + ": end rest = " + rest
- if (rest == None or rest == ""):
- print "Warning! option end statement at line " + str(self.line_ct_stack[0]) + \
- " encountered with no argument."
- return None
- if len(self.options_stack) == 0:
- print "Warning! option end statement at line " + str(self.line_ct_stack[0]) + \
- " found with no matching begin for option: " + rest
- return None
- if rest != self.options_stack[0]:
- print "Warning! Mismatched option begin/end group at line " + \
- str(self.line_ct_stack[0]) + ". Currently inside option: " + \
- self.options_stack[0] + " not " + rest
- return None
- else:
- if self.debug: print "Popping " + self.options_stack[0]
- self.options_stack.pop(0)
- if self.debug: print "new options_stack: " + str(self.options_stack)
- return None
-
- return retval
-
-
- #### ignore_to_subcomm_end ###########################################
-
- def ignore_to_subcomm_end (self, old_line, script_file, end_str):
- if self.debug: print "ignore to: " + end_str
- for line in script_file:
- self.line_ct_stack[0] += 1
- line = line.strip()
- if self.verbose: print "Ignore line " + str(self.line_ct_stack[0]) + ": " + line
-
- m = re.match(_end_comm_re, line)
- if m == None: continue
-
- rest = m.group(1)
- if rest == None: rest = ""
-
- if rest == end_str: return
-
-
- #### include_command #################################################
- # Call run_system_script recursively on the file path provided.
-
- def include_command (self, line, script_file):
- path = self.path_expand(line)
- if path == None:
- print "Ignoring: \n\tinclude " + line
- return "fail"
-
- if not os.path.isfile(path):
- print "Line " + str(self.line_ct_stack[0]) + \
- ": Could not find include file: " + path
- return "fail"
- if self.verbose: print "line: " + str(self.line_ct_stack[0]) + \
- ": include " + line
- return self.run_script_file (path)
-
-
- #### enable_option_command ###########################################
- # Deletes an option from the list of active options.
- # Parses the first argument after "enable" as the key to enable.
- # The end of the key is the end of the line or the first whitespace
- # character.
-
- def enable_option_command (self, line, script_file):
- if line == "":
- print "Empty option to enable at line: " + \
- str(self.line_ct_stack[0]) + "."
- return "fail"
- m = re.match(_comm_re, line)
- if m == None:
- print "Could not parse enable command at line " + \
- str(self.line_ct_stack[0]) + "."
- return "fail"
- option = m.group(1)
- if option == None:
- print "Empty option to enable command at line: " + \
- str(self.line_ct_stack[0]) + "."
- return "fail"
- if self.verbose: print "Line " + str(self.line_ct_stack[0]) + \
- ": enable option: " + option
- # Remove it from other set if present
- if option in self.options_disabled:
- self.options_disabled.remove(option)
- # Add it if not already present.
- if option not in self.options_enabled:
- self.options_enabled.append(option)
- return "success"
-
-
- #### disable_option_command ###########################################
- # Deletes an option from the list of active options.
- # Parses the first argument after "disable" as the key to enable.
- # The end of the key is the end of the line or the first whitespace
- # character.
-
- def disable_option_command (self, line, script_file):
- if line == "":
- print "Empty option to disable at line: " + \
- str(self.line_ct_stack[0]) + "."
- return "fail"
- m = re.match(_comm_re, line)
- if m == None:
- print "Could not parse disable option command at line " + \
- str(self.line_ct_stack[0]) + "."
- return "fail"
- option = m.group(1)
- if option == None:
- print "Empty option to disable command at line " + \
- str(self.line_ct_stack[0]) + "."
- return "fail"
- if self.verbose: print "line: " + \
- str(self.line_ct_stack[0]) + ": disable option: " + option
- # Remove it from other set if present
- if option in self.options_enabled:
- self.options_enabled.remove(option)
- # Add it if not already present.
- if option not in self.options_disabled:
- self.options_disabled.append(option)
- return "success"
-
-
- #### configure_command ###############################################
- # First arg is the item to configure.
- # Second arg is the setting.
- # This enables adding option setting inside a script file.
-
- def configure_command (self, line, script_file):
- m = re.match(_two_args_re, line)
- if m == None or m.group(1) == None or m.group(2) == None:
- print "Could not parse configure command arguments at line " + \
- str(self.line_ct_stack[0]) + ": {" + line + "}"
- return "fail"
- item = m.group(1)
- setting = m.group(2)
- if item not in _configurables:
- print "Ignoring invalid configuration item at line " + \
- str(self.line_ct_stack[0]) + ": " + item
- return "fail"
- if setting not in _configurables[item]:
- print "At line " + str(self.line_ct_stack[0]) + \
- ": Cannot set " + item + " to " + setting + "."
- return "fail"
- if item == "tape":
- self.simh.set_tape_config(setting)
- elif item == "rx":
- self.simh.set_rx_config (setting)
- elif item == "tti":
- self.simh.set_tti_config (setting)
- return "success"
-
-
- #### cpto_command ###########################################
- # Calls os8_pip_to with the command line arguments.
-
- def cpto_command (self, line, script_file):
- if not self.booted:
- print "Cannot run cpto command at line " + \
- str(self.line_ct_stack[0]) + ". OS/8 has not been booted."
- return "die"
-
- # Is 2nd and final arg the option?
- m = re.match(_from_to_re_1, line)
- if m != None:
- # Yes. Expand Source first.
- path = self.path_expand(m.group(1))
- if path == None:
- print "Ignoring: \n\tcpto " + line
- return "fail"
- self.simh.os8_pip_to (path, "DSK:", m.group(2))
- else:
- # Is this normal case of source, dest, with possibly empty option?
- m = re.match(_from_to_re_2, line)
- if m == None:
- print "Could not parse cpto command at line " + \
- str(self.line_ct_stack[0]) + "."
- return "fail"
- path = self.path_expand(m.group(1))
- if path == None:
- print "Ignoring: \n\tcpto " + line
- return "fail"
- self.simh.os8_pip_to (path, m.group(2), m.group(4))
- return "success"
-
-
- #### cpto_command ###########################################
- # Calls os8_pip_from with the command line arguments.
-
- def cpfrom_command (self, line, script_file):
- if not self.booted:
- print "Cannot run cpfrom command at line " + \
- str(self.line_ct_stack[0]) + ". OS/8 has not been booted."
- return "die"
- m = re.match(_from_to_re_2, line)
- if m == None:
- print "Could not parse cpfrom command at line " + \
- str(self.line_ct_stack[0]) + "."
- return "fail"
-
- path = self.path_expand(m.group(2))
- if path == None:
- print "Ignoring: \n\tcpfrom " + line
- return "fail"
- self.simh.os8_pip_from (m.group(1), path, m.group(4))
- return "success"
-
-
- #### copy_command ###############################################
- # Simple script interface to create a copy of a file.
-
- def copy_command (self, line, script_file):
- m = re.match(_two_args_re, line)
- if m == None or m.group(1) == None or m.group(2) == None:
- print "Could not copy command: " + line
- return "fail"
- from_path = self.path_expand(m.group(1))
- to_path = self.path_expand(m.group(2))
-
- if from_path == None or to_path == None:
- print "Ignoring: \n\t copy " + line
- return "fail"
-
- print "copy command: \n\tfrom: " + from_path + ", \n\tto: " + to_path
-
- if (not os.path.isfile(from_path)):
- print "At line " + str(self.line_ct_stack[0]) + \
- ": Required copy input file: " + from_path + " not found."
- return "fail"
-
- save_if_needed(to_path)
-
- try:
- shutil.copyfile(from_path, to_path)
- except shutil.Error as e:
- print "copy command failed with error: " + str(e)
- return "fail"
- except IOError as e:
- print "copy command failed with IOError: " + str(e)
- return "fail"
- return "success"
-
-
- #### resume_command #############################################
- # Call the os8_resume in simh to resume OS/8.
-
- def resume_command (self, line, script_file):
- if self.verbose: print "Resuming OS/8 at line " + \
- str(self.line_ct_stack[0]) + "."
-
- self.simh.os8_restart()
- return "success"
-
-
- #### restart_command #############################################
- # Call the os8_restart in simh to resume OS/8.
-
- def restart_command (self, line, script_file):
- if self.verbose: print "Restarting OS/8 at line " + \
- str(self.line_ct_stack[0]) + "."
-
- self.simh.os8_restart()
- return "success"
-
-
- #### patch_command ##############################################
- # Read the named patch file and perform its actions.
-
- def patch_command (self, line, script_file):
- if not self.booted:
- print "Cannot run patch command at line " + \
- str(self.line_ct_stack[0]) + ". OS/8 has not been booted."
- return "die"
- path = self.path_expand(line)
- if path == None:
- print "Ignoring: \n\t" + "patch " + line
- return "fail"
- if not os.path.isfile(path):
- print "At line " + str(self.line_ct_stack[0]) + \
- ": Patch file: " + path + " not found."
- return "fail"
-
- self.run_patch_file (path)
- return "success"
-
-
- #### exit_command ###############################################
- # Call POSIX exit to exit the running program,
- # returning a numerical status value.
-
- def exit_command (self, line, script_file):
- m = re.match (_exit_arg_re, line)
- intfound = False
- if m != None:
- if m.group(1) != None:
- intfound = True
- status = int(m.group(1))
- elif m.group(2) != None:
- intfound = True
- status = int(m.group(2))
- else:
- intfound = False
- if self.verbose:
- if intfound: stat_str = str(status)
- else: stat_str = line
- print "Calling sys.exit (" + stat_str + ") at line: " + \
- str(self.line_ct_stack[0]) + "."
- if intfound: sys.exit (status)
- else: sys.exit(line)
-
-
- #### print_command ###############################################
- # Print text from the running script
- # If verbose is set, say what line in the script containing
- # the print command.
-
- def print_command (self, line, script_file):
- if self.verbose:
- print "Line: " + str(self.line_ct_stack[0]) + ": " + line
- else:
- print self.print_expand(line)
- return "success"
-
-
- #### _command ###########################################
- #
-
- def _command (self, line, script_file):
- return "success"
-
-
- #### run_script_file ############################################
- # Run os8 command script file
- # Call parsers as needed for supported sub commands.
- #
- # Commands:
- # mount [ ...]
- # option: required | preserve | readonly | ro | scratch
- # umount
- # boot
- # os8
- # the rest of the line is passed verbatim to OS/8
- # pal8
- # include
- # configure
- # device: tti | tape | rx
- # tt parameter: KSR | 7b
- # tape parameter: td | dt
- # rx parameter: rx8e | rx28 | rx01 | rx02
- # enable
- # disable
- # cpto [] []
- # cpfrom []
- # format: /A | /I | /B
- # copy
- # patch
- # resume
- # restart
- # begin
- # end
- # print
- # exit
-
- # Sub-commands:
- # build, fotp, absldr
- #
- # Commands return, "success", "fail", or "die".
-
- def run_script_file (self, script_path):
- # Strings, regexps, and command arrays used by run_system
- commands = {"mount": self.mount_command,
- "boot": self.boot_command,
- "os8": self.os8_command,
- "pal8": self.pal8_command,
- "include": self.include_command,
- "begin": self.begin_command,
- "end": self.end_command,
- "exit": self.exit_command,
- "print": self.print_command,
- "umount": self.umount_command,
- "simh": self.simh_command,
- "configure": self.configure_command,
- "enable": self.enable_option_command,
- "disable": self.disable_option_command,
- "cpto": self.cpto_command,
- "cpfrom": self.cpfrom_command,
- "copy": self.copy_command,
- "resume": self.resume_command,
- "restart": self.restart_command,
- "patch": self.patch_command}
-
- try:
- script_file = open(script_path, "r")
- except IOError:
- print script_path + " not found."
- return "fail"
-
- # Every time we start a new script
- # We append a new line number count of 0
- # onto our line_ct_stack
- self.line_ct_stack.insert(0, 0)
- if self.debug: print "New line_ct_stack: " + str(self.line_ct_stack)
-
- for line in script_file:
- line = self.basic_line_parse (line, script_file)
- if line == None: continue
-
- m = re.match(_comm_re, line)
- if m == None:
- print "Ignoring command line at line " + \
- str(self.line_ct_stack[0]) + ": " + line
- continue
-
- if m.group(1) not in commands:
- print "Unrecognized script command at line " + \
- str(self.line_ct_stack[0]) + ": " + m.group(1)
- continue
-
- # print "arg: " + m.group(3)
- if m.group(3) == None: rest = ""
- else: rest = m.group(3)
- retval = commands[m.group(1)](rest, script_file)
- if retval == "die":
- print "\nFatal error encountered in " + script_path + \
- " at line " + str(self.line_ct_stack[0]) + ":"
- print "\t" + line
- sys.exit(-1)
-
- # Done. Pop the line count off our line_ct_stack
- self.line_ct_stack.pop(0)
- if self.debug: print "popped line_ct_stack: " + str(self.line_ct_stack)
-
- return "success"
-
-
- #### end_command #####################################################
-
- def end_command (self, line, script_file):
- print "Unexpectedly encountered end command at line " + \
- str(self.line_ct_stack[0]) + ": " + line
- return "fail"
-
-
- #### parse_odt #######################################################
-
- def parse_odt (self, com, line):
- if self.debug: print line
-
- if line == "\\c": return "break"
- match = _odt_parse.match(line)
- if match == None:
- print "Aborting because of bad ODT line: " + line
- self.simh.os8_send_ctrl('C')
- return "err"
- loc = match.group(1)
- old_val = match.group(2)
- new_val = match.group(3)
- expect_val_str = "\s*[0-7]{4} "
-
- if self.debug: print "Loc: " + loc + ", old_val: " + old_val + ", new_val: " + new_val
- self.simh.os8_send_str (loc + "/")
- self.simh._child.expect(expect_val_str)
-
- if old_val.isdigit(): # We need to check old value
- found_val = self.simh._child.after.strip()
- if found_val != old_val:
- print "\tOld value: " + found_val + " does not match " + old_val + ". Aborting patch."
- # Abort out of ODT back to the OS/8 Monitor
- self.simh.os8_send_ctrl('C')
- return "err"
-
- self.simh.os8_send_line (new_val)
- return "cont"
-
-
- #### futil_exit ########################################################
-
- def futil_exit (self, com, line):
- self.simh.os8_send_line(line)
- return "break"
-
-
- #### futil_file ########################################################
-
- def futil_file (self, com, line):
- # Redundant re-parse of line but nobody else wants args right now.
- match = _com_split_parse.match(line)
- if match == None:
- print "Aborting because of mal-formed FUTIL FILE command: " + line
- self.simh.os8_send_ctrl('C')
- return "err"
- fname = match.group(2)
- expect_futil_file_str = "\n" + fname + "\s+(.*)$"
- self.simh.os8_send_line (line)
- self.simh._child.expect(expect_futil_file_str)
- result = self.simh._child.after.strip()
- match = _com_split_parse.match(result)
- if match == None:
- print "Aborting because unexpected return status: " + result + " from: " + line
- self.simh.os8_send_ctrl('C')
- return "err"
- if match.group(2).strip() == "LOOKUP FAILED":
- print "Aborting because of FUTIL lookup failure on: " + fname
- self.simh.os8_send_ctrl('C')
- return "err"
- else:
- return "cont"
-
-
- #### parse_futil #####################################################
- #
- # Very simple minded:
- # If first char on line is an alpha, run the command.
- # If the first char on line is number, do the substitute command.
- #
- # Substitute command acts like ODT.
- # Future version should support the IF construct.
- #
- # When we encounter the EXIT command, we return success.
-
- def parse_futil (self, com, line):
- futil_specials = {
- "EXIT": self.futil_exit,
- "FILE": self.futil_file
- }
-
- if line[0].isdigit():
- # Treat the line as ODT
- return self.parse_odt(com, line)
- else:
- match = _com_split_parse.match(line)
- if match == None:
- print "Ignoring failed FUTIL command parse of: " + line
- return "cont"
- fcom = match.group(1)
- rest = match.group(2)
-
- if fcom not in futil_specials:
- # Blind faith and no error checking.
- self.simh.os8_send_line(line)
- return "cont"
- else:
- return futil_specials[fcom](fcom, line)
-
-
- #### run_patch_file ##################################################
-
- def run_patch_file (self, pathname):
- sys.stdout.write ("Applying patch " + os.path.basename (pathname) + "...")
- sys.stdout.flush ()
-
- try:
- patch_file = open(pathname, "r")
- except IOError:
- print pathname + " not found. Skipping."
- return "fail"
-
- special_commands = {
- "ODT": self.parse_odt,
- "R": None, # Get next parser.
- "FUTIL": self.parse_futil
- }
-
- inside_a_command = False
- the_command = ""
- the_command_parser = None
-
- # Resume OS/8 if necessary.
- if self.simh._context == "simh":
- self.resume_command(line, script_file)
-
- for line in patch_file:
- line = line.rstrip()
- if line == "": continue
- elif line[0] == '#': continue # Ignore Comments
- elif inside_a_command:
- retval = the_command_parser (the_command, line)
- if retval == "break":
- inside_a_command = False
- self.simh.os8_send_ctrl('C')
- elif retval == "err":
- patch_file.close()
- return "fail"
- elif line[0] == '.': # New OS/8 Command
- match = _com_os8_parse.match(line)
- if match == None:
- print "Aborting patch on failed OS/8 command parse of: " + line
- return "fail"
- com = match.group(1)
- rest = match.group(2)
-
- if com in special_commands:
- if com == "R":
- # Run command is special. Take arg as the command and run it.
- com = rest
- inside_a_command = True
- the_command = com
- the_command_parser = special_commands[com]
-
- # We carefully separate com and args
- # But don't make much use of that yet.
- if self.verbose and self.debug: print line
- self.simh.os8_send_cmd ("\\.", line[1:]) # Skip Prompt.
-
- patch_file.close()
-
- print "Success."
- return "success"
-
-
- #### skip_patch ######################################################
- # Returns true if the given filename matches one of the regex string
- # keys of the given skips dict and the flag value for that key is set.
- # See skips definition in make_patch, which calls this.
-
- def skip_patch (fn, skips):
- for p in skips:
- if re.search (p, fn) and skips[p]: return True
- return False
-
-
- #### call_pal8 #######################################################
- # Generic call out to PAL8 with error recovery.
- # We rely on the caller to have good specifications for source,
- # binary and optional listing files.
-
- def call_pal8 (self, source, binary):
- pal8_replies = ["ERRORS DETECTED: ", "BE\s+\S+", "CF\s+\S+", "DE\s+\S+", "DF", "IC\s+\S+", "ID\s+\S+",
- "IE\s+\S+", "II\s+\S+", "IP\s+\S+", "IZ\s+\S+", "LD\s+\S+", "LG\s+\S+", "PE\s+\S+",
- "PH\s+\S+", "RD\s+\S+", "SE\s+\S+", "UO\s+\S+", "US\s+\S+", "ZE\s+\S+", "\S+ NOT FOUND"]
-
- if self.verbose: print "Assembling " + source
- com_line = binary + "<" + source
- self.simh.os8_send_cmd ("\\.", "R PAL8")
- # Did the command successfully run and enter the command decoder?
- reply = self.simh._child.expect (self.simh._cd_replies)
- if reply != 0:
- print "PAL8 failed to start at line " + \
- str(self.line_ct_stack[0])
- return "fail"
-
- self.simh.os8_send_line (com_line)
- err_count = 0
- reply = self.simh._child.expect (pal8_replies)
- executed_line = self.simh._child.before.strip()
- reply_str = self.simh._child.after.strip()
- if reply == 0:
- self.simh._child.expect("\d+")
- err_count = int(self.simh._child.after.strip())
- reply_str += " " + self.simh._child.after.strip()
- if reply > 0 or err_count > 0:
- print "PAL8 Error: "
- print "\t*" + executed_line
- print "\t" + reply_str
- self.simh.os8_send_ctrl ('c') # exit PAL8 Just in case.
- # We could do something better than just dying, I expect.
- return "fail"
- # self.simh.os8_send_ctrl ('[') # exit PAL8
- return "success"
-
-
- #### simh_command ####################################################
- # I tried to avoid including this command but sometimes you just
- # have to reconfigure subtle bits of device drivers.
- # We assume we can call a simh command at any time, but
- # doing so puts us in the simh context that persists until we
- # issue a boot or go command.
-
- def simh_command (self, line, script_file):
- print "simh command is disabled. Line " + \
- str(self.line_ct_stack[0]) + " ignored."
- return "fail"
- if self.verbose: print line
- self.simh.send_cmd(line)
- return "success"
-
-
- #### umount_command ##################################################
- def umount_command (self, line, script_file):
- detach_comm = "det " + line
- if self.verbose: print "line: " + \
- str(self.line_ct_stack[0]) + ": " + detach_comm
- self.simh.send_cmd(detach_comm)
- return "success"
-
-
- #### make_scratch ####################################################
- # Create a copy of the contents of image file using the python
- # method to create a secure, named temp filename.
- # The caller has split the image file name into a base path,
- # and an extension which we use.
- # After copying the source image, the file is closed and the name
- # of the scratch file is returned.
- def make_scratch (self, base_imagename, extension):
- try:
- src_image = open(base_imagename+extension, "rb")
- except IOError:
- print "Open of imagefile " + base_imagename + extension + " failed."
- return None
-
- dest_image = tempfile.NamedTemporaryFile(mode='w+b', \
- prefix=base_imagename+'-temp-', \
- suffix=extension, \
- delete=False)
- destname = dest_image.name
- if self.debug: print "Temp file name is: " + destname
-
- try:
- shutil.copyfileobj(src_image, dest_image)
- except shutil.Error as e:
- print "Copy of imagefile " + base_imagename + extension + \
- " to scratch failed with error: " + str(e)
- return None
- except IOError as e:
- print "Copy of imagefile " + base_imagename + extension + \
- ", failed with IOError: " + \
- str(e)
- return None
- src_image.close()
- dest_image.close()
- self.scratch_list.append(destname)
- return destname
-
-
- #### mount_command ###################################################
- # mount [option ...]
- #
- # Interface to SIMH attach command with options that do a bit more.
- #
- # Options:
- # required: is required to exist, otherwise abort the script.
- # preserve if already exists, create a copy with a
- # version number suffix. This is useful when you want to prevent
- # overwrites of a good image file with changes that might not work.
- # os8-run preserves all versions seen and creates a new
- # version that doesn't overwrite any of the previous ones.
- # readonly: Passes the `-r` option to SIMH attach to mount the
- # device in read only mode.
- # ro: abbreviation for readonly.
- # scratch: Create a writeable scratch copy of the named image
- # file and mount it. This is helpful when you are booting a
- # distribution DECtape. Booted DECtape images must be writeable.
- # To protect a distribution DECtape, use this option.
- # When all script runs are done, the scratch version is deleted.
- # new: If there is an existing file, move it aside as a .save because
- # we want to create a new empty image file.
- #
- # If the mount command fails for any reason, we presume
- # it is a fatal error and abort the script.
-
- def mount_command (self, line, script_file):
- m = re.match(_mount_re, line)
- if m == None:
- print "At line " + str(self.line_ct_stack[0]) + \
- ", could not parse mount. Ignoring: {" + line + "}."
- return "die"
- simh_dev = m.group(1)
- unit = m.group(2)
- rest = m.group(3)
- parts = rest.split()
- if len(parts) == 0:
- print "At line " + str(self.line_ct_stack[0]) + \
- ": No image name specified in: {" + line + "}"
- return "die"
- ro_arg = ""
- imagename = self.path_expand(parts[0])
- if imagename == None: return "die"
-
- dot = imagename.rindex(".")
- base_imagename = imagename[:dot]
- extension = imagename[dot:]
- copy_imagename = ""
- # Case of additional arguments.
- if len (parts) > 1:
- # Perform must_exist before scratch
- if "new" in parts[1:]:
- save_if_needed(imagename)
- if "must_exist" in parts[1:] or "required" in parts[1:]:
- if not os.path.exists(imagename):
- print "At line " + str(self.line_ct_stack[0]) + \
- ", " + imagename + " must exist but was not found. Not mounting."
- return "die"
- if "scratch" in parts[1:]:
- copy_imagename = self.make_scratch(base_imagename, extension)
- if copy_imagename == None: return "die"
- imagename = copy_imagename
-
- if "readonly" in parts[1:] or "ro" in parts[1:]:
- if copy_imagename != "":
- print "At line " + str(self.line_ct_stack[0]) + \
- ": You don't really need to set readonly on a scratch copy."
- ro_arg = "-r "
- if "no_overwrite" in parts[1:] or "preserve" in parts[1:]:
- if copy_imagename != "":
- print "Ignoring preserve option at line " + \
- str(self.line_ct_stack[0]) + " because scratch option is present."
- else:
- next_tape = 0
- while os.path.isfile(imagename):
- print "Found: " + imagename
- next_tape += 1
- imagename = base_imagename + "-" + str(next_tape) + extension
- if unit == None or unit == "":
- print "Need unit number for: " + line
- return "die"
-
- if simh_dev not in _os8_from_simh_dev:
- print "At line " + str(self.line_ct_stack[0]) + \
- ": Unrecognized simh dev: " + simh_dev
- return "die"
- os8dev = _os8_from_simh_dev[simh_dev]
-
- attach_comm = "att " + ro_arg + simh_dev + unit + " " + imagename
-
- if self.verbose: print "Line " + str(self.line_ct_stack[0]) + \
- ": mount: " + attach_comm
- self.simh.send_cmd(attach_comm)
- return "success"
-
-
- #### boot_command ####################################################
- #
- # Check to see if the device to be booted has something attached.
- # If not die.
- # If so, boot it, and set our booted state to True.
-
- def boot_command (self, line, script_file):
- # First confirm something is attached to boot from.
- ucname = line.upper()
- boot_replies = [ucname + "\s+(.+)\r", "Non-existent device"]
- self.simh.send_cmd("show " + line)
- retval = self.simh._child.expect(boot_replies)
- if retval == 1:
- print "Attempt to boot non-existent device: " + line
- return "die"
- m = re.match("^(\S+)\s(\S+),\s+(attached to |not attached)(\S+)?,\s+(.+)\r",
- self.simh._child.after)
- if m == None:
- print "Could not determine if device " + line + " is attached."
- return "die"
-
- # Caution match group we want ends with a space.
- if m.group(3) != "attached to ":
- print "Attempt to boot on non-attached device: " + line
- return "die"
-
- boot_comm = "boot " + line
- if self.verbose: print "Line " + str(self.line_ct_stack[0]) + ": " + \
- boot_comm
- self.simh.send_cmd(boot_comm)
- self.booted = True
- return "success"
-
-
- #### os8_command #####################################################
-
- def os8_command (self, line, script_file):
- if not self.booted:
- print "Cannot run os8 command at line " + \
- str(self.line_ct_stack[0]) + ". OS/8 has not been booted."
- return "die"
-
- os8_comm = line
- if self.verbose: print "Line: " + \
- str(self.line_ct_stack[0]) + ": os8_command: " + os8_comm
-
- # Resume OS/8 if necessary.
- if self.simh._context == "simh":
- self.resume_command(line, script_file)
-
- self.simh.os8_send_cmd ("\\.", os8_comm, self.debug)
- return "success"
-
-
- #### pal8_command ####################################################
- # The "pal8" script command comes in two forms:
- # The two argument form where the PAL8 status is printed on the fly
- # and the 3 argument form where all status goes into the listing file.
- # We do the 3 argument form with a simple "os8" script command.
-
- def pal8_command (self, line, script_file):
- if not self.booted:
- print "Cannot run pal8 command at line " + \
- str(self.line_ct_stack[0]) + ". OS/8 has not been booted."
- return "die"
-
- m_2form = re.match (_two_arg_pal_re, line)
- if m_2form != None:
-
- # Resume OS/8 if necessary.
- if self.simh._context == "simh":
- self.resume_command(line, script_file)
-
- # Call the 2arg pal8 code that works hard at error analysis.
- return self.call_pal8 (m_2form.group(4), m_2form.group(1))
- else:
- m_3form = re.match (_three_arg_pal_re, line)
- if m_3form != None:
- # Just run the OS/8 command.
- os8_comm = "pal8 " + line
- if self.verbose: print "Line: " + \
- str(self.line_ct_stack[0]) + ": Calling 3-arg pal8 command: " + os8_comm
- self.simh.os8_send_cmd ("\\.", os8_comm)
- else:
- print "At line " + str(self.line_ct_stack[0]) + \
- ": Unrecognized pal8 form: {" + line + "}."
- return "fail"
- return "success"
-
-
- #### begin_command ###################################################
-
- def begin_command (self, line, script_file):
- if not self.booted:
- print "Cannot execute begin subcommand block at line " + \
- str(self.line_ct_stack[0]) + ". OS/8 has not been booted."
- return "die"
-
- sub_commands = {"fotp": self.fotp_subcomm, "build": self.build_subcomm,
- "absldr": self.absldr_subcomm,
- "cdprog": self.cdprog_subcomm}
-
- m = re.match(_comm_re, line)
- if m == None:
- print "Could not parse sub-command at line " + \
- str(self.line_ct_stack[0]) + ": " + line
- if m.group(1) not in sub_commands:
- print "Ignoring unrecognized sub-command at line " + \
- str(self.line_ct_stack[0]) + ": " + m.group(1)
- print "Ignoring everything to next end."
- self.ignore_to_subcomm_end(line, script_file, "")
- return "fail"
- else:
- # Resume OS/8 if necessary.
- if self.simh._context == "simh":
- self.resume_command(line, script_file)
-
- return sub_commands[m.group(1)](m.group(3), script_file)
-
-
- #### run_build_build #################################################
- # ***CAUTION***
- # When you do this you are instructing BUILD to
- # OVERWRITE the system area. If you do this to your
- # running RK05 pack by mistake, you WILL make a mess
- # and need to re-run mkos8 to re-make it.
-
- def run_build_build (self, os8_spec, cd_spec):
- self.simh.os8_send_cmd ("\\$", "BUILD", debug=True)
- self.simh.os8_send_cmd ("LOAD OS/8: ", os8_spec, debug=True)
- self.simh.os8_send_cmd ("LOAD CD: ", cd_spec, debug=True)
- return "success"
-
-
- #### build_subcomm ###################################################
-
- def build_subcomm (self, old_line, script_file):
- # A race condition results if we send ^C when we are already at
- # Monitor level. So need_exit gets set to False, when we know
- # we have already exited build, and are at the monitor prompt.
- need_exit = True
- os8_comm = "RU " + old_line
- if self.verbose: print "Line " + str(self.line_ct_stack[0]) + ": " + \
- os8_comm
- prompt_str = "\n\\$$"
- if self.debug:
- print "sending to simh: " + os8_comm
- print " and expecting prompt: '\\n\\\\$$'"
- self.simh.os8_send_cmd ("\\.", os8_comm)
- self.simh._child.expect(prompt_str)
-
- for line in script_file:
- # if self.debug:
- # print "line: " + line
- line = self.basic_line_parse(line, script_file)
- if line == None: continue
-
- m = re.match(_comm_re, line)
- if m == None:
- print "Ignoring mal-formed build sub-command at line " + \
- str(self.line_ct_stack[0]) + ": " + line
- continue
-
- build_sub = m.group(1)
- rest = m.group(3)
- if rest == None: rest = ""
-
- if build_sub not in _build_comm_regs:
- print "Unrecognized BUILD command at line " + \
- str(self.line_ct_stack[0]) + ": " + build_sub
- continue
-
- if build_sub == "end":
- if rest == "":
- print "Warning! end statement encountered inside build with no argument at line " + \
- str(self.line_ct_stack[0]) + ".\nExiting build."
- return "fail"
- elif rest != "build":
- print "Warning! Mismatched begin/end blocks in BUILD at line " + \
- str(self.line_ct_stack[0]) + ".\nEncountered end: {" + rest + "}. Exiting BUILD."
- return "fail"
-
- if self.verbose:
- print "Line " + str(self.line_ct_stack[0]) + ": end BUILD"
- if self.debug:
- print "before: " + self.simh._child.before.strip()
- print "after: " + self.simh._child.after.strip()
- # Return to monitor level unless need_exit == False.
- if need_exit:
- self.simh.os8_send_ctrl ('c')
-
- return "success"
-
- build_re = _build_comm_regs[build_sub]
-
- if build_re != None:
- m2 = re.match(build_re, rest)
- if m2 == None:
- print "Ignoring mal-formed BUILD at line " + \
- str(self.line_ct_stack[0]) + ": " + build_sub + " command: " + rest
- continue
-
- if build_sub == "BUILD":
- if m2.group(1) == None or m2.group(1) == "":
- print "Missing source of OS8.BN. Ignoring BUILD command at line " + \
- str(self.line_ct_stack[0]) + "."
- continue
- else: kbm_arg = m2.group(1)
-
- if m2.group(3) == None or m2.group(3) == "":
- print "Missing sorce of CD.BN. Ignoring BUILD command at line " + \
- str(self.line_ct_stack[0]) + "."
- continue
- else: cd_arg = m2.group(3)
-
- if self.verbose: print "Line " + str(self.line_ct_stack[0]) + \
- ": BUILD KBM: " + kbm_arg + ", CD: " + cd_arg
- if self.debug:
- print "sending to simh: BUILD"
- self.simh.os8_send_line ("BUILD")
-
- build_build_replies = ["LOAD OS/8: "]
- build_build_replies.extend(_build_replies)
-
- if self.debug:
- print "expecting: " + str(build_build_replies)
- reply = self.simh._child.expect(build_build_replies)
- if self.debug:
- print "reply: " + str(reply)
- print "before: " + self.simh._child.before.strip()
- print "after: " + self.simh._child.after.strip()
- if reply != 0:
- print "No prompt for LOAD OS/8 in BUILD command within BUILD at line " + \
- str(self.line_ct_stack[0]) + "."
- print "Instead got: {" + self.simh._child.after + "}."
- print "Exiting BUILD."
- return "die"
- if self.debug:
- print "sending to simh: " + kbm_arg
- self.simh.os8_send_line (kbm_arg)
-
- build_build_replies = ["LOAD CD: "]
- build_build_replies.extend(_build_replies)
-
- if self.debug:
- print "expecting: " + str(build_build_replies)
- reply = self.simh._child.expect(build_build_replies)
- if self.debug:
- print "reply: " + str(reply)
- print "before: " + self.simh._child.before.strip()
- print "after: " + self.simh._child.after.strip()
- if reply != 0:
- print "No prompt for LOAD CD in BUILD command within BUILD at line " + \
- str(self.line_ct_stack[0]) + "."
- print "Instead got: {" + self.simh._child.after + "}."
- print "Exiting BUILD."
- return "die"
- if self.debug:
- print "sending to simh: " + cd_arg
- self.simh.os8_send_line (cd_arg)
-
- # Done with BUILD command dialog within BUILD.SV
- # Get that BUILD.SV prompt.
- if self.debug:
- print "Expecting prompt: '\\n\\\\$$'"
- self.simh._child.expect(prompt_str)
- if self.debug:
- print "Resume BUILD.SV command loop."
- continue
-
- comm = build_sub + " " + rest
- if self.verbose: print "Line " + str(self.line_ct_stack[0]) + \
- ": BUILD-> " + comm
-
- if self.debug:
- print "sending to simh: " + comm
- self.simh.os8_send_line (comm)
- if self.debug:
- print "expecting: " + str(_build_replies)
- reply = self.simh._child.expect(_build_replies)
- if self.debug:
- print "reply: " + str(reply)
- print "before: " + self.simh._child.before.strip()
- print "after: " + self.simh._child.after.strip()
- if reply > 3:
- print "BUILD error at line " + str(self.line_ct_stack[0]) + \
- " with command " + self.simh._child.before.strip()
- print "\t" + self.simh._child.after.strip()
- self.simh.os8_send_ctrl ('c')
- # Special case "BOOT" sub-command: May ask, "WRITE ZERO DIRECT?"
- if build_sub == "BOOT":
- if reply == 2:
- if self.debug:
- print "Boot received \"WRITE ZERO DIRECT?\""
- print "sending to simh: Y"
- self.simh.os8_send_line("Y")
- if self.debug:
- print "Expecting \"SYS BUILT\""
- reply = self.simh._child.expect("SYS BUILT")
- if self.debug:
- print "ZeroDir: reply: " + str(reply)
- print "before: " + self.simh._child.before.strip()
- print "after: " + self.simh._child.after.strip()
- need_exit = False
- elif reply == 0:
- reply = self.simh._child.expect("SYS BUILT")
- if self.debug:
- print "$: reply: " + str(reply)
- print "before: " + self.simh._child.before.strip()
- print "after: " + self.simh._child.after.strip()
- need_exit = False
- elif reply == 1:
- reply = self.simh._child.expect("\\.")
- if self.debug:
- print "SysBuilt: reply: " + str(reply)
- print "before: " + self.simh._child.before.strip()
- print "after: " + self.simh._child.after.strip()
- print "Warning end of file encountered with no end of BUILD command block at line " + \
- str(self.line_ct_stack[0]) + "."
- return "fail"
-
- #### cdprog_subcomm ##################################################
- # Cycle through OS/8 command decoder with the command specified
- # in the argument.
-
- def cdprog_subcomm (self, old_line, script_file):
- os8_comm = "RU " + old_line
- end_str = "cdprog " + old_line
- if self.verbose: print "Line: " + \
- str(self.line_ct_stack[0]) + ": " + os8_comm
- self.simh.os8_send_cmd ("\\.", os8_comm)
-
- for line in script_file:
- line = self.basic_line_parse(line, script_file)
- if line == None: continue
-
- # Test for special case, "end" and act on it if present.
- m = re.match(_comm_re, line)
- if m != None and m.group(1) != None and m.group(1) != "" and m.group(1) == "end":
- rest = m.group(3)
- retval = "fail" # Return fail unless proven successful.
- if rest == None or rest == "":
- print "Warning! end statement encountered inside cdprog with no argument at line " + \
- str(self.line_ct_stack[0]) + "."
- print "Expecting: {" + end_str + "}."
- elif rest != end_str:
- print "Warning! Mismatched begin/end blocks in cdprog at line " + \
- str(self.line_ct_stack[0]) + ".\n"
- print "Expecting: {" + end_str + "}. Got: {" + rest + "}.\n"
- else:
- retval = "success"
- if self.verbose: print "Line " + str(self.line_ct_stack[0]) + ": end " + end_str
- if retval == "fail":
- print "Exiting cdprog, possibly earlier than expected at line " + \
- str(self.line_ct_stack[0]) + "."
- self.simh.os8_send_ctrl ('[')
- return retval
-
- # We could do some basic OS/8 command decoder synax checking here.
- comm = line
- if self.verbose: print "Line: " + \
- str(self.line_ct_stack[0]) + ": * " + line
- self.simh.os8_send_cmd ("\\*", line)
- print "Warning end of file encountered at line " + \
- str(self.line_ct_stack[0]) + " with no end of cdprog command block."
- self.simh.os8_send_ctrl ('[')
- return "fail"
-
-
- #### fotp_subcomm ####################################################
-
- def fotp_subcomm (self, old_line, script_file):
- os8_comm = "RU " + old_line
- if self.verbose: print "Line: " + \
- str(self.line_ct_stack[0]) + ": " + os8_comm
- self.simh.os8_send_cmd ("\\.", os8_comm)
-
- for line in script_file:
- line = self.basic_line_parse(line, script_file)
- if line == None: continue
-
- # Test for special case, "end" and act on it if present.
- m = re.match(_comm_re, line)
- if m != None and m.group(1) != None and m.group(1) != "" and m.group(1) == "end":
- rest = m.group(3)
- if rest == None or rest == "":
- print "Warning! end statement encountered inside fotp with no argument at line " + \
- str(self.line_ct_stack[0]) + "."
- return "fail"
- elif rest != "fotp":
- print "Warning! Mismatched begin/end blocks in FOTP at line " + \
- str(self.line_ct_stack[0]) + ".\nEncountered end: {" + rest + "}. Exiting FOTP."
- return "fail"
-
- if self.verbose: print "Line " + str(self.line_ct_stack[0]) + ": end FOTP"
- self.simh.os8_send_ctrl ('[')
- return "success"
-
- m = re.match(_fotp_re, line)
- if m == None:
- print "At line " + str(self.line_ct_stack[0]) + \
- ": ignoring mal-formed fotp file spec: {" + line + "}."
- continue
-
- comm = line
- if self.verbose: print "Line: " + \
- str(self.line_ct_stack[0]) + ": * " + line
- self.simh.os8_send_cmd ("\\*", line)
- print "Warning end of file encountered at line " + \
- str(self.line_ct_stack[0]) + " with no end of FOTP command block."
- return "fail"
-
-
- #### absldr_subcomm ##################################################
- # A clone of fotp_subcom. Can we find a way to merge the common code?
-
- def absldr_subcomm (self, old_line, script_file):
- os8_comm = "RU " + old_line
- if self.verbose: print " line: " + \
- str(self.line_ct_stack[0]) + ": " + os8_comm
- self.simh.os8_send_cmd ("\\.", os8_comm)
-
- for line in script_file:
- line = self.basic_line_parse(line, script_file)
- if line == None: continue
-
- # Test for special case, "end" and act on it if present.
- m = re.match(_comm_re, line)
- if m != None and m.group(1) != None and m.group(1) != "" and m.group(1) == "end":
- rest = m.group(3)
- if rest == None or rest == "":
- print "Warning! end statement encountered inside ABSLDR with no argument at line " + \
- str(self.line_ct_stack[0]) + "."
- return "fail"
- elif rest != "absldr":
- print "Warning! Mismatched begin/end blocks in ABSLDR at line " + \
- str(self.line_ct_stack[0]) + ".\nEncountered end: {" + rest + "}. Exiting ABSLDR."
- return "fail"
-
- if self.verbose: print "End ABSLDR at line " + str(self.line_ct_stack[0]) + "."
- self.simh.os8_send_ctrl ('[')
- return "success"
-
- m = re.match(_absldr_re, line)
- if m == None:
- print "Ignoring mal-formed absldr file spec at line " + str(self.line_ct_stack[0]) + \
- ": {" + line + "}."
- continue
-
- comm = line
- if self.verbose: print "Line : " + str(self.line_ct_stack[0]) + "* " + line
- self.simh.os8_send_cmd ("\\*", line)
- print "Warning end of file encountered at line " + \
- str(self.line_ct_stack[0]) + "with no end of ABSLDR command block."
- return "fail"
-
-
- #### check_exists ####################################################
- # Check existence of all files needed
-
- def check_exists (s, image_copyins):
- for copyin in image_copyins:
- image = copyin[1]
- image_path = dirs.os8mi + image
- if (not os.path.isfile(image_path)):
- print "Required file: " + image_path + " not found."
- mkos8_abort(s)
- # else: print "Found " + image_path
-
-
- #### Data Structures ################################################
- #
- # The make procedures use helper procedures
- # to confirm that the relevant input image file exists and
- # to perform the file copies.
- #
- # A data structure called "image copyin"
- # describes the image file pathname relative to an implied root,
- # provides a message string when the action is run,
- # names a default destination device for whole image content copies,
- # offers an optional array of specific file copy actions.
- #
- # FUTURE: Parse source path for ".tu56" vs. ".rk05" for more general use.
- # Currently all code assumes a copyin comes from a DECtape image.
- #
- # Example: We Install all files for ADVENT, the Adventure game:
- #
- # advent_copyin = ['RKB0:', 'subsys/advent.tu56', "Installing ADVENT...", None]
- #
- # A DECtape device is chosen for attachment in SIMH and
- # a 'COPY *.*' command is filled in with the Destination device, and the chosen DECtape.
- #
- # A data structer called "file copyin"
- # provides override destination to allow renames or varied destinations.
- # names individual files within a copyin to use
- #
- # Example: To copy the C compiler we want all .SV files on SYS
- # but everything else to RKB0:
- # (Note the useful /V option to invert the match.)
- #
- # cc8_sv_file_copyin = ['SYS:', '*.SV']
- # cc8_rest_file_copyin = ['RKB0:', '*.SV/V']
- #
- # A 'COPY' command is filled in with the override destination and
- # The file spec is used with the chosen dectape instead of "*.*"
- #
-
- #### copyin_pair #####################################################
- # Copy two images into two destinations with two messages
- #
- # Assumes our context is "in simh".
- # Assumes dt0 and dt1 are free.
- # Assumes rk0 is the boot device
- # Detaches dt0 and dt1 after using them.
- # copyin0 mounts on dt0. copyin1 mounts on dt1.
- # Either copyin or both can be None
-
- def copyin_pair (s, copyin0, copyin1, debug):
- if debug:
- if copyin0:
- print "Copying: " + copyin0[1] + " to: " + copyin0[0] + "from dt0"
- else: print "copyin0 is empty."
- if copyin1:
- print "Copying: " + copyin1[1] + " to: " + copyin1[0] + "from dt1"
- else: print "copyin1 is empty."
-
- if not copyin0 and not copyin1: return # Nothing to do.
-
- # The order of events here is a bit funky because we want
- # to use both DECtape drives but also
- # switch between SIMH and OS/8 as infrequently as possible.
-
- if copyin0: self.simh.send_cmd ("attach -r dt0 " + dirs.os8mi + copyin0[1])
- if copyin1: self.simh.send_cmd ("attach -r dt1 " + dirs.os8mi + copyin1[1])
-
- self.simh.os8_restart()
-
- if copyin0:
- if self.verbose: print copyin0[2]
- if copyin0[3]: # We have specific files to do.
- for file_copyin in copyin0[3]:
- self.simh.os8_send_cmd ("\\.", "COPY " + file_copyin[0] + " where destination and default option /A is implied.
+# Or where destination is implied and option is set.
+# Or where option /A is implied.
+# Or are explicit.
+# Valid options are "/A", "/I", and "/B"
+# Use two regex's in order:
+# in group 1, in group 2.
+# Option is one of /I /B /A in group 4.
+
+# source in group 1, option in group 3.
+_from_to_re_1 = re.compile ("^(\S+)(\s+(/[AIB]))?$")
+# source in group 1, destination in group 2, option in group 4.
+_from_to_re_2 = re.compile ("^(\S+)\s+(\S+)(\s+(/[AIB]))?$")
+
+# Array of regular expressions for syntax checking inside BUILD
+_build_comm_regs = {"LOAD" : re.compile("^(\S+:)?\S+(.BN)?$"),
+ "UNLOAD": re.compile("^\S+(,\S+)?$"),
+ "INSERT": re.compile("^\S+,\S+(,\S+)?$"),
+ "DELETE": re.compile("^\S+(,\S+)?$"),
+ "SYSTEM": re.compile("^\S+$"),
+ "DSK" : re.compile("^(\S+:)?\S+$"),
+ "BUILD" : re.compile("^(\S+(.BN)?)\s+(\S+(.BN)?)$"),
+ "PRINT" : None,
+ "BOOT" : None,
+ "end" : None}
+
+_build_replies = ["\\$", "SYS BUILT", "WRITE ZERO DIRECT\\?", "\\?BAD ARG",
+ "\\?BAD INPUT", "\\?BAD LOAD",
+ "\\?BAD ORIGIN", "\\?CORE", "\\?DSK", "\\?HANDLERS",
+ "I/O ERR", "\\?NAME", "NO ROOM", "SYS NOT FOUND",
+ "\\?PLAT", "\\?SYNTAX", "\\?SYS", "SYS ERR",
+ "\S+ NOT FOUND"]
+
+# Parse two whitspace separated arguments into group(1) and group(2)
+_two_args_re = re.compile("^(\S+)\s+(\S+)$")
+
+_rx_settings = ["rx01", "rx02", "RX8E", "RX28"]
+_tape_settings = ["td", "dt"]
+_tti_settings = ["KSR", "7b"]
+_configurables = {"rx": _rx_settings, "tape": _tape_settings,
+ "tti": _tti_settings}
+
+# Matches if the string begins with a dollar sign, and has at least
+# one slash, returning the string between the dollar sign and the
+# first slash in group 1 and the rest in group 2.
+# No whitespace in the string.
+_expandable_re = re.compile ("^\$([^/\s]+)/(\S*)$")
+
+# Parse an exit arg for an integer or an integer in parentheses
+_exit_arg_re = re.compile ("^(\s*[+-]?\s*\d+)|\s*\(\s*([+-]?\s*\d+)\s*\)\s*$")
+
+# Options enabled/not_disabled for conditional execution in scripts.
+#
+# Earlier code allowed --enable and --disable. We interface to it.
+# We maintain two arrays: options_enable and options_disabled for those
+# two argument constructs.
+#
+# Argument parsing of repeated enable and disable arguments is as follows:
+# --enable looks on options_disabled and if present removes it, then adds
+# to options_enabled.
+# --disable looks on options_enabled and if present removes it, then adds
+# to options_enabled.
+#
+# Last seen enable/disable command line or executed command for a
+# particular option wins.
+#
+# Scripts have enable/disable commands that are run-time
+# changers of the contents of options_enabled and options_disabled.
+#
+# When we run a script we have begin/end blocks for enabled/not_disabled options:
+# "begin enabled " ... "end enabled
+# "begin not_disabled " ... "end not_disabled
+#
+# The enabled block looks for an explicit enablement on the options_enabled
+# list. If none is found we default to ignoring the contents of the block.
+#
+# The not_disabled block looks for an explicit disablement on the
+# options_disabled lis. If found, the block is ignored. Otherwise
+# the block defaults to being executed.
+#
+# begin/end blocks can be nested. We track the nesting with options_stack.
+# Testing for options happens when the begin command is evaluated.
+# So changing an enable/disable option inside a begin/end block
+# takes effect at the next begin statement.
+# You can write a script as follows:
+# enable foo
+# begin enabled foo
+# # Commands to executed
+# disable foo
+# # Commands still being executed.
+# begin enabled foo
+# # Commands to ignore
+# end enabled foo
+# end enabled foo
+
+# Local routine to perform a save of a pre-existing file
+# because we do this in a couple places
+
+def save_if_needed(path):
+ if os.path.isfile(path):
+ save_path = path + ".save"
+ print("Pre-existing " + path + " found. Saving as " + path + ".save")
+ if os.path.isfile(save_path):
+ print("Overwriting old " + path + ".save")
+ os.remove(save_path)
+ os.rename(path, save_path)
+
+def version_to_array (version):
+ vers_array = []
+ this_str = ""
+
+ for c in version:
+ if c != ".":
+ this_str += c
+ else:
+ vers_array.append(this_str)
+ this_str = ""
+ if this_str != "": vers_array.append(this_str)
+ return vers_array
+
+
+class os8script:
+ # Contains a simh object, other global state and methods
+ # for running OS/8 scripts under simh.
+ #### globals and constants ###########################################
+
+
+ def __init__ (self, simh, enabled_options, disabled_options, verbose=False, debug=True):
+ self.lang_version = LANG_VERSION
+ self.verbose = verbose
+ self.debug = debug
+ self.simh = simh
+ self.options_enabled = enabled_options
+ self.options_disabled = disabled_options
+ # Do we need separate stacks for enabled/disabled options?
+ self.options_stack = []
+ # List of scratch files to delete when we are done with all script runs.
+ self.scratch_list = []
+ self.booted = False
+ self.line_ct_stack = []
+
+
+ #### path_expand #######################################################
+ # Simple minded variable substitution in a path.
+ # A path beginning with a dollar sign parses the characters between
+ # the dollar sign and the first slash seen becomes a name to
+ # expand with a couple local names: $home and the anchor directories
+ # defined in lib/pidp8i/dirs.py.
+ # Returns None if the expansion fails. That signals the caller to fail.
+
+ def path_expand (self, path):
+ m = re.match(_expandable_re, path)
+ if m == None: return path
+ var = m.group(1)
+
+ val = getattr (dirs, var, None)
+ if val != None:
+ return os.path.join(val,m.group(2))
+ else:
+ print("At line " + str(self.line_ct_stack[0]) + \
+ ": {$" + var + "} is not a valid path expansion in " + path)
+ return None
+
+
+ #### print_expand ######################################################
+ # Close kin to path_expand. Takes a string that may name a path
+ # substitution or the magic $version value and performs the appropriate
+ # value substitution.
+
+ def print_expand (self,str):
+ end = str.find("$")
+ if end == -1: return str
+
+ m = re.findall("\$\S+",str)
+ if m == None: return str
+
+ outstr = ""
+ start = 0
+
+ for name in m:
+ end = str.index(name, start)
+ outstr += str[start:end]
+
+ sub = getattr (dirs, name[1:], None)
+ if sub == None:
+ if name == "$version": sub = self.lang_version
+ else: sub = name
+
+ outstr += sub
+ start = end + len(name)
+
+ return outstr
+
+
+ #### version_test ######################################################
+ # Compare each component of the version test agains the actual version
+ # Return true if actual version is greater than or equal to the test
+ # version.
+ # Caller validates test with _version_parse_re so we only
+ # need to return True or False, not error.
+
+ def version_test (self, test):
+ test_array = version_to_array(test)
+ version_array = version_to_array(self.lang_version)
+
+ idx = 0
+ endpoint = len(test_array)
+
+ while idx < endpoint:
+ # If version has more digits than test, the greater than test succeeds.
+ if idx >= len(version_array):
+ vers_item = "0"
+ else:
+ vers_item = version_array[idx]
+ test_item = test_array[idx]
+ if self.debug:
+ print("version_test: vers_item: " + vers_item + \
+ ", test_item: " + test_item)
+
+ vers_num = int(vers_item)
+ test_num = int(test_item)
+
+ # First time version componet greater than test -> success.
+ if vers_num > test_num:
+ if self.debug:
+ print("version_test: Success: version greater than test.")
+ return True
+ # First time version component less than test -> failure.
+ elif test_num > vers_num:
+ if self.debug: print("version_test: Fails on sub compare.")
+ return False
+ #Otherwise is equal. Keep going.
+
+ idx += 1
+ # Made it all the way through. Test succeeds.
+ if self.debug:
+ print("version_test: Success. Made it thru test string.")
+ return True
+
+
+ #### basic_line_parse ################################################
+ # Returns stripped line and any other cleanup we want.
+ # Returns None if we should just 'continue' on to the next line.
+ # Filters out comments.
+ # Processes the option begin/end blocks.
+
+ def basic_line_parse (self, line, script_file):
+ self.line_ct_stack[0] += 1
+ retval = line.strip()
+ if retval == "": return None
+ elif retval[0] == "#": return None
+ # First test if we are in a begin option block
+ m = re.match (_begin_en_dis_comm_re, retval)
+ if m != None:
+ en_dis = m.group(1)
+ rest = m.group(2)
+ if self.verbose: print("Line " + str(self.line_ct_stack[0]) + \
+ ": doing_begin_option: " + en_dis + " " + rest)
+ if self.debug:
+ print("options_enabled: " + str (self.options_enabled))
+ print("options_disabled: " + str (self.options_disabled))
+ print("options_stack: " + str(self.options_stack))
+
+ vers_match = False
+ if en_dis == "version":
+ # Check for mal-formed version match first
+ if re.match (_version_parse_re, rest) == None:
+ print("Mal-formed version match string {" + rest + "} at line " + \
+ str(self.line_ct_stack[0]) + ". Ignoring this block.")
+ self.ignore_to_subcomm_end (retval, script_file, en_dis + " " + rest)
+ return None
+ vers_match = self.version_test (rest)
+ if vers_match:
+ # Block is active. We push it onto the stack
+ if self.debug:
+ print("Pushing version enabled block " + rest + \
+ " onto options_stack")
+ self.options_stack.insert(0, rest)
+ if self.debug: print(" new options_stack: " + \
+ str(self.options_stack))
+ else:
+ # Option is inactive. Ignore all subseqent lines
+ # until we get to an end command that matches our option.
+ self.ignore_to_subcomm_end (retval, script_file, en_dis + " " + rest)
+ return None
+
+ elif en_dis == "enabled":
+ if rest in self.options_enabled:
+ # Block is active. We push it onto the stack
+ if self.debug:
+ print("Pushing enabled block " + rest + " onto options_stack")
+ self.options_stack.insert(0, rest)
+ if self.debug:
+ print("new options_stack: " + str(self.options_stack))
+ else:
+ # Option is inactive. Ignore all subseqent lines
+ # until we get to an end command that matches our option.
+ self.ignore_to_subcomm_end (retval, script_file, en_dis + " " + rest)
+
+ return None
+ # only other choice is disabled because of our regex.
+ else:
+ if rest not in self.options_disabled:
+ # Block defaults to active. We push it onto the stack
+ if self.debug:
+ print("Pushing not_disabled block " + rest + \
+ " onto options_stack")
+ self.options_stack.insert(0, rest)
+ if self.debug:
+ print("new options_stack: " + str(self.options_stack))
+ else:
+ # Block is inactive. Ignore all subseqent lines
+ # until we get to an end command that matches our option.
+ self.ignore_to_subcomm_end (retval, script_file, en_dis + " " + rest)
+ return None
+
+ m = re.match(_end_en_dis_comm_re, retval)
+ if m != None:
+ rest = m.group(2)
+ if self.verbose:
+ print("Line " + str(self.line_ct_stack[0]) + ": end rest = " + rest)
+ if (rest == None or rest == ""):
+ print("Warning! option end statement at line " + \
+ str(self.line_ct_stack[0]) + " encountered with no argument.")
+ return None
+ if len(self.options_stack) == 0:
+ print("Warning! option end statement at line " + \
+ str(self.line_ct_stack[0]) + \
+ " found with no matching begin for option: " + rest)
+ return None
+ if rest != self.options_stack[0]:
+ print("Warning! Mismatched option begin/end group at line " + \
+ str(self.line_ct_stack[0]) + ". Currently inside option: " + \
+ self.options_stack[0] + " not " + rest)
+ return None
+ else:
+ if self.debug: print("Popping " + self.options_stack[0])
+ self.options_stack.pop(0)
+ if self.debug:
+ print("new options_stack: " + str(self.options_stack))
+ return None
+
+ return retval
+
+
+ #### ignore_to_subcomm_end ###########################################
+
+ def ignore_to_subcomm_end (self, old_line, script_file, end_str):
+ if self.debug: print("ignore to: " + end_str)
+ for line in script_file:
+ self.line_ct_stack[0] += 1
+ line = line.strip()
+ if self.verbose:
+ print("Ignore line " + str(self.line_ct_stack[0]) + ": " + line)
+
+ m = re.match(_end_comm_re, line)
+ if m == None: continue
+
+ rest = m.group(1)
+ if rest == None: rest = ""
+
+ if rest == end_str: return
+
+
+ #### include_command #################################################
+ # Call run_system_script recursively on the file path provided.
+
+ def include_command (self, line, script_file):
+ path = self.path_expand(line)
+ if path == None:
+ print("Ignoring: \n\tinclude " + line)
+ return "fail"
+
+ if not os.path.isfile(path):
+ print("Line " + str(self.line_ct_stack[0]) + \
+ ": Could not find include file: " + path)
+ return "fail"
+ if self.verbose:
+ print("line: " + str(self.line_ct_stack[0]) + ": include " + line)
+ return self.run_script_file (path)
+
+
+ #### enable_option_command ###########################################
+ # Deletes an option from the list of active options.
+ # Parses the first argument after "enable" as the key to enable.
+ # The end of the key is the end of the line or the first whitespace
+ # character.
+
+ def enable_option_command (self, line, script_file):
+ if line == "":
+ print("Empty option to enable at line: " + \
+ str(self.line_ct_stack[0]) + ".")
+ return "fail"
+ m = re.match(_comm_re, line)
+ if m == None:
+ print("Could not parse enable command at line " + \
+ str(self.line_ct_stack[0]) + ".")
+ return "fail"
+ option = m.group(1)
+ if option == None:
+ print("Empty option to enable command at line: " + \
+ str(self.line_ct_stack[0]) + ".")
+ return "fail"
+ if self.verbose:
+ print("Line " + str(self.line_ct_stack[0]) + \
+ ": enable option: " + option)
+ # Remove it from other set if present
+ if option in self.options_disabled:
+ self.options_disabled.remove(option)
+ # Add it if not already present.
+ if option not in self.options_enabled:
+ self.options_enabled.append(option)
+ return "success"
+
+
+ #### disable_option_command ###########################################
+ # Deletes an option from the list of active options.
+ # Parses the first argument after "disable" as the key to enable.
+ # The end of the key is the end of the line or the first whitespace
+ # character.
+
+ def disable_option_command (self, line, script_file):
+ if line == "":
+ print("Empty option to disable at line: " + \
+ str(self.line_ct_stack[0]) + ".")
+ return "fail"
+ m = re.match(_comm_re, line)
+ if m == None:
+ print("Could not parse disable option command at line " + \
+ str(self.line_ct_stack[0]) + ".")
+ return "fail"
+ option = m.group(1)
+ if option == None:
+ print("Empty option to disable command at line " + \
+ str(self.line_ct_stack[0]) + ".")
+ return "fail"
+ if self.verbose:
+ print("line: " + str(self.line_ct_stack[0]) + \
+ ": disable option: " + option)
+ # Remove it from other set if present
+ if option in self.options_enabled:
+ self.options_enabled.remove(option)
+ # Add it if not already present.
+ if option not in self.options_disabled:
+ self.options_disabled.append(option)
+ return "success"
+
+
+ #### configure_command ###############################################
+ # First arg is the item to configure.
+ # Second arg is the setting.
+ # This enables adding option setting inside a script file.
+
+ def configure_command (self, line, script_file):
+ m = re.match(_two_args_re, line)
+ if m == None or m.group(1) == None or m.group(2) == None:
+ print("Could not parse configure command arguments at line " + \
+ str(self.line_ct_stack[0]) + ": {" + line + "}")
+ return "fail"
+ item = m.group(1)
+ setting = m.group(2)
+ if item not in _configurables:
+ print("Ignoring invalid configuration item at line " + \
+ str(self.line_ct_stack[0]) + ": " + item)
+ return "fail"
+ if setting not in _configurables[item]:
+ print("At line " + str(self.line_ct_stack[0]) + \
+ ": Cannot set " + item + " to " + setting + ".")
+ return "fail"
+ if item == "tape":
+ self.simh.set_tape_config(setting)
+ elif item == "rx":
+ self.simh.set_rx_config (setting)
+ elif item == "tti":
+ self.simh.set_tti_config (setting)
+ return "success"
+
+
+ #### cpto_command ###########################################
+ # Calls os8_pip_to with the command line arguments.
+
+ def cpto_command (self, line, script_file):
+ if not self.booted:
+ print("Cannot run cpto command at line " + \
+ str(self.line_ct_stack[0]) + ". OS/8 has not been booted.")
+ return "die"
+
+ # Is 2nd and final arg the option?
+ m = re.match(_from_to_re_1, line)
+ if m != None:
+ # Yes. Expand Source first.
+ path = self.path_expand(m.group(1))
+ if path == None:
+ print("Ignoring: \n\tcpto " + line)
+ return "fail"
+ self.simh.os8_pip_to (path, "DSK:", m.group(2))
+ else:
+ # Is this normal case of source, dest, with possibly empty option?
+ m = re.match(_from_to_re_2, line)
+ if m == None:
+ print("Could not parse cpto command at line " + \
+ str(self.line_ct_stack[0]) + ".")
+ return "fail"
+ path = self.path_expand(m.group(1))
+ if path == None:
+ print("Ignoring: \n\tcpto " + line)
+ return "fail"
+ self.simh.os8_pip_to (path, m.group(2), m.group(4))
+ return "success"
+
+
+ #### cpto_command ###########################################
+ # Calls os8_pip_from with the command line arguments.
+
+ def cpfrom_command (self, line, script_file):
+ if not self.booted:
+ print("Cannot run cpfrom command at line " + \
+ str(self.line_ct_stack[0]) + ". OS/8 has not been booted.")
+ return "die"
+ m = re.match(_from_to_re_2, line)
+ if m == None:
+ print("Could not parse cpfrom command at line " + \
+ str(self.line_ct_stack[0]) + ".")
+ return "fail"
+
+ path = self.path_expand(m.group(2))
+ if path == None:
+ print("Ignoring: \n\tcpfrom " + line)
+ return "fail"
+ self.simh.os8_pip_from (m.group(1), path, m.group(4))
+ return "success"
+
+
+ #### copy_command ###############################################
+ # Simple script interface to create a copy of a file.
+
+ def copy_command (self, line, script_file):
+ m = re.match(_two_args_re, line)
+ if m == None or m.group(1) == None or m.group(2) == None:
+ print("Could not copy command: " + line)
+ return "fail"
+ from_path = self.path_expand(m.group(1))
+ to_path = self.path_expand(m.group(2))
+
+ if from_path == None or to_path == None:
+ print("Ignoring: \n\t copy " + line)
+ return "fail"
+
+ print("copy command: \n\tfrom: " + from_path + ", \n\tto: " + to_path)
+
+ if (not os.path.isfile(from_path)):
+ print("At line " + str(self.line_ct_stack[0]) + \
+ ": Required copy input file: " + from_path + " not found.")
+ return "fail"
+
+ save_if_needed(to_path)
+
+ try:
+ shutil.copyfile(from_path, to_path)
+ except shutil.Error as e:
+ print("copy command failed with error: " + str(e))
+ return "fail"
+ except IOError as e:
+ print("copy command failed with IOError: " + str(e))
+ return "fail"
+ return "success"
+
+
+ #### resume_command #############################################
+ # Call the os8_resume in simh to resume OS/8.
+
+ def resume_command (self, line, script_file):
+ if self.verbose:
+ print("Resuming OS/8 at line " + str(self.line_ct_stack[0]) + ".")
+
+ self.simh.os8_restart()
+ return "success"
+
+
+ #### restart_command #############################################
+ # Call the os8_restart in simh to resume OS/8.
+
+ def restart_command (self, line, script_file):
+ if self.verbose:
+ print("Restarting OS/8 at line " + str(self.line_ct_stack[0]) + ".")
+
+ self.simh.os8_restart()
+ return "success"
+
+
+ #### patch_command ##############################################
+ # Read the named patch file and perform its actions.
+
+ def patch_command (self, line, script_file):
+ if not self.booted:
+ print("Cannot run patch command at line " + \
+ str(self.line_ct_stack[0]) + ". OS/8 has not been booted.")
+ return "die"
+ path = self.path_expand(line)
+ if path == None:
+ print("Ignoring: \n\t" + "patch " + line)
+ return "fail"
+ if not os.path.isfile(path):
+ print("At line " + str(self.line_ct_stack[0]) + \
+ ": Patch file: " + path + " not found.")
+ return "fail"
+
+ self.run_patch_file (path)
+ return "success"
+
+
+ #### exit_command ###############################################
+ # Call POSIX exit to exit the running program,
+ # returning a numerical status value.
+
+ def exit_command (self, line, script_file):
+ m = re.match (_exit_arg_re, line)
+ intfound = False
+ if m != None:
+ if m.group(1) != None:
+ intfound = True
+ status = int(m.group(1))
+ elif m.group(2) != None:
+ intfound = True
+ status = int(m.group(2))
+ else:
+ intfound = False
+ if self.verbose:
+ if intfound: stat_str = str(status)
+ else: stat_str = line
+ print("Calling sys.exit (" + stat_str + ") at line: " + \
+ str(self.line_ct_stack[0]) + ".")
+ if intfound: sys.exit (status)
+ else: sys.exit(line)
+
+
+ #### print_command ###############################################
+ # Print text from the running script
+ # If verbose is set, say what line in the script containing
+ # the print command.
+
+ def print_command (self, line, script_file):
+ if self.verbose:
+ print("Line: " + str(self.line_ct_stack[0]) + ": " + line)
+ else:
+ print(self.print_expand(line))
+ return "success"
+
+
+ #### _command ###########################################
+ #
+
+ def _command (self, line, script_file):
+ return "success"
+
+
+ #### run_script_file ############################################
+ # Run os8 command script file
+ # Call parsers as needed for supported sub commands.
+ #
+ # Commands:
+ # mount [ ...]
+ # option: required | preserve | readonly | ro | scratch
+ # umount
+ # boot
+ # os8
+ # the rest of the line is passed verbatim to OS/8
+ # pal8
+ # include
+ # configure
+ # device: tti | tape | rx
+ # tt parameter: KSR | 7b
+ # tape parameter: td | dt
+ # rx parameter: rx8e | rx28 | rx01 | rx02
+ # enable
+ # disable
+ # cpto [] []
+ # cpfrom []
+ # format: /A | /I | /B
+ # copy
+ # patch
+ # resume
+ # restart
+ # begin
+ # end
+ # print
+ # exit
+
+ # Sub-commands:
+ # build, fotp, absldr
+ #
+ # Commands return, "success", "fail", or "die".
+
+ def run_script_file (self, script_path):
+ # Strings, regexps, and command arrays used by run_system
+ commands = {"mount": self.mount_command,
+ "boot": self.boot_command,
+ "os8": self.os8_command,
+ "pal8": self.pal8_command,
+ "include": self.include_command,
+ "begin": self.begin_command,
+ "end": self.end_command,
+ "exit": self.exit_command,
+ "print": self.print_command,
+ "umount": self.umount_command,
+ "simh": self.simh_command,
+ "configure": self.configure_command,
+ "enable": self.enable_option_command,
+ "disable": self.disable_option_command,
+ "cpto": self.cpto_command,
+ "cpfrom": self.cpfrom_command,
+ "copy": self.copy_command,
+ "resume": self.resume_command,
+ "restart": self.restart_command,
+ "patch": self.patch_command}
+
+ try:
+ script_file = open(script_path, "r")
+ except IOError:
+ print(script_path + " not found.")
+ return "fail"
+
+ # Every time we start a new script
+ # We append a new line number count of 0
+ # onto our line_ct_stack
+ self.line_ct_stack.insert(0, 0)
+ if self.debug:
+ print("New line_ct_stack: " + str(self.line_ct_stack))
+
+ for line in script_file:
+ line = self.basic_line_parse (line, script_file)
+ if line == None: continue
+
+ m = re.match(_comm_re, line)
+ if m == None:
+ print("Ignoring command line at line " + \
+ str(self.line_ct_stack[0]) + ": " + line)
+ continue
+
+ if m.group(1) not in commands:
+ print("Unrecognized script command at line " + \
+ str(self.line_ct_stack[0]) + ": " + m.group(1))
+ continue
+
+ # print("arg: " + m.group(3))
+ if m.group(3) == None: rest = ""
+ else: rest = m.group(3)
+ retval = commands[m.group(1)](rest, script_file)
+ if retval == "die":
+ print("\nFatal error encountered in " + script_path + \
+ " at line " + str(self.line_ct_stack[0]) + ":")
+ print("\t" + line)
+ sys.exit(-1)
+
+ # Done. Pop the line count off our line_ct_stack
+ self.line_ct_stack.pop(0)
+ if self.debug:
+ print("popped line_ct_stack: " + str(self.line_ct_stack))
+
+ return "success"
+
+
+ #### end_command #####################################################
+
+ def end_command (self, line, script_file):
+ print("Unexpectedly encountered end command at line " + \
+ str(self.line_ct_stack[0]) + ": " + line)
+ return "fail"
+
+
+ #### parse_odt #######################################################
+
+ def parse_odt (self, com, line):
+ if self.debug: print(line)
+
+ if line == "\\c": return "break"
+ match = _odt_parse.match(line)
+ if match == None:
+ print("Aborting because of bad ODT line: " + line)
+ self.simh.os8_send_ctrl('C')
+ return "err"
+ loc = match.group(1)
+ old_val = match.group(2)
+ new_val = match.group(3)
+ expect_val_str = "\s*[0-7]{4} "
+
+ if self.debug:
+ print("Loc: " + loc + ", old_val: " + old_val + ", new_val: " + \
+ new_val)
+ self.simh.os8_send_str (loc + "/")
+ self.simh._child.expect(expect_val_str)
+
+ if old_val.isdigit(): # We need to check old value
+ found_val = self.simh._child.after.decode().strip()
+ if found_val != old_val:
+ print("\tOld value: " + found_val + " does not match " +
+ old_val + ". Aborting patch.")
+ # Abort out of ODT back to the OS/8 Monitor
+ self.simh.os8_send_ctrl('C')
+ return "err"
+
+ self.simh.os8_send_line (new_val)
+ return "cont"
+
+
+ #### futil_exit ########################################################
+
+ def futil_exit (self, com, line):
+ self.simh.os8_send_line(line)
+ return "break"
+
+
+ #### futil_file ########################################################
+
+ def futil_file (self, com, line):
+ # Redundant re-parse of line but nobody else wants args right now.
+ match = _com_split_parse.match(line)
+ if match == None:
+ print("Aborting because of mal-formed FUTIL FILE command: " + line)
+ self.simh.os8_send_ctrl('C')
+ return "err"
+ fname = match.group(2)
+ expect_futil_file_str = "\n" + fname + "\s+(.*)$"
+ self.simh.os8_send_line (line)
+ self.simh._child.expect(expect_futil_file_str)
+ result = self.simh._child.after.decode().strip()
+ match = _com_split_parse.match(result)
+ if match == None:
+ print("Aborting because unexpected return status: " + result + \
+ " from: " + line)
+ self.simh.os8_send_ctrl('C')
+ return "err"
+ if match.group(2).strip() == "LOOKUP FAILED":
+ print("Aborting because of FUTIL lookup failure on: " + fname)
+ self.simh.os8_send_ctrl('C')
+ return "err"
+ else:
+ return "cont"
+
+
+ #### parse_futil #####################################################
+ #
+ # Very simple minded:
+ # If first char on line is an alpha, run the command.
+ # If the first char on line is number, do the substitute command.
+ #
+ # Substitute command acts like ODT.
+ # Future version should support the IF construct.
+ #
+ # When we encounter the EXIT command, we return success.
+
+ def parse_futil (self, com, line):
+ futil_specials = {
+ "EXIT": self.futil_exit,
+ "FILE": self.futil_file
+ }
+
+ if line[0].isdigit():
+ # Treat the line as ODT
+ return self.parse_odt(com, line)
+ else:
+ match = _com_split_parse.match(line)
+ if match == None:
+ print("Ignoring failed FUTIL command parse of: " + line)
+ return "cont"
+ fcom = match.group(1)
+ rest = match.group(2)
+
+ if fcom not in futil_specials:
+ # Blind faith and no error checking.
+ self.simh.os8_send_line(line)
+ return "cont"
+ else:
+ return futil_specials[fcom](fcom, line)
+
+
+ #### run_patch_file ##################################################
+
+ def run_patch_file (self, pathname):
+ sys.stdout.write ("Applying patch " + os.path.basename (pathname) + "...")
+ sys.stdout.flush ()
+
+ try:
+ patch_file = open(pathname, "r")
+ except IOError:
+ print(pathname + " not found. Skipping.")
+ return "fail"
+
+ special_commands = {
+ "ODT": self.parse_odt,
+ "R": None, # Get next parser.
+ "FUTIL": self.parse_futil
+ }
+
+ inside_a_command = False
+ the_command = ""
+ the_command_parser = None
+
+ # Resume OS/8 if necessary.
+ if self.simh._context == "simh":
+ self.resume_command(line, script_file)
+
+ for line in patch_file:
+ line = line.rstrip()
+ if line == "": continue
+ elif line[0] == '#': continue # Ignore Comments
+ elif inside_a_command:
+ retval = the_command_parser (the_command, line)
+ if retval == "break":
+ inside_a_command = False
+ self.simh.os8_send_ctrl('C')
+ elif retval == "err":
+ patch_file.close()
+ return "fail"
+ elif line[0] == '.': # New OS/8 Command
+ match = _com_os8_parse.match(line)
+ if match == None:
+ print("Aborting patch on failed OS/8 command parse of: " + line)
+ return "fail"
+ com = match.group(1)
+ rest = match.group(2)
+
+ if com in special_commands:
+ if com == "R":
+ # Run command is special. Take arg as the command and run it.
+ com = rest
+ inside_a_command = True
+ the_command = com
+ the_command_parser = special_commands[com]
+
+ # We carefully separate com and args
+ # But don't make much use of that yet.
+ if self.verbose and self.debug: print(line)
+ self.simh.os8_send_cmd ("\\.", line[1:]) # Skip Prompt.
+
+ patch_file.close()
+
+ print("Success.")
+ return "success"
+
+
+ #### skip_patch ######################################################
+ # Returns true if the given filename matches one of the regex string
+ # keys of the given skips dict and the flag value for that key is set.
+ # See skips definition in make_patch, which calls this.
+
+ def skip_patch (fn, skips):
+ for p in skips:
+ if re.search (p, fn) and skips[p]: return True
+ return False
+
+
+ #### call_pal8 #######################################################
+ # Generic call out to PAL8 with error recovery.
+ # We rely on the caller to have good specifications for source,
+ # binary and optional listing files.
+
+ def call_pal8 (self, source, binary):
+ pal8_replies = ["ERRORS DETECTED: ", "BE\s+\S+", "CF\s+\S+", "DE\s+\S+", "DF", "IC\s+\S+", "ID\s+\S+",
+ "IE\s+\S+", "II\s+\S+", "IP\s+\S+", "IZ\s+\S+", "LD\s+\S+", "LG\s+\S+", "PE\s+\S+",
+ "PH\s+\S+", "RD\s+\S+", "SE\s+\S+", "UO\s+\S+", "US\s+\S+", "ZE\s+\S+", "\S+ NOT FOUND"]
+
+ if self.verbose: print("Assembling " + source)
+ com_line = binary + "<" + source
+ self.simh.os8_send_cmd ("\\.", "R PAL8")
+ # Did the command successfully run and enter the command decoder?
+ reply = self.simh._child.expect (self.simh._cd_replies)
+ if reply != 0:
+ print("PAL8 failed to start at line " + str(self.line_ct_stack[0]))
+ return "fail"
+
+ self.simh.os8_send_line (com_line)
+ err_count = 0
+ reply = self.simh._child.expect (pal8_replies)
+ executed_line = self.simh._child.before.decode().strip()
+ reply_str = self.simh._child.after.decode().strip()
+ if reply == 0:
+ self.simh._child.expect("\d+")
+ err_count = int(self.simh._child.after.decode().strip())
+ reply_str += " " + self.simh._child.after.decode().strip()
+ if reply > 0 or err_count > 0:
+ print("PAL8 Error: ")
+ print("\t*" + executed_line)
+ print("\t" + reply_str)
+ self.simh.os8_send_ctrl ('c') # exit PAL8 Just in case.
+ # We could do something better than just dying, I expect.
+ return "fail"
+ # self.simh.os8_send_ctrl ('[') # exit PAL8
+ return "success"
+
+
+ #### simh_command ####################################################
+ # I tried to avoid including this command but sometimes you just
+ # have to reconfigure subtle bits of device drivers.
+ # We assume we can call a simh command at any time, but
+ # doing so puts us in the simh context that persists until we
+ # issue a boot or go command.
+
+ def simh_command (self, line, script_file):
+ print("simh command is disabled. Line " + \
+ str(self.line_ct_stack[0]) + " ignored.")
+ return "fail"
+ if self.verbose: print(line)
+ self.simh.send_cmd(line)
+ return "success"
+
+
+ #### umount_command ##################################################
+ def umount_command (self, line, script_file):
+ detach_comm = "det " + line
+ if self.verbose: print("line: " + \
+ str(self.line_ct_stack[0]) + ": " + detach_comm)
+ self.simh.send_cmd(detach_comm)
+ return "success"
+
+
+ #### make_scratch ####################################################
+ # Create a copy of the contents of image file using the python
+ # method to create a secure, named temp filename.
+ # The caller has split the image file name into a base path,
+ # and an extension which we use.
+ # After copying the source image, the file is closed and the name
+ # of the scratch file is returned.
+ def make_scratch (self, base_imagename, extension):
+ try:
+ src_image = open(base_imagename+extension, "rb")
+ except IOError:
+ print("Open of imagefile " + base_imagename + extension + " failed.")
+ return None
+
+ dest_image = tempfile.NamedTemporaryFile(mode='w+b', \
+ prefix=base_imagename+'-temp-', \
+ suffix=extension, \
+ delete=False)
+ destname = dest_image.name
+ if self.debug: print("Temp file name is: " + destname)
+
+ try:
+ shutil.copyfileobj(src_image, dest_image)
+ except shutil.Error as e:
+ print("Copy of imagefile " + base_imagename + extension + \
+ " to scratch failed with error: " + str(e))
+ return None
+ except IOError as e:
+ print("Copy of imagefile " + base_imagename + extension + \
+ ", failed with IOError: " + str(e))
+ return None
+ src_image.close()
+ dest_image.close()
+ self.scratch_list.append(destname)
+ return destname
+
+
+ #### mount_command ###################################################
+ # mount [option ...]
+ #
+ # Interface to SIMH attach command with options that do a bit more.
+ #
+ # Options:
+ # required: is required to exist, otherwise abort the script.
+ # preserve if already exists, create a copy with a
+ # version number suffix. This is useful when you want to prevent
+ # overwrites of a good image file with changes that might not work.
+ # os8-run preserves all versions seen and creates a new
+ # version that doesn't overwrite any of the previous ones.
+ # readonly: Passes the `-r` option to SIMH attach to mount the
+ # device in read only mode.
+ # ro: abbreviation for readonly.
+ # scratch: Create a writeable scratch copy of the named image
+ # file and mount it. This is helpful when you are booting a
+ # distribution DECtape. Booted DECtape images must be writeable.
+ # To protect a distribution DECtape, use this option.
+ # When all script runs are done, the scratch version is deleted.
+ # new: If there is an existing file, move it aside as a .save because
+ # we want to create a new empty image file.
+ #
+ # If the mount command fails for any reason, we presume
+ # it is a fatal error and abort the script.
+
+ def mount_command (self, line, script_file):
+ m = re.match(_mount_re, line)
+ if m == None:
+ print("At line " + str(self.line_ct_stack[0]) + \
+ ", could not parse mount. Ignoring: {" + line + "}.")
+ return "die"
+ simh_dev = m.group(1)
+ unit = m.group(2)
+ rest = m.group(3)
+ parts = rest.split()
+ if len(parts) == 0:
+ print("At line " + str(self.line_ct_stack[0]) + \
+ ": No image name specified in: {" + line + "}")
+ return "die"
+ ro_arg = ""
+ imagename = self.path_expand(parts[0])
+ if imagename == None: return "die"
+
+ dot = imagename.rindex(".")
+ base_imagename = imagename[:dot]
+ extension = imagename[dot:]
+ copy_imagename = ""
+ # Case of additional arguments.
+ if len (parts) > 1:
+ # Perform must_exist before scratch
+ if "new" in parts[1:]:
+ save_if_needed(imagename)
+ if "must_exist" in parts[1:] or "required" in parts[1:]:
+ if not os.path.exists(imagename):
+ print("At line " + str(self.line_ct_stack[0]) + ", " + \
+ imagename + " must exist but was not found. Not mounting.")
+ return "die"
+ if "scratch" in parts[1:]:
+ copy_imagename = self.make_scratch(base_imagename, extension)
+ if copy_imagename == None: return "die"
+ imagename = copy_imagename
+
+ if "readonly" in parts[1:] or "ro" in parts[1:]:
+ if copy_imagename != "":
+ print("At line " + str(self.line_ct_stack[0]) + \
+ ": You don't really need to set readonly on a scratch copy.")
+ ro_arg = "-r "
+ if "no_overwrite" in parts[1:] or "preserve" in parts[1:]:
+ if copy_imagename != "":
+ print("Ignoring preserve option at line " + \
+ str(self.line_ct_stack[0]) + \
+ " because scratch option is present.")
+ else:
+ next_tape = 0
+ while os.path.isfile(imagename):
+ print("Found: " + imagename)
+ next_tape += 1
+ imagename = base_imagename + "-" + str(next_tape) + extension
+ if unit == None or unit == "":
+ print("Need unit number for: " + line)
+ return "die"
+
+ if simh_dev not in _os8_from_simh_dev:
+ print("At line " + str(self.line_ct_stack[0]) + \
+ ": Unrecognized simh dev: " + simh_dev)
+ return "die"
+ os8dev = _os8_from_simh_dev[simh_dev]
+
+ attach_comm = "att " + ro_arg + simh_dev + unit + " " + imagename
+
+ if self.verbose:
+ print("Line " + str(self.line_ct_stack[0]) + ": mount: " + \
+ attach_comm)
+ self.simh.send_cmd(attach_comm)
+ return "success"
+
+
+ #### boot_command ####################################################
+ #
+ # Check to see if the device to be booted has something attached.
+ # If not die.
+ # If so, boot it, and set our booted state to True.
+
+ def boot_command (self, line, script_file):
+ # First confirm something is attached to boot from.
+ ucname = line.upper()
+ boot_replies = [ucname + "\s+(.+)\r", "Non-existent device"]
+ self.simh.send_cmd("show " + line)
+ retval = self.simh._child.expect(boot_replies)
+ if retval == 1:
+ print("Attempt to boot non-existent device: " + line)
+ return "die"
+ reply = self.simh._child.after.decode()
+ m = re.match("^(\S+)\s(\S+),\s+(attached to |not attached)(\S+)?,\s+(.+)\r",
+ reply)
+ if m == None:
+ print("Could not determine if device " + line + " is attached; " +
+ "got '" + reply + "'")
+ return "die"
+
+ # Caution match group we want ends with a space.
+ if m.group(3) != "attached to ":
+ print("Attempt to boot on non-attached device: " + line)
+ return "die"
+
+ boot_comm = "boot " + line
+ if self.verbose:
+ print("Line " + str(self.line_ct_stack[0]) + ": " + boot_comm)
+ self.simh.send_cmd(boot_comm)
+ self.booted = True
+ return "success"
+
+
+ #### os8_command #####################################################
+
+ def os8_command (self, line, script_file):
+ if not self.booted:
+ print("Cannot run os8 command at line " + \
+ str(self.line_ct_stack[0]) + ". OS/8 has not been booted.")
+ return "die"
+
+ os8_comm = line
+ if self.verbose: print("Line: " + \
+ str(self.line_ct_stack[0]) + ": os8_command: " + os8_comm)
+
+ # Resume OS/8 if necessary.
+ if self.simh._context == "simh":
+ self.resume_command(line, script_file)
+
+ self.simh.os8_send_cmd ("\\.", os8_comm, self.debug)
+ return "success"
+
+
+ #### pal8_command ####################################################
+ # The "pal8" script command comes in two forms:
+ # The two argument form where the PAL8 status is printed on the fly
+ # and the 3 argument form where all status goes into the listing file.
+ # We do the 3 argument form with a simple "os8" script command.
+
+ def pal8_command (self, line, script_file):
+ if not self.booted:
+ print("Cannot run pal8 command at line " + \
+ str(self.line_ct_stack[0]) + ". OS/8 has not been booted.")
+ return "die"
+
+ m_2form = re.match (_two_arg_pal_re, line)
+ if m_2form != None:
+
+ # Resume OS/8 if necessary.
+ if self.simh._context == "simh":
+ self.resume_command(line, script_file)
+
+ # Call the 2arg pal8 code that works hard at error analysis.
+ return self.call_pal8 (m_2form.group(4), m_2form.group(1))
+ else:
+ m_3form = re.match (_three_arg_pal_re, line)
+ if m_3form != None:
+ # Just run the OS/8 command.
+ os8_comm = "pal8 " + line
+ if self.verbose:
+ print("Line: " + str(self.line_ct_stack[0]) + \
+ ": Calling 3-arg pal8 command: " + os8_comm)
+ self.simh.os8_send_cmd ("\\.", os8_comm)
+ else:
+ print("At line " + str(self.line_ct_stack[0]) + \
+ ": Unrecognized pal8 form: {" + line + "}.")
+ return "fail"
+ return "success"
+
+
+ #### begin_command ###################################################
+
+ def begin_command (self, line, script_file):
+ if not self.booted:
+ print("Cannot execute begin subcommand block at line " + \
+ str(self.line_ct_stack[0]) + ". OS/8 has not been booted.")
+ return "die"
+
+ sub_commands = {"fotp": self.fotp_subcomm, "build": self.build_subcomm,
+ "absldr": self.absldr_subcomm,
+ "cdprog": self.cdprog_subcomm}
+
+ m = re.match(_comm_re, line)
+ if m == None:
+ print("Could not parse sub-command at line " + \
+ str(self.line_ct_stack[0]) + ": " + line)
+ if m.group(1) not in sub_commands:
+ print("Ignoring unrecognized sub-command at line " + \
+ str(self.line_ct_stack[0]) + ": " + m.group(1))
+ print("Ignoring everything to next end.")
+ self.ignore_to_subcomm_end(line, script_file, "")
+ return "fail"
+ else:
+ # Resume OS/8 if necessary.
+ if self.simh._context == "simh":
+ self.resume_command(line, script_file)
+
+ return sub_commands[m.group(1)](m.group(3), script_file)
+
+
+ #### run_build_build #################################################
+ # ***CAUTION***
+ # When you do this you are instructing BUILD to
+ # OVERWRITE the system area. If you do this to your
+ # running RK05 pack by mistake, you WILL make a mess
+ # and need to re-run mkos8 to re-make it.
+
+ def run_build_build (self, os8_spec, cd_spec):
+ self.simh.os8_send_cmd ("\\$", "BUILD", debug=True)
+ self.simh.os8_send_cmd ("LOAD OS/8: ", os8_spec, debug=True)
+ self.simh.os8_send_cmd ("LOAD CD: ", cd_spec, debug=True)
+ return "success"
+
+
+ #### build_subcomm ###################################################
+
+ def build_subcomm (self, old_line, script_file):
+ # A race condition results if we send ^C when we are already at
+ # Monitor level. So need_exit gets set to False, when we know
+ # we have already exited build, and are at the monitor prompt.
+ need_exit = True
+ os8_comm = "RU " + old_line
+ if self.verbose:
+ print("Line " + str(self.line_ct_stack[0]) + ": " + os8_comm)
+ prompt_str = "\n\\$$"
+ if self.debug:
+ print("sending to simh: " + os8_comm)
+ print(" and expecting prompt: '\\n\\\\$$'")
+ self.simh.os8_send_cmd ("\\.", os8_comm)
+ self.simh._child.expect(prompt_str)
+
+ for line in script_file:
+ # if self.debug:
+ # print("line: " + line)
+ line = self.basic_line_parse(line, script_file)
+ if line == None: continue
+
+ m = re.match(_comm_re, line)
+ if m == None:
+ print("Ignoring mal-formed build sub-command at line " + \
+ str(self.line_ct_stack[0]) + ": " + line)
+ continue
+
+ build_sub = m.group(1)
+ rest = m.group(3)
+ if rest == None: rest = ""
+
+ if build_sub not in _build_comm_regs:
+ print("Unrecognized BUILD command at line " + \
+ str(self.line_ct_stack[0]) + ": " + build_sub)
+ continue
+
+ if build_sub == "end":
+ if rest == "":
+ print("Warning! end statement encountered inside build with no argument at line " + \
+ str(self.line_ct_stack[0]) + ".\nExiting build.")
+ return "fail"
+ elif rest != "build":
+ print("Warning! Mismatched begin/end blocks in BUILD at line " + \
+ str(self.line_ct_stack[0]) + ".\nEncountered end: {" + \
+ rest + "}. Exiting BUILD.")
+ return "fail"
+
+ if self.verbose:
+ print("Line " + str(self.line_ct_stack[0]) + ": end BUILD")
+ if self.debug:
+ print("before: " + self.simh._child.before.decode().strip())
+ print("after: " + self.simh._child.after.decode().strip())
+ # Return to monitor level unless need_exit == False.
+ if need_exit:
+ self.simh.os8_send_ctrl ('c')
+
+ return "success"
+
+ build_re = _build_comm_regs[build_sub]
+
+ if build_re != None:
+ m2 = re.match(build_re, rest)
+ if m2 == None:
+ print("Ignoring mal-formed BUILD at line " + \
+ str(self.line_ct_stack[0]) + ": " + build_sub + \
+ " command: " + rest)
+ continue
+
+ if build_sub == "BUILD":
+ if m2.group(1) == None or m2.group(1) == "":
+ print("Missing source of OS8.BN. Ignoring BUILD command at line " + \
+ str(self.line_ct_stack[0]) + ".")
+ continue
+ else: kbm_arg = m2.group(1)
+
+ if m2.group(3) == None or m2.group(3) == "":
+ print("Missing sorce of CD.BN. Ignoring BUILD command at line " + \
+ str(self.line_ct_stack[0]) + ".")
+ continue
+ else: cd_arg = m2.group(3)
+
+ if self.verbose:
+ print("Line " + str(self.line_ct_stack[0]) + \
+ ": BUILD KBM: " + kbm_arg + ", CD: " + cd_arg)
+ if self.debug:
+ print("sending to simh: BUILD")
+ self.simh.os8_send_line ("BUILD")
+
+ build_build_replies = ["LOAD OS/8: "]
+ build_build_replies.extend(_build_replies)
+
+ if self.debug:
+ print("expecting: " + str(build_build_replies))
+ reply = self.simh._child.expect(build_build_replies)
+ if self.debug:
+ print("reply: " + str(reply))
+ print("before: " + self.simh._child.before.decode().strip())
+ print("after: " + self.simh._child.after.decode().strip())
+ if reply != 0:
+ print("No prompt for LOAD OS/8 in BUILD command within BUILD at line " + \
+ str(self.line_ct_stack[0]) + ".")
+ print("Instead got: {" + self.simh._child.after.decode() + "}.")
+ print("Exiting BUILD.")
+ return "die"
+ if self.debug:
+ print("sending to simh: " + kbm_arg)
+ self.simh.os8_send_line (kbm_arg)
+
+ build_build_replies = ["LOAD CD: "]
+ build_build_replies.extend(_build_replies)
+
+ if self.debug:
+ print("expecting: " + str(build_build_replies))
+ reply = self.simh._child.expect(build_build_replies)
+ if self.debug:
+ print("reply: " + str(reply))
+ print("before: " + self.simh._child.before.decode().strip())
+ print("after: " + self.simh._child.after.decode().strip())
+ if reply != 0:
+ print("No prompt for LOAD CD in BUILD command within BUILD at line " + \
+ str(self.line_ct_stack[0]) + ".")
+ print("Instead got: {" + self.simh._child.after.decode() + "}.")
+ print("Exiting BUILD.")
+ return "die"
+ if self.debug:
+ print("sending to simh: " + cd_arg)
+ self.simh.os8_send_line (cd_arg)
+
+ # Done with BUILD command dialog within BUILD.SV
+ # Get that BUILD.SV prompt.
+ if self.debug:
+ print("Expecting prompt: '\\n\\\\$$'")
+ self.simh._child.expect(prompt_str)
+ if self.debug:
+ print("Resume BUILD.SV command loop.")
+ continue
+
+ comm = build_sub + " " + rest
+ if self.verbose:
+ print("Line " + str(self.line_ct_stack[0]) + ": BUILD-> " + comm)
+
+ if self.debug:
+ print("sending to simh: " + comm)
+ self.simh.os8_send_line (comm)
+ if self.debug:
+ print("expecting: " + str(_build_replies))
+ reply = self.simh._child.expect(_build_replies)
+ if self.debug:
+ print("reply: " + str(reply))
+ print("before: " + self.simh._child.before.decode().strip())
+ print("after: " + self.simh._child.after.decode().strip())
+ if reply > 3:
+ print("BUILD error at line " + str(self.line_ct_stack[0]) + \
+ " with command " + self.simh._child.before.decode().strip())
+ print("\t" + self.simh._child.after.decode().strip())
+ self.simh.os8_send_ctrl ('c')
+ # Special case "BOOT" sub-command: May ask, "WRITE ZERO DIRECT?"
+ if build_sub == "BOOT":
+ if reply == 2:
+ if self.debug:
+ print("Boot received \"WRITE ZERO DIRECT?\"")
+ print("sending to simh: Y")
+ self.simh.os8_send_line("Y")
+ if self.debug:
+ print("Expecting \"SYS BUILT\"")
+ reply = self.simh._child.expect("SYS BUILT")
+ if self.debug:
+ print("ZeroDir: reply: " + str(reply))
+ print("before: " + self.simh._child.before.decode().strip())
+ print("after: " + self.simh._child.after.decode().strip())
+ need_exit = False
+ elif reply == 0:
+ reply = self.simh._child.expect("SYS BUILT")
+ if self.debug:
+ print("$: reply: " + str(reply))
+ print("before: " + self.simh._child.before.decode().strip())
+ print("after: " + self.simh._child.after.decode().strip())
+ need_exit = False
+ elif reply == 1:
+ reply = self.simh._child.expect("\\.")
+ if self.debug:
+ print("SysBuilt: reply: " + str(reply))
+ print("before: " + self.simh._child.before.decode().strip())
+ print("after: " + self.simh._child.after.decode().strip())
+ print("Warning end of file encountered with no end of BUILD command block at line " + \
+ str(self.line_ct_stack[0]) + ".")
+ return "fail"
+
+ #### cdprog_subcomm ##################################################
+ # Cycle through OS/8 command decoder with the command specified
+ # in the argument.
+
+ def cdprog_subcomm (self, old_line, script_file):
+ os8_comm = "RU " + old_line
+ end_str = "cdprog " + old_line
+ if self.verbose:
+ print("Line: " + str(self.line_ct_stack[0]) + ": " + os8_comm)
+ self.simh.os8_send_cmd ("\\.", os8_comm)
+
+ for line in script_file:
+ line = self.basic_line_parse(line, script_file)
+ if line == None: continue
+
+ # Test for special case, "end" and act on it if present.
+ m = re.match(_comm_re, line)
+ if m != None and m.group(1) != None and m.group(1) != "" and m.group(1) == "end":
+ rest = m.group(3)
+ retval = "fail" # Return fail unless proven successful.
+ if rest == None or rest == "":
+ print("Warning! end statement encountered inside cdprog " + \
+ "with no argument at line " + \
+ str(self.line_ct_stack[0]) + ".")
+ print("Expecting: {" + end_str + "}.")
+ elif rest != end_str:
+ print("Warning! Mismatched begin/end blocks in cdprog at line " + \
+ str(self.line_ct_stack[0]) + ".\n")
+ print("Expecting: {" + end_str + "}. Got: {" + rest + "}.\n")
+ else:
+ retval = "success"
+ if self.verbose:
+ print("Line " + str(self.line_ct_stack[0]) + ": end " + end_str)
+ if retval == "fail":
+ print("Exiting cdprog, possibly earlier than expected at line " + \
+ str(self.line_ct_stack[0]) + ".")
+ self.simh.os8_send_ctrl ('[')
+ return retval
+
+ # We could do some basic OS/8 command decoder synax checking here.
+ comm = line
+ if self.verbose:
+ print("Line: " + str(self.line_ct_stack[0]) + ": * " + line)
+ self.simh.os8_send_cmd ("\\*", line)
+ print("Warning end of file encountered at line " + \
+ str(self.line_ct_stack[0]) + \
+ " with no end of cdprog command block.")
+ self.simh.os8_send_ctrl ('[')
+ return "fail"
+
+
+ #### fotp_subcomm ####################################################
+
+ def fotp_subcomm (self, old_line, script_file):
+ os8_comm = "RU " + old_line
+ if self.verbose:
+ print("Line: " + str(self.line_ct_stack[0]) + ": " + os8_comm)
+ self.simh.os8_send_cmd ("\\.", os8_comm)
+
+ for line in script_file:
+ line = self.basic_line_parse(line, script_file)
+ if line == None: continue
+
+ # Test for special case, "end" and act on it if present.
+ m = re.match(_comm_re, line)
+ if m != None and m.group(1) != None and m.group(1) != "" and m.group(1) == "end":
+ rest = m.group(3)
+ if rest == None or rest == "":
+ print("Warning! end statement encountered inside fotp " + \
+ "with no argument at line " + \
+ str(self.line_ct_stack[0]) + ".")
+ return "fail"
+ elif rest != "fotp":
+ print("Warning! Mismatched begin/end blocks in FOTP at line " + \
+ str(self.line_ct_stack[0]) + ".\nEncountered end: {" + \
+ rest + "}. Exiting FOTP.")
+ return "fail"
+
+ if self.verbose:
+ print("Line " + str(self.line_ct_stack[0]) + ": end FOTP")
+ self.simh.os8_send_ctrl ('[')
+ return "success"
+
+ m = re.match(_fotp_re, line)
+ if m == None:
+ print("At line " + str(self.line_ct_stack[0]) + \
+ ": ignoring mal-formed fotp file spec: {" + line + "}.")
+ continue
+
+ comm = line
+ if self.verbose:
+ print("Line: " + str(self.line_ct_stack[0]) + ": * " + line)
+ self.simh.os8_send_cmd ("\\*", line)
+ print("Warning end of file encountered at line " + \
+ str(self.line_ct_stack[0]) +
+ " with no end of FOTP command block.")
+ return "fail"
+
+
+ #### absldr_subcomm ##################################################
+ # A clone of fotp_subcom. Can we find a way to merge the common code?
+
+ def absldr_subcomm (self, old_line, script_file):
+ os8_comm = "RU " + old_line
+ if self.verbose:
+ print(" line: " + str(self.line_ct_stack[0]) + ": " + os8_comm)
+ self.simh.os8_send_cmd ("\\.", os8_comm)
+
+ for line in script_file:
+ line = self.basic_line_parse(line, script_file)
+ if line == None: continue
+
+ # Test for special case, "end" and act on it if present.
+ m = re.match(_comm_re, line)
+ if m != None and m.group(1) != None and m.group(1) != "" and m.group(1) == "end":
+ rest = m.group(3)
+ if rest == None or rest == "":
+ print("Warning! end statement encountered inside ABSLDR " + \
+ "with no argument at line " +
+ str(self.line_ct_stack[0]) + ".")
+ return "fail"
+ elif rest != "absldr":
+ print("Warning! Mismatched begin/end blocks in ABSLDR at " + \
+ "line " + str(self.line_ct_stack[0]) + \
+ ".\nEncountered end: {" + rest + "}. Exiting ABSLDR.")
+ return "fail"
+
+ if self.verbose:
+ print("End ABSLDR at line " + str(self.line_ct_stack[0]) + ".")
+ self.simh.os8_send_ctrl ('[')
+ return "success"
+
+ m = re.match(_absldr_re, line)
+ if m == None:
+ print("Ignoring mal-formed absldr file spec at line " + \
+ str(self.line_ct_stack[0]) + ": {" + line + "}.")
+ continue
+
+ comm = line
+ if self.verbose:
+ print("Line : " + str(self.line_ct_stack[0]) + "* " + line)
+ self.simh.os8_send_cmd ("\\*", line)
+ print("Warning end of file encountered at line " + \
+ str(self.line_ct_stack[0]) + "with no end of ABSLDR command block.")
+ return "fail"
+
+
+ #### check_exists ####################################################
+ # Check existence of all files needed
+
+ def check_exists (s, image_copyins):
+ for copyin in image_copyins:
+ image = copyin[1]
+ image_path = dirs.os8mi + image
+ if (not os.path.isfile(image_path)):
+ print("Required file: " + image_path + " not found.")
+ mkos8_abort(s)
+ # else: print("Found " + image_path)
+
+
+ #### Data Structures ################################################
+ #
+ # The make procedures use helper procedures
+ # to confirm that the relevant input image file exists and
+ # to perform the file copies.
+ #
+ # A data structure called "image copyin"
+ # describes the image file pathname relative to an implied root,
+ # provides a message string when the action is run,
+ # names a default destination device for whole image content copies,
+ # offers an optional array of specific file copy actions.
+ #
+ # FUTURE: Parse source path for ".tu56" vs. ".rk05" for more general use.
+ # Currently all code assumes a copyin comes from a DECtape image.
+ #
+ # Example: We Install all files for ADVENT, the Adventure game:
+ #
+ # advent_copyin = ['RKB0:', 'subsys/advent.tu56', "Installing ADVENT...", None]
+ #
+ # A DECtape device is chosen for attachment in SIMH and
+ # a 'COPY *.*' command is filled in with the Destination device, and the chosen DECtape.
+ #
+ # A data structer called "file copyin"
+ # provides override destination to allow renames or varied destinations.
+ # names individual files within a copyin to use
+ #
+ # Example: To copy the C compiler we want all .SV files on SYS
+ # but everything else to RKB0:
+ # (Note the useful /V option to invert the match.)
+ #
+ # cc8_sv_file_copyin = ['SYS:', '*.SV']
+ # cc8_rest_file_copyin = ['RKB0:', '*.SV/V']
+ #
+ # A 'COPY' command is filled in with the override destination and
+ # The file spec is used with the chosen dectape instead of "*.*"
+ #
+
+ #### copyin_pair #####################################################
+ # Copy two images into two destinations with two messages
+ #
+ # Assumes our context is "in simh".
+ # Assumes dt0 and dt1 are free.
+ # Assumes rk0 is the boot device
+ # Detaches dt0 and dt1 after using them.
+ # copyin0 mounts on dt0. copyin1 mounts on dt1.
+ # Either copyin or both can be None
+
+ def copyin_pair (s, copyin0, copyin1, debug):
+ if debug:
+ if copyin0:
+ print("Copying: " + copyin0[1] + " to: " + copyin0[0] + "from dt0")
+ else:
+ print("copyin0 is empty.")
+
+ if copyin1:
+ print("Copying: " + copyin1[1] + " to: " + copyin1[0] + "from dt1")
+ else:
+ print("copyin1 is empty.")
+
+ if not copyin0 and not copyin1: return # Nothing to do.
+
+ # The order of events here is a bit funky because we want
+ # to use both DECtape drives but also
+ # switch between SIMH and OS/8 as infrequently as possible.
+
+ if copyin0: self.simh.send_cmd ("attach -r dt0 " + dirs.os8mi + copyin0[1])
+ if copyin1: self.simh.send_cmd ("attach -r dt1 " + dirs.os8mi + copyin1[1])
+
+ self.simh.os8_restart()
+
+ if copyin0:
+ if self.verbose: print(copyin0[2])
+ if copyin0[3]: # We have specific files to do.
+ for file_copyin in copyin0[3]:
+ self.simh.os8_send_cmd ("\\.", "COPY " + file_copyin[0] + "
- pkg_resources.parse_version("4.0"))
- self._child.delaybeforesend = None if pev4 else 0
-
- # Wait for the simulator's startup message.
- if not self.try_wait ('PDP-8 simulator V.*git commit id: [0-9a-f]', 10):
- raise RuntimeError ('Simulator failed to start')
-
-
- #### back_to_cmd ######################################################
- # Pause the simulation and return to the SIMH command prompt when the
- # simulated software emits the given prompt string. Typically used to
- # wait for OS/8 to finish running a command so we can do something
- # down at the SIMH layer instead.
-
- def back_to_cmd (self, prompt):
- self._child.expect ("\n%s$" % prompt)
- self.os8_kbd_delay ()
- self._child.sendcontrol ('e')
- self._context = "simh"
-
-
- #### os8_get_file ####################################################
- # Rough inverse of os8_send_file.
- #
- # Both paths must be given and are used literally. (Contrast our
- # inverse, where the destinatinon file name is produced from the
- # source if not given.)
- #
- # When this function is called to pull a file sent by our inverse, the
- # conversion should be lossless except for the transforms done by our
- # underlying utility tools, such as the LF -> CR+LF done by txt2ptp
- # but not undone by ptp2txt.
- #
- # Entry context should be inside OS/8. Exit context is inside OS/8.
-
- def os8_get_file (self, intname, extname):
- # Attach a blank paper tape to the simulator.
- ptf = tempfile.NamedTemporaryFile (suffix = '.pt', delete = False)
- ptf.close ()
- ptn = ptf.name
- self.back_to_cmd ('\\.')
- self.send_cmd ('attach ptp ' + ptn)
-
- # Punch internal file to external paper tape image
- self.os8_restart ()
- self.os8_send_cmd ('\\.', 'PUNCH ' + intname);
- self.back_to_cmd ('\\.') # wait for transfer to finish
-
- # Convert text file from SIMH paper tape format
- tool = os.path.join (pidp8i.dirs.build, 'bin', 'ptp2txt')
- self.send_cmd ('detach ptp')
- subprocess.call (tool + ' < ' + ptn + ' > ' + extname, shell = True)
-
- # Return to OS/8, just because that's where we were on entry, so we
- # should not change that.
- self.os8_restart ()
-
-
- #### os8_kbd_delay ###################################################
- # Artificially delay the media generation process to account for the
- # fact that OS/8 lacks a modern multi-character keyboard input buffer.
- # It is unsafe to send text faster than a contemporary terminal could,
- # though we can scale it based on how much faster this host is than a
- # real PDP-8. See the constants above for the calculation.
-
- def os8_kbd_delay (self):
- time.sleep (self._os8_kbd_delay)
-
-
- #### os8_send_cmd ####################################################
- # Wait for an OS/8 command prompt running within SIMH, then send the
- # given line.
- #
- # The default timeout may seem excessive, but it is based on hard
- # experience: when SIMH is running on a slow host with slow devices
- # (e.g. the byte-by-byte transfer of the TD8E tape controller) a
- # single OS/8 command can take a very long time if it requires a lot
- # of I/O. If you are calling this for a command that you know for a
- # fact takes less time on all hosts and with all practical device
- # configurations, we encourage you to pass a smaller value.
- #
- # The caller must pass a prompt string because OS/8 has several
- # different prompt types: ., *, $, and #, at least. Beware in passing
- # these that they're treated as regular expressions, so characters
- # special in Python REs must be escaped. And then since the RE escape
- # character (\) is also special in Python strings, you must double-
- # escape *it*. So, '\\$' is a reasonable thing to pass as the prompt
- # value, meaning "look for a literal $ character."
- #
- # This routine requires the caller to ensure that the system is in
- # OS/8 Keyboard Monitor context — that is, ready for another OS/8
- # command — before calling it. While this routine is able to check
- # whether we're in OS/8 context as a prerequisite, it is not practical
- # for us to return the system to OS/8 context automatically from some
- # other context because that would require us to know the current
- # context in detail, but only the caller has that full knowledge.
- #
- # Part of the problem is that in order to synchronize this object's
- # internal state machine with the SIMH + OS/8 + running program state,
- # we have to somehow provoke a prompt character from the running
- # program. How do we do that without knowing the current context?
- # In some contexts, a CR or LF will do it, in others BS, and in others
- # it'll take Ctrl-C. Then you have a new problem, with is that those
- # same characters aren't harmless: they'll change the very context
- # we're trying to probe! For instance, a Ctrl-C sent to the OS/8
- # Keyboard Monitor just results in another prompt, but a Ctrl-C sent
- # to a program running *under* OS/8 might kill it. Or it might be
- # treated as input. Or it might be ignored entirely.
- #
- # There is no magic sequence we can send to SIMH or OS/8 to return the
- # system to OS/8 Keyboard Monitor context without either changing the
- # context in some way that might break the caller's needed state (e.g.
- # Ctrl-E, go 7600) or lose data (e.g. Ctrl-C) or fail entirely (e.g.
- # Enter.) It's up to the caller to arrange this.
-
- def os8_send_cmd (self, prompt, line, debug=False, timeout=60):
- if self._context != 'os8':
- print "OS/8 is not running. Cannot execute: " + line
- return
- if debug:
- print "os8_send_cmd: expecting: " + prompt
- print "\tLast match before: {" + self._child.before + "}"
- print "\tLast match after: {" + self._child.after + "}"
- self._child.expect ("\n%s$" % prompt, timeout = timeout)
- self.os8_send_line (line)
-
-
- #### os8_send_ctrl ###################################################
- # Send a control character to OS/8 corresponding to the ASCII letter
- # given. We precede it with the OS/8 keyboard delay, since we're
- # probably following a call to os8_send_line or os8_send_cmd.
-
- def os8_send_ctrl (self, char):
- cc = char[0].lower ()
- self.os8_kbd_delay ()
- self._child.sendcontrol (cc)
-
- if cc == 'e': self._context = 'simh'
-
-
- #### mk_os8_name # ###################################################
- # Create an OS/8 filename: of the form XXXXXX.YY
- # From a POSIX path.
-
- def mk_os8_name(self, dev, path):
- bns = os.path.basename (path)
- bns = re.sub("-|:|\(|\)|!", "", bns)
- bns = bns.upper()
- if "." not in bns:
- return dev + bns[:min(6, len(bns))]
- else:
- dot = bns.index('.')
- return dev + bns[:min(6, dot, len(bns))] + "." + bns[dot+1: dot+3]
-
-
- #### os8_send_file ###################################################
- # Send a copy of a local text file to OS/8. The local path may
- # contain directory components, but the remote must not, of course.
- #
- # If the destination file name is not uppercase, it will be so forced.
- #
- # If the destination file name is not given, it is taken as the
- # basename of the source file name.
- #
- # The file is sent via the SIMH paper tape device through PIP in its
- # default ASCII mode, rather than character by character for two reasons:
- #
- # 1. It's faster. It runs as fast as the simulator can process the
- # I/O instructions, without any os8_kbd_delay() hooey.
- #
- # 2. It allows lowercase input regardless of the way the simulator is
- # configured. ASCII is ASCII.
- #
- # Entry context should be inside OS/8. Exit context is inside OS/8.
-
- def os8_send_file (self, source, dest = None):
- # Create path and file names not given
- bns = os.path.basename (source)
- if dest == None: dest = bns
- dest = dest.upper ()
-
- # Convert text file to SIMH paper tape format
- bdir = pidp8i.dirs.build
- pt = os.path.join (bdir, 'obj', bns + '.pt')
- tool = os.path.join (bdir, 'bin', 'txt2ptp')
- subprocess.call (tool + ' < ' + source + ' > ' + pt, shell = True)
-
- # Paper tape created, so attach it read-only and copy it in. We're
- # relying on txt2ptp to insert the Ctrl-Z EOF marker at the end of
- # the file.
- self.back_to_cmd ('\\.')
- self.send_cmd ('attach -r ptr ' + pt)
- self.os8_restart ()
- self.os8_send_cmd ('\\.', 'R PIP')
- self.os8_send_cmd ('\\*', dest + ' ' + pt, shell = True)
- did_conversion = True
- elif option not in self._valid_pip_options:
- print "Invalid PIP option: " + option + ". Ignoring: " + path + " to OS/8."
- return
- else:
- pt = path
-
- # TODO: Sacrificial extra character code goes here.
-
- # Paper tape created, so attach it read-only and copy it in. We're
- # relying on txt2ptp to insert the Ctrl-Z EOF marker at the end of
- # the file.
- self.back_to_cmd ('\\.')
- self.send_cmd ('attach -r ptr ' + pt)
- self.os8_restart ()
- self.os8_send_cmd ('\\.', 'R PIP')
- # Was the start of PIP successful, or did we get a Monitor error?
- reply = self._child.expect (self._cd_replies)
- if reply != 0:
- self.pip_error_handler ("os8_pip_to", reply)
- return
-
- # Has the read-in been successful?
- self.os8_send_line (dest + ' ' + path, shell = True)
- os.remove(pf)
-
-
- #### os8_send_line ###################################################
- # Core of os8_send_cmd. Also used by code that needs to send text
- # "blind" to OS/8, without expecting a prompt, as when driving EDIT.
-
- def os8_send_line (self, line):
- self.os8_send_str (line)
- self._child.send ("\r")
-
-
- #### os8_send_str ########################################################
- # Core of os8_send_line. Also used by code that needs to send text
- # "blind" to OS/8, without expecting a prompt and without a CR, as
- # when driving TECO.
-
- def os8_send_str (self, str):
- for i in xrange (0, len (str)):
- self._child.send (str[i])
- self.os8_kbd_delay ()
-
-
- #### os8_resume #######################################################
- # Resume OS/8.
- #
- # It would be nice if we could just send the "cont" command
- # and have python expect and OS/8 synch right up.
- # But so far we have not figured out how to do that.
- # To resume OS/8 from SIMH we need to provoke a prompt.
- # Typing a rubout or ^U at a SIMH terminal session does this.
- # But not when SIMH is run under python expect.
- # We don't know why.
- #
- # boot works
- # go 7600 works
- # ^C \n\r works.
- #
- # The resume command uses the ^C method as the least disruptive
- # to system state.
-
- def os8_resume (self):
- if self._context == "os8": return # Already running.
-
- self.send_cmd("cont") # sets os8 context for us.
-
- # Now provoke a keyboard monitor prompt.
- self.os8_send_ctrl('c')
- self.os8_kbd_delay()
- self.os8_send_str('\r\n')
-
-
- #### os8_restart #######################################################
- # Called while in the SIMH command prompt, this restarts OS/8.
- #
- # This one-line function exists to abstract the method we use and to
- # document the reason we do it this way.
- #
- # Currently we do this by calling the OS/8 command entry point, which
- # has the virtue that it forces another . prompt, which keeps the
- # send/expect sequencing simple when switching between OS/8 and SIMH
- # command modes.
- #
- # That is why we don't use "cont" here instead: it requires that the
- # caller always be aware of when the . prompt went out; complicated.
- #
- # Another simple alternative is "boot rk0", which actually benchmarks
- # a smidge faster here. We choose this method instead because we
- # expect that some of our callers will want to do several different
- # things in a single OS/8 session, which rebooting would screw up.
-
- def os8_restart (self):
- self.send_cmd ("go 7600")
-
-
- #### os8_squish ########################################################
- # Wraps the OS/8 SQUISH command for a given device.
-
- def os8_squish (self, device):
- self.os8_send_cmd ('\\.', "SQUISH " + device + ":")
- self.os8_kbd_delay ()
- self._child.send ("Y\r");
-
-
- #### os8_zero_core ###################################################
- # Starting from OS/8 context, bounce out to SIMH context and zero all
- # of core excepting:
- #
- # 0. zero page - many apps put temporary data here
- # 1. the top pages of fields 1 & 2 - OS/8 is resident here
- # 2. the top page of field 2 - OS/8's TD8E driver (if any) lives here
- #
- # We then restart OS/8, which means we absolutely need to do #1 and
- # may need to do #2. We could probably get away with zeroing page 0.
- #
- # All of the above explains why we have this special OS/8 alternative
- # to the zero_core() method.
-
- def os8_zero_core (self):
- self.back_to_cmd ('\\.')
- self.send_cmd ('de 00200-07577 0')
- self.send_cmd ('de 10000-17577 0')
- self.send_cmd ('de 20000-27577 0')
- self.send_cmd ('de 30000-77777 0')
- self.os8_restart ()
-
-
- #### quit ############################################################
- # Quits the simulator and waits for it to exit
-
- def quit (self):
- self.send_cmd ("q")
- self._child.expect (pexpect.EOF)
-
-
- #### read_tail #######################################################
- # Watch for a literal string, then get what follows on that line.
-
- def read_tail (self, head, timeout = -1):
- self._child.expect_exact ([head], timeout)
- return self._child.readline ()
-
-
- #### send_cmd ########################################################
- # Wait for a SIMH command prompt and then send the given command.
- # If we are not in the simh context send ^e and set context "simh".
- # If we are not in simh context, send ^e set context "simh"
- # and hope for the best.
- # If we issue a command that enters os8 context, set context "os8".
- # Note exiting out of OS/8 into the SIMH context is a bit of a
- # trap door. Resynchronizing with python expect requires provoking
- # a prompt, and prompts are context specific.
- # Perhaps we should require separate and explicit commands to
- # escape to SIMH. But for now, just be careful to use os8_resume
- # after calling send_cmd.
-
- def send_cmd (self, cmd):
- if self._context == "os8":
- self._child.expect ("\n\\.$")
- self._child.sendcontrol ('e')
- self._context = "simh"
- elif self._context != "simh":
- self._child.sendcontrol ('e')
- self._context = "simh"
-
- self._child.expect ("sim> $")
- self._child.sendline (cmd)
- m = re.match (self._simh_comm_re, cmd)
- if m != None and m.group(1)[:1].upper() in self._enters_os8_context:
- self._context = "os8"
-
-
- #### send_line #######################################################
- # Sends the given line "blind", without waiting for a prompt.
-
- def send_line (self, line):
- self._child.sendline (line)
-
-
- #### set_logfile #####################################################
-
- def set_logfile (self, lf):
- self._child.logfile = lf
-
-
- #### spin ############################################################
- # Let child run without asking anything more from it, with an optional
- # timeout value. If no value is given, lets child run indefinitely.
-
- def spin (self, timeout = None):
- self._child.expect (pexpect.EOF, timeout = timeout)
-
-
- #### try_wait ########################################################
- # A wrapper around self._child.expect which catches exceptions and
- # returns false on pexpect timeout. If you pass a list instead of a
- # string, it also returns true if the match wasn't for the first
- # element, so you can pass [success, failure1, failure2, etc.] to
- # check for a known-success case and one or more failure cases.
-
- def try_wait (self, matches, timeout = -1):
- try:
- return self._child.expect (matches, timeout = timeout) == 0
- except pexpect.exceptions.TIMEOUT:
- sys.stderr.write ("Exceeded " + str(timeout) + " sec timeout " +
- "waiting for " + str(matches) + "\n")
- return False
- except:
- sys.stderr.write ("Failed to match " + str(matches) +
- ": unknown exception.\n")
- return False
-
-
- #### zero_core #######################################################
- # From SIMH context, zero the entire contents of core, which is
- # assumed to be 32 kWords.
- #
- # SIMH's PDP-8 simulator doesn't start with core zeroed, on purpose,
- # because the actual hardware did not do that. SIMH does not attempt
- # to simulate the persistence of core memory by saving it to disk
- # between runs, but the SIMH developers are right to refuse to do this
- # by default: you cannot trust the prior state of a PDP-8's core
- # memory before initializing it yourself.
- #
- # See os8_zero_core () for a less heavy-handed alternative for use
- # when running under OS/8.
-
- def zero_core (self):
- self.send_cmd ('de all 0')
-
-
- #### describe_dev_config #############################################
- # We provide an interface to alter SIMH device configurations for
- # specific parameters and specific devices
- #
- # dev configs supported: rx, tti, tape
- #
- # rx: RX8E, RX28 RX8E is the simh name for RX01 support.
- # RX28 is the simh name for RX02 support.
- # tti: KSR, 7b 7b is full keyboard support.
- # KSR forces upcase of lower case keys on input.
- # tape: td, dt td is the TD8E DECtape device
- # dt is the TC08 DECtape device
-
- def describe_dev_config (self, name):
- if name == "tape":
- lines = self.do_simh_show("dt")
- dev_status = self.parse_show_tape_dev(lines)
-
- if dev_status == "dt": return "dt"
- else:
- lines = self.do_simh_show("td")
- return self.parse_show_tape_dev(lines)
- elif name == "rx":
- lines = self.do_simh_show("rx")
- return self.parse_show_rx_dev(lines)
- elif name == "tti":
- lines = self.do_simh_show("tti")
- return self.parse_show_tti(lines)
- else: return None
-
-
- #### do_simh_show ###################################################
- # Calls show on the device name.
- # Returns array of lines from output.
-
- def do_simh_show (self, name):
- supported_shows = ["dt", "td", "tti", "rx"]
- if name not in supported_shows: return None
-
- ucname = name.upper()
- self.send_cmd("show " + name)
- self._child.expect(ucname + "\s+(.+)\r")
- lines = self._child.after.split ("\r")
- return lines
-
-
- #### parse_show_tape_dev ############################################
- # Returns current state of DECtape support.
- # One of: disabled, td, dt, or None if parse fails.
-
- def parse_show_tape_dev (self, lines):
- if lines == None: return None
- is_enabled_re = re.compile("^(TD|DT)\s+(disabled|(devno=\S+,\s(\d)\s+units))$")
- m = re.match(is_enabled_re, lines[0])
-
- if m == None or m.group(1) == None or m.group(2) == None: return None
- if m.group(2) == "disabled": return "disabled"
- elif m.group(1) == "TD": return "td"
- elif m.group(1) == "DT": return "dt"
- else: return None
-
-
- #### parse_show_tape_attached ########################################
- # Returns an ordered list of files attached or None if disabled.
- def parse_show_tape_attached (self, lines):
- if lines == None: return None
- if len(lines) < 2: return None
- attached = {}
- attachment_re = re.compile("^\s+(((DT|TD)(\d)(.+),\s+(not\s+attached|attached\s+to\s+(\S+)),(.+))|12b)$")
- for line in lines[1:]:
- m = re.match(attachment_re, line)
- if m == None or m.group(1) == None or m.group(1) == "12b": continue
- filename = m.group(7)
- if filename == None: filename = ""
- attached[m.group(4)] = filename
- return attached
-
- #### do_print_lines ###################################################
- # Debugging aid. Prints what we parsed out of _child.after.
- def do_print_lines (self, lines):
- for line in lines:
- print line
-
- #### simh_configure routines #########################################
- # These routines affect the state of device configuration in SIMH.
- # They are intended as robust ways to toggle between incompatible
- # configurations of SIMH:
- # Choice of TD8E or TC08 DECtape (SIMH td and dt devices).
- # Choice of RX01 or RX02 Floppy emulation.
- # The SIMH rx device sets RX8E for RX01, and RX28 for RX02.
- # Choice of KSR or 7bit console configuration.
- #
- # When re-configuring dt, dt, and rx devices, any attached
- # images are detached before reconfiguration is attempted.
- # (SIMH errors out if you don't detach them.)
- #
- # The check to see if the change is unnecessary.
- # For now they return None if no change necessary.
- #
- # After re-configuring the device, the SIMH show command is used
- # to confirm the re-configuration was successful.
- #
- # In future, we should add exception handling for no change necessary.
- # For now, return True if the change was successful and False if not.
-
-
- def set_tape_config (self, to_tape):
- if to_tape == "dt": from_tape = "td"
- elif to_tape == "td": from_tape = "dt"
- else:
- print "Cannot set_tape_config for " + to_tape
- return False
-
- if self.verbose: print "Disable: " + from_tape + ", and enable: " + to_tape
-
- lines = self.do_simh_show(from_tape)
- from_status = self.parse_show_tape_dev(lines)
-
- if from_status == None:
- print "do_tape_change: Trouble parsing \'show " + from_tape + "\' output from simh. Giving up on:"
- self.do_print_lines (lines)
- return False
-
- if from_status == "disabled": print from_tape + " already is disabled."
- else:
- attached_from = self.parse_show_tape_attached(lines)
- if attached_from == None:
- print "do_tape_change: Trouble parsing \'show " + from_tape + "\' output from simh. Giving up on:"
- self.do_print_lines (lines)
- return False
- else:
- for unit in attached_from.keys():
- if attached_from[unit] != "":
- det_comm = "det " + from_tape + unit
- if self.verbose: print det_comm + "(Had: " + attached_from[unit] + ")"
- self.send_cmd(det_comm)
- self.send_cmd("set " + from_tape + " disabled")
-
- lines = self.do_simh_show(to_tape)
- to_status = self.parse_show_tape_dev(lines)
-
- if to_status == None:
- print "do_tape_change: Trouble parsing \'show " + to_tape + "\' output from simh. Giving up on:"
- self.do_print_lines (lines)
- return False
- elif to_status != "disabled": print to_tape + " already is enabled."
- else:
- self.send_cmd("set " + to_tape + " enabled")
-
- # Test to confirm to_tape is now enabled.
-
- lines = self.do_simh_show(to_tape)
- to_status = self.parse_show_tape_dev(lines)
-
- if to_status == None:
- print "Failed enable of " + to_tape + ". Parse fail on \'show " + to_tape + "\'. Got:"
- self.do_print_lines (lines)
- return False
- elif to_status == "disabled":
- print "Failed enable of " + to_tape + ". Device still disabled."
- return False
- else:
- return True
-
-
- #### parse_show_rx_dev ###############################################
- # Show the rx device configuration.
-
- def parse_show_rx_dev (self, lines):
- if lines == None: return None
- is_enabled_re = re.compile("^\s*(RX)\s+(disabled|((RX8E|RX28),\s+devno=\S+,\s+(\d)\s+units))$")
- m = re.match(is_enabled_re, lines[0])
- if m == None or m.group(2) == None: return None
- if m.group(2) == "disabled": return "disabled"
- return m.group(4)
-
-
- #### parse_show_rx_attached ##########################################
- # Returns an ordered list of files attached or None if disabled.
-
- def parse_show_rx_attached (self, lines):
- if len(lines) < 2: return None
- attached = {}
- attachment_re = re.compile("^\s+(((RX)(\d)(.+),\s+(not\s+attached|attached\s+to\s+(\S+)),(.+))|autosize)$")
- for line in lines[1:]:
- m = re.match(attachment_re, line)
- if m == None or m.group(1) == None or m.group(1) == "autosize": continue
- filename = m.group(7)
- if filename == None: filename = ""
- attached[m.group(4)] = filename
- return attached
-
-
- #### set_rx_config ####################################################
-
- def set_rx_config (self, to_rx):
- to_rx = to_rx.lower()
- if to_rx == "rx8e": from_rx = "rx28"
- elif to_rx == "rx01":
- to_rx = "rx8e"
- from_rx = "rx28"
- elif to_rx == "rx28": from_rx = "rx8e"
- elif to_rx == "rx02":
- to_rx = "rx28"
- from_rx = "rx8e"
- else:
- print "Cannot set_rx_config for " + to_rx
- return False
-
- if self.verbose: print "Switch rx driver: " + from_rx + ", to: " + to_rx
- lines = self.do_simh_show("rx")
-
- rx_type = self.parse_show_rx_dev (lines)
- if rx_type == None:
- print "do_rx_change: Trouble parsing \'show rx\' output from simh. Giving up on:"
- self.do_print_lines(lines)
- return False
- elif rx_type == "disabled":
- if self.verbose: print "rx is disabled. Enabling..."
- self.send_cmd("set rx enabled")
- # Retry getting rx info
- lines = self.do_simh_show("rx")
- rx_type = self.parse_show_rx_dev (lines)
- if rx_type == None:
- print "do_rx_change after re-enable: Trouble parsing \`show rx\` output from simh. Giving up on:"
- self.do_print_lines(lines)
- return False
- elif rx_type == "disabled":
- print "do_rx_change re-enable of rx failed. Giving up."
- return False
-
- if rx_type.lower() == to_rx:
- print "rx device is already set to " + to_rx
- return None
-
- attached_rx= self.parse_show_rx_attached(lines)
- if attached_rx == None:
- print "do_rx_change: Trouble parsing /'show rx\' from simh to find rx attachments. Got:"
- self.do_print_lines(lines)
- else:
- for unit in attached_rx.keys():
- if attached_rx[unit] != "":
- det_comm = "det rx" + unit
- if self.verbose: print det_comm + "(Had: " + attached_rx[unit] + ")"
- self.send_cmd(det_comm)
-
- self.send_cmd("set rx " + to_rx)
-
- # Test to confirm new setting of RX
- lines = self.do_simh_show("rx")
- rx_type = self.parse_show_rx_dev (lines)
-
- if rx_type == None:
- print "Failed change of rx to " + to_rx + ". Parse fail on \'show rx\'."
- return False
- elif rx_type.lower() != to_rx:
- print "Failed change of rx to " + to_rx + ". Instead got: " + rx_type
- return False
- return True
-
-
- #### get_tti ##################################################
- # Returns an ordered list of files attached or None if disabled.
- def parse_show_tti (self, lines):
- if lines == None: return None
- is_enabled_re = re.compile("^(KSR|7b)$")
- if len(lines) < 2: return None
-
- # That second line of output contains embedded newlines.
- m = re.match(is_enabled_re, lines[1].strip())
- if m == None or m.group(1) == None: return None
- return m.group(1)
-
-
-
- #### do_tti_change ###################################################
-
- def set_tti_config (self, to_tti):
- if to_tti == "KSR": from_tti = "7b"
- elif to_tti == "7b": from_tti = "KSR"
- else:
- print "Cannot set_tti_config to " + to_tti
- return
-
- if self.verbose: print "Switch tti driver from: " + from_tti + ", to: " + to_tti
-
- lines = self.do_simh_show("tti")
- tti_type = self.parse_show_tti (lines)
- if tti_type == None:
- print "do_tti_change: Trouble parsing \'show tti\' output from simh. Giving up on:"
- self.do_print_lines(lines)
- return False
- elif tti_type == to_tti:
- print "tti device is already set to " + to_tti
- return None
-
- self.send_cmd("set tti " + to_tti)
-
- # Test to confirm new setting of tti
- lines = self.do_simh_show("tti")
- tti_type = self.parse_show_tti (lines)
-
- if tti_type == None:
- print "Failed change of tti to " + to_tti + ". Parse fail on \'show tti\'."
- return False
- elif tti_type != to_tti:
- print "Failed change of tti to " + to_tti + ". Instead got: " + tti_type
- return False
- else:
- return True
-
-
ADDED lib/simh.py.in
Index: lib/simh.py.in
==================================================================
--- /dev/null
+++ lib/simh.py.in
@@ -0,0 +1,1098 @@
+#!/usr/bin/env @PYCMD@
+# -*- coding: utf-8 -*-
+########################################################################
+# simh/__init__.py - A wrapper class around pexpect for communicating
+# with an instance of the PiDP-8/I SIMH simulator running OS/8.
+#
+# See ../doc/class-simh.md for a usage tutorial.
+#
+# Copyright © 2017 by Jonathan Trites, © 2017-2019 by William Cattey
+# and Warren Young.
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM,
+# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
+# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
+# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+# Except as contained in this notice, the names of the authors above
+# shall not be used in advertising or otherwise to promote the sale,
+# use or other dealings in this Software without prior written
+# authorization from those authors.
+########################################################################
+
+import os
+import pexpect
+import pkg_resources
+import subprocess
+import tempfile
+import time
+import re
+import sys
+
+import pidp8i
+
+class simh:
+ # pexpect object instance, set by ctor
+ _child = None
+
+ # Constant used by os8_kbd_delay, assembled in stages:
+ #
+ # 1. PDP-8 RS-232 bits per character: 7-bit ASCII plus necessary
+ # start, stop, and parity bits.
+ #
+ # 2. The ratio of the instructions per second ratios of a PDP-8/I to
+ # that of the host hardware running the simulator. The former is
+ # an approximate value; see lib/pidp8i/ips.py.in for the value and
+ # its defense. The latter is either the IPS rate for:
+ #
+ # a) a Raspberry Pi Model B+, that being the slowest host system
+ # we run this simulator on; or
+ #
+ # b) the IPS rate of the actual host hardware if you have run the
+ # "bin/teco-pi-demo -b" benchmark on it.
+ #
+ # 2. The fact that real PDP-8s ran OS/8 reliably at 300 bps, and have
+ # been claimed to get flaky as early as 600 bps by some. (Others
+ # claim to have run them up to 9,600 bps.)
+ #
+ # 3. The "safe BPS" value is the fastest bit per second speed actual
+ # PDP-8 hardware was known to run OS/8 terminal I/O at. In this
+ # case, it is the high-speed tape reader.
+ #
+ # TODO: We may be able to increase this.
+ #
+ # We have one report that OS/8 was tested with terminals up to
+ # about ~600 bps before becoming unreliable.
+ #
+ # We have another report that OS/8 could run under ETOS with
+ # 9,600 bps terminals, but we don't know if that tells us anything
+ # about OS/8 running without the ETOS multitasking hardware.
+ #
+ # 4. Given above, calculate safe characters per second for host HW.
+ #
+ # 5. Invert to get seconds per character, that being the delay value.
+ _bpc = 7 + 1 + 1 + 1 # [1]
+ _ips_ratio = float (pidp8i.ips.current) / pidp8i.ips.pdp8i # [2]
+ _pdp8i_safe_bps = 300 # [3]
+ _host_safe_cps = _pdp8i_safe_bps * _ips_ratio / _bpc # [4]
+ _os8_kbd_delay = 1 / _host_safe_cps # [5]
+
+ # Known OS/8 error strings and a flag indicating whether the error
+ # dumps us back out to the OS/8 command monitor or leaves us in the
+ # called program.
+
+ _os8_errors = [
+ # The date comment tells when each message is observed and validated
+ #
+ # OS/8 Handbook 1974 page 1-43/81 Keyboard Monitor Error Messages:
+ ["MONITOR ERROR 2 AT \d+ \\(DIRECTORY I/O ERROR\\)", True], # 2018.02.11
+ ["MONITOR ERROR 5 AT \d+ \\(I/O ERROR ON SYS\\)", True],
+ ["MONITOR ERROR 6 AT \d+ \\(DIRECTORY I/O ERROR\\)", True],
+ ["(\S+) NOT AVAILABLE", False],
+ ["(\S+) NOT FOUND", False], # 2018.02.11
+ # OS/8 Handbook 1974 page 1-51/89 Command Decoder Error Messages
+ ["ILLEGAL SYNTAX", False], # 2018.02.11
+ ["(\S+) DOES NOT EXIST", False],
+ # ["(\S+) NOT FOUND", False], # See above
+ ["TOO MANY FILES", False],
+ # OS/8 Handbook 1974 page 1-75/113 CCL Error Messages
+ ["BAD DEVICE", False],
+ ["BAD EXTENSION", False],
+ # OS/8 Handbook 1974 page 1-106/144 PIP Error Messages
+ ["ARE YOU SURE", False],
+ ["BAD DIRECTORY ON DEVICE #\s?\d+", False],
+ ["BAD SYSTEM HEAD", False],
+ ["CAN'T OPEN OUTPUT FILE", False],
+ ["DEVICE #\d+ NOT A DIRECTORY DEVICE", False],
+ ["DIRECTORY ERROR", False],
+ ["ERROR DELETING FILE", False],
+ ["ILLEGIAL BINARY INPUT, FILE #\d+", False],
+ ["INPUT ERROR, FILE #\s?\d+", False],
+ ["IO ERROR IN \\(file name\\) --CONTINUING", False],
+ ["NO ROOM FOR OUTPUT FILE", False],
+ ["NO ROOM IN \\(file name\\) --CONTINUING", False],
+ ["OUTPUT ERROR", False],
+ ["PREMATURE END OF FILE, FILE #\s?\d+", False],
+ ["ZERO SYS?", False],
+ # OS/8 Handbook 1974 page 2-81/244: DIRECT Error Messages
+ ["BAD INPUT DIRECTORY", False],
+ ["DEVICE DOES NOT HAVE A DIRECTORY", False],
+ ["ERROR CLOSING FILE", False],
+ ["ERROR CLOSING FILE", False],
+ ["ERROR READING INPUT DIRECTORY", False],
+ ["ILLEGAL \\*", False],
+ # OS/8 Handbook 1974 page: 2-109/272: FOTP Error Messages
+ ["ERROR ON INPUT DEVICE, SKIPPING \\((\S+)\\)", False],
+ ["ERROR ON OUTPUT DEVICE, SKIPPING \\((\S+)\\)", False],
+ ["ERROR READING INPUT DIRECTORY", False],
+ ["ERROR READING OUTPUT DIRECTORY", False],
+ ["ILLEGAL \\?", False],
+ ["NO FILES OF THE FORM (\S+)", False],
+ ["NO ROOM, SKIPPING \\((\S+)\\)", False],
+ ["SYSTEM ERROR-CLOSING FILE", False],
+ ["USE PIP FOR NON-FILE STRUCTURED DEVICE", False],
+ ["LINE TOO LONG IN FILE#\d+", False],
+ ]
+
+ # Pattern to match a SIMH command. The command verb ends up in
+ # match().group(1), and anything after the verb in group(3).
+ _simh_comm_re = re.compile ("^\s*(\S+)(\s+(.*))?$")
+
+ # Significant prefixes of SIMH command verbs that transition from SIMH
+ # command context back into the simulation: BOOT, CONTINUE, and GO.
+ # We need only the first letter in all cases, since these particular
+ # commands are not ambiguous. They're uppercase because the code that
+ # uses this always uppercases the command before searching this list.
+ _enters_os8_context = ["B", "C", "G"]
+
+
+ #### ctor ############################################################
+ # basedir is the parent of bin/{pdp8,pidp8i-sim}.
+ #
+ # Setting the skip_gpio flag to True causes us to use bin/pdp8 instead
+ # of bin/pidp8i-sim, which runs the simulator with the PiDP-8/I GPIO
+ # thread disabled. You might do this because you don't need any front
+ # panel interaction or because you know you're not running on an RPi
+ # in the first place.
+
+ def __init__ (self, basedir, skip_gpio = False):
+ # Start the simulator instance
+ sim = 'pdp8' if skip_gpio else 'pidp8i-sim';
+ self._child = pexpect.spawn (os.path.join (basedir, 'bin', sim))
+ self._valid_pip_options = ["/A", "/B", "/I"]
+ self._os8_file_re = re.compile("(\S+):(\S+)?")
+ self._os8_error_match_strings = []
+ self._os8_fatal_check = []
+ self.verbose = False
+
+ # We keep track of what our command context is so our caller does
+ # not need to explicitly call back_to_cmd() or sendcontrol ('e').
+ # We keep track of the command context and transition automatically.
+ self._context = "simh"
+
+ # Parse our OS/8 Errors table into actionable chunks
+ for error_spec in self._os8_errors:
+ self._os8_error_match_strings.append(error_spec[0])
+ self._os8_fatal_check.append(error_spec[1])
+
+ self._pip_to_replies = ['\\^']
+ self._pip_to_replies.extend(self._os8_error_match_strings)
+ # Did command start the command decoder or die with a monitor error?
+ self._cd_replies = ['\\*']
+ self._cd_replies.extend(self._os8_error_match_strings)
+
+ # Turn off pexpect's default inter-send() delay. We add our own as
+ # necessary. The conditional tracks an API change between 3 and 4.
+ pev4 = (pkg_resources.get_distribution("pexpect").parsed_version >
+ pkg_resources.parse_version("4.0"))
+ self._child.delaybeforesend = None if pev4 else 0
+
+ # Wait for the simulator's startup message.
+ if not self.try_wait ('PDP-8 simulator V.*git commit id: [0-9a-f]', 10):
+ raise RuntimeError ('Simulator failed to start')
+
+
+ #### back_to_cmd ######################################################
+ # Pause the simulation and return to the SIMH command prompt when the
+ # simulated software emits the given prompt string. Typically used to
+ # wait for OS/8 to finish running a command so we can do something
+ # down at the SIMH layer instead.
+
+ def back_to_cmd (self, prompt):
+ self._child.expect ("\n%s$" % prompt)
+ self.os8_kbd_delay ()
+ self._child.sendcontrol ('e')
+ self._context = "simh"
+
+
+ #### os8_get_file ####################################################
+ # Rough inverse of os8_send_file.
+ #
+ # Both paths must be given and are used literally. (Contrast our
+ # inverse, where the destinatinon file name is produced from the
+ # source if not given.)
+ #
+ # When this function is called to pull a file sent by our inverse, the
+ # conversion should be lossless except for the transforms done by our
+ # underlying utility tools, such as the LF -> CR+LF done by txt2ptp
+ # but not undone by ptp2txt.
+ #
+ # Entry context should be inside OS/8. Exit context is inside OS/8.
+
+ def os8_get_file (self, intname, extname):
+ # Attach a blank paper tape to the simulator.
+ ptf = tempfile.NamedTemporaryFile (suffix = '.pt', delete = False)
+ ptf.close ()
+ ptn = ptf.name
+ self.back_to_cmd ('\\.')
+ self.send_cmd ('attach ptp ' + ptn)
+
+ # Punch internal file to external paper tape image
+ self.os8_restart ()
+ self.os8_send_cmd ('\\.', 'PUNCH ' + intname);
+ self.back_to_cmd ('\\.') # wait for transfer to finish
+
+ # Convert text file from SIMH paper tape format
+ tool = os.path.join (pidp8i.dirs.build, 'bin', 'ptp2txt')
+ self.send_cmd ('detach ptp')
+ subprocess.call (tool + ' < ' + ptn + ' > ' + extname, shell = True)
+
+ # Return to OS/8, just because that's where we were on entry, so we
+ # should not change that.
+ self.os8_restart ()
+
+
+ #### os8_kbd_delay ###################################################
+ # Artificially delay the media generation process to account for the
+ # fact that OS/8 lacks a modern multi-character keyboard input buffer.
+ # It is unsafe to send text faster than a contemporary terminal could,
+ # though we can scale it based on how much faster this host is than a
+ # real PDP-8. See the constants above for the calculation.
+
+ def os8_kbd_delay (self):
+ time.sleep (self._os8_kbd_delay)
+
+
+ #### os8_send_cmd ####################################################
+ # Wait for an OS/8 command prompt running within SIMH, then send the
+ # given line.
+ #
+ # The default timeout may seem excessive, but it is based on hard
+ # experience: when SIMH is running on a slow host with slow devices
+ # (e.g. the byte-by-byte transfer of the TD8E tape controller) a
+ # single OS/8 command can take a very long time if it requires a lot
+ # of I/O. If you are calling this for a command that you know for a
+ # fact takes less time on all hosts and with all practical device
+ # configurations, we encourage you to pass a smaller value.
+ #
+ # The caller must pass a prompt string because OS/8 has several
+ # different prompt types: ., *, $, and #, at least. Beware in passing
+ # these that they're treated as regular expressions, so characters
+ # special in Python REs must be escaped. And then since the RE escape
+ # character (\) is also special in Python strings, you must double-
+ # escape *it*. So, '\\$' is a reasonable thing to pass as the prompt
+ # value, meaning "look for a literal $ character."
+ #
+ # This routine requires the caller to ensure that the system is in
+ # OS/8 Keyboard Monitor context — that is, ready for another OS/8
+ # command — before calling it. While this routine is able to check
+ # whether we're in OS/8 context as a prerequisite, it is not practical
+ # for us to return the system to OS/8 context automatically from some
+ # other context because that would require us to know the current
+ # context in detail, but only the caller has that full knowledge.
+ #
+ # Part of the problem is that in order to synchronize this object's
+ # internal state machine with the SIMH + OS/8 + running program state,
+ # we have to somehow provoke a prompt character from the running
+ # program. How do we do that without knowing the current context?
+ # In some contexts, a CR or LF will do it, in others BS, and in others
+ # it'll take Ctrl-C. Then you have a new problem, with is that those
+ # same characters aren't harmless: they'll change the very context
+ # we're trying to probe! For instance, a Ctrl-C sent to the OS/8
+ # Keyboard Monitor just results in another prompt, but a Ctrl-C sent
+ # to a program running *under* OS/8 might kill it. Or it might be
+ # treated as input. Or it might be ignored entirely.
+ #
+ # There is no magic sequence we can send to SIMH or OS/8 to return the
+ # system to OS/8 Keyboard Monitor context without either changing the
+ # context in some way that might break the caller's needed state (e.g.
+ # Ctrl-E, go 7600) or lose data (e.g. Ctrl-C) or fail entirely (e.g.
+ # Enter.) It's up to the caller to arrange this.
+
+ def os8_send_cmd (self, prompt, line, debug=False, timeout=60):
+ if self._context != 'os8':
+ print("OS/8 is not running. Cannot execute: " + lin)
+ return
+ if debug:
+ print("os8_send_cmd: expecting: " + prompt)
+ print("\tLast match before: {" + self._child.before.decode() + "}")
+ print("\tLast match after: {" + self._child.after.decode() + "}")
+ self._child.expect ("\n%s$" % prompt, timeout = timeout)
+ self.os8_send_line (line)
+
+
+ #### os8_send_ctrl ###################################################
+ # Send a control character to OS/8 corresponding to the ASCII letter
+ # given. We precede it with the OS/8 keyboard delay, since we're
+ # probably following a call to os8_send_line or os8_send_cmd.
+
+ def os8_send_ctrl (self, char):
+ cc = char[0].lower ()
+ self.os8_kbd_delay ()
+ self._child.sendcontrol (cc)
+
+ if cc == 'e': self._context = 'simh'
+
+
+ #### mk_os8_name # ###################################################
+ # Create an OS/8 filename: of the form XXXXXX.YY
+ # From a POSIX path.
+
+ def mk_os8_name(self, dev, path):
+ bns = os.path.basename (path)
+ bns = re.sub("-|:|\(|\)|!", "", bns)
+ bns = bns.upper()
+ if "." not in bns:
+ return dev + bns[:min(6, len(bns))]
+ else:
+ dot = bns.index('.')
+ return dev + bns[:min(6, dot, len(bns))] + "." + bns[dot+1: dot+3]
+
+
+ #### os8_send_file ###################################################
+ # Send a copy of a local text file to OS/8. The local path may
+ # contain directory components, but the remote must not, of course.
+ #
+ # If the destination file name is not uppercase, it will be so forced.
+ #
+ # If the destination file name is not given, it is taken as the
+ # basename of the source file name.
+ #
+ # The file is sent via the SIMH paper tape device through PIP in its
+ # default ASCII mode, rather than character by character for two reasons:
+ #
+ # 1. It's faster. It runs as fast as the simulator can process the
+ # I/O instructions, without any os8_kbd_delay() hooey.
+ #
+ # 2. It allows lowercase input regardless of the way the simulator is
+ # configured. ASCII is ASCII.
+ #
+ # Entry context should be inside OS/8. Exit context is inside OS/8.
+
+ def os8_send_file (self, source, dest = None):
+ # Create path and file names not given
+ bns = os.path.basename (source)
+ if dest == None: dest = bns
+ dest = dest.upper ()
+
+ # Convert text file to SIMH paper tape format
+ bdir = pidp8i.dirs.build
+ pt = os.path.join (bdir, 'obj', bns + '.pt')
+ tool = os.path.join (bdir, 'bin', 'txt2ptp')
+ subprocess.call (tool + ' < ' + source + ' > ' + pt, shell = True)
+
+ # Paper tape created, so attach it read-only and copy it in. We're
+ # relying on txt2ptp to insert the Ctrl-Z EOF marker at the end of
+ # the file.
+ self.back_to_cmd ('\\.')
+ self.send_cmd ('attach -r ptr ' + pt)
+ self.os8_restart ()
+ self.os8_send_cmd ('\\.', 'R PIP')
+ self.os8_send_cmd ('\\*', dest + ' ' + pt, shell = True)
+ did_conversion = True
+ elif option not in self._valid_pip_options:
+ print("Invalid PIP option: " + option + ". Ignoring: " + path + \
+ " to OS/8.")
+ return
+ else:
+ pt = path
+
+ # TODO: Sacrificial extra character code goes here.
+
+ # Paper tape created, so attach it read-only and copy it in. We're
+ # relying on txt2ptp to insert the Ctrl-Z EOF marker at the end of
+ # the file.
+ self.back_to_cmd ('\\.')
+ self.send_cmd ('attach -r ptr ' + pt)
+ self.os8_restart ()
+ self.os8_send_cmd ('\\.', 'R PIP')
+ # Was the start of PIP successful, or did we get a Monitor error?
+ reply = self._child.expect (self._cd_replies)
+ if reply != 0:
+ self.pip_error_handler ("os8_pip_to", reply)
+ return
+
+ # Has the read-in been successful?
+ self.os8_send_line (dest + ' ' + path, shell = True)
+ os.remove(pf)
+
+
+ #### os8_send_line ###################################################
+ # Core of os8_send_cmd. Also used by code that needs to send text
+ # "blind" to OS/8, without expecting a prompt, as when driving EDIT.
+
+ def os8_send_line (self, line):
+ self.os8_send_str (line)
+ self._child.send ("\r")
+
+
+ #### os8_send_str ########################################################
+ # Core of os8_send_line. Also used by code that needs to send text
+ # "blind" to OS/8, without expecting a prompt and without a CR, as
+ # when driving TECO.
+
+ def os8_send_str (self, str):
+ for i in range (0, len (str)):
+ self._child.send (str[i])
+ self.os8_kbd_delay ()
+
+
+ #### os8_resume #######################################################
+ # Resume OS/8.
+ #
+ # It would be nice if we could just send the "cont" command
+ # and have python expect and OS/8 synch right up.
+ # But so far we have not figured out how to do that.
+ # To resume OS/8 from SIMH we need to provoke a prompt.
+ # Typing a rubout or ^U at a SIMH terminal session does this.
+ # But not when SIMH is run under python expect.
+ # We don't know why.
+ #
+ # boot works
+ # go 7600 works
+ # ^C \n\r works.
+ #
+ # The resume command uses the ^C method as the least disruptive
+ # to system state.
+
+ def os8_resume (self):
+ if self._context == "os8": return # Already running.
+
+ self.send_cmd("cont") # sets os8 context for us.
+
+ # Now provoke a keyboard monitor prompt.
+ self.os8_send_ctrl('c')
+ self.os8_kbd_delay()
+ self.os8_send_str('\r\n')
+
+
+ #### os8_restart #######################################################
+ # Called while in the SIMH command prompt, this restarts OS/8.
+ #
+ # This one-line function exists to abstract the method we use and to
+ # document the reason we do it this way.
+ #
+ # Currently we do this by calling the OS/8 command entry point, which
+ # has the virtue that it forces another . prompt, which keeps the
+ # send/expect sequencing simple when switching between OS/8 and SIMH
+ # command modes.
+ #
+ # That is why we don't use "cont" here instead: it requires that the
+ # caller always be aware of when the . prompt went out; complicated.
+ #
+ # Another simple alternative is "boot rk0", which actually benchmarks
+ # a smidge faster here. We choose this method instead because we
+ # expect that some of our callers will want to do several different
+ # things in a single OS/8 session, which rebooting would screw up.
+
+ def os8_restart (self):
+ self.send_cmd ("go 7600")
+
+
+ #### os8_squish ########################################################
+ # Wraps the OS/8 SQUISH command for a given device.
+
+ def os8_squish (self, device):
+ self.os8_send_cmd ('\\.', "SQUISH " + device + ":")
+ self.os8_kbd_delay ()
+ self._child.send ("Y\r");
+
+
+ #### os8_zero_core ###################################################
+ # Starting from OS/8 context, bounce out to SIMH context and zero all
+ # of core excepting:
+ #
+ # 0. zero page - many apps put temporary data here
+ # 1. the top pages of fields 1 & 2 - OS/8 is resident here
+ # 2. the top page of field 2 - OS/8's TD8E driver (if any) lives here
+ #
+ # We then restart OS/8, which means we absolutely need to do #1 and
+ # may need to do #2. We could probably get away with zeroing page 0.
+ #
+ # All of the above explains why we have this special OS/8 alternative
+ # to the zero_core() method.
+
+ def os8_zero_core (self):
+ self.back_to_cmd ('\\.')
+ self.send_cmd ('de 00200-07577 0')
+ self.send_cmd ('de 10000-17577 0')
+ self.send_cmd ('de 20000-27577 0')
+ self.send_cmd ('de 30000-77777 0')
+ self.os8_restart ()
+
+
+ #### quit ############################################################
+ # Quits the simulator and waits for it to exit
+
+ def quit (self):
+ self.send_cmd ("q")
+ self._child.expect (pexpect.EOF)
+
+
+ #### read_tail #######################################################
+ # Watch for a literal string, then get what follows on that line.
+
+ def read_tail (self, head, timeout = -1):
+ self._child.expect_exact ([head], timeout)
+ return self._child.readline ()
+
+
+ #### send_cmd ########################################################
+ # Wait for a SIMH command prompt and then send the given command.
+ # If we are not in the simh context send ^e and set context "simh".
+ # If we are not in simh context, send ^e set context "simh"
+ # and hope for the best.
+ # If we issue a command that enters os8 context, set context "os8".
+ # Note exiting out of OS/8 into the SIMH context is a bit of a
+ # trap door. Resynchronizing with python expect requires provoking
+ # a prompt, and prompts are context specific.
+ # Perhaps we should require separate and explicit commands to
+ # escape to SIMH. But for now, just be careful to use os8_resume
+ # after calling send_cmd.
+
+ def send_cmd (self, cmd):
+ if self._context == "os8":
+ self._child.expect ("\n\\.$")
+ self._child.sendcontrol ('e')
+ self._context = "simh"
+ elif self._context != "simh":
+ self._child.sendcontrol ('e')
+ self._context = "simh"
+
+ self._child.expect ("sim> $")
+ self._child.sendline (cmd)
+ m = re.match (self._simh_comm_re, cmd)
+ if m != None and m.group(1)[:1].upper() in self._enters_os8_context:
+ self._context = "os8"
+
+
+ #### send_line #######################################################
+ # Sends the given line "blind", without waiting for a prompt.
+
+ def send_line (self, line):
+ self._child.sendline (line)
+
+
+ #### set_logfile #####################################################
+
+ def set_logfile (self, lf):
+ self._child.logfile = lf
+
+
+ #### spin ############################################################
+ # Let child run without asking anything more from it, with an optional
+ # timeout value. If no value is given, lets child run indefinitely.
+
+ def spin (self, timeout = None):
+ self._child.expect (pexpect.EOF, timeout = timeout)
+
+
+ #### try_wait ########################################################
+ # A wrapper around self._child.expect which catches exceptions and
+ # returns false on pexpect timeout. If you pass a list instead of a
+ # string, it also returns true if the match wasn't for the first
+ # element, so you can pass [success, failure1, failure2, etc.] to
+ # check for a known-success case and one or more failure cases.
+
+ def try_wait (self, matches, timeout = -1):
+ try:
+ return self._child.expect (matches, timeout = timeout) == 0
+ except pexpect.exceptions.TIMEOUT:
+ sys.stderr.write ("Exceeded " + str(timeout) + " sec timeout " +
+ "waiting for " + str(matches) + "\n")
+ return False
+ except:
+ sys.stderr.write ("Failed to match " + str(matches) +
+ ": unknown exception.\n")
+ return False
+
+
+ #### zero_core #######################################################
+ # From SIMH context, zero the entire contents of core, which is
+ # assumed to be 32 kWords.
+ #
+ # SIMH's PDP-8 simulator doesn't start with core zeroed, on purpose,
+ # because the actual hardware did not do that. SIMH does not attempt
+ # to simulate the persistence of core memory by saving it to disk
+ # between runs, but the SIMH developers are right to refuse to do this
+ # by default: you cannot trust the prior state of a PDP-8's core
+ # memory before initializing it yourself.
+ #
+ # See os8_zero_core () for a less heavy-handed alternative for use
+ # when running under OS/8.
+
+ def zero_core (self):
+ self.send_cmd ('de all 0')
+
+
+ #### describe_dev_config #############################################
+ # We provide an interface to alter SIMH device configurations for
+ # specific parameters and specific devices
+ #
+ # dev configs supported: rx, tti, tape
+ #
+ # rx: RX8E, RX28 RX8E is the simh name for RX01 support.
+ # RX28 is the simh name for RX02 support.
+ # tti: KSR, 7b 7b is full keyboard support.
+ # KSR forces upcase of lower case keys on input.
+ # tape: td, dt td is the TD8E DECtape device
+ # dt is the TC08 DECtape device
+
+ def describe_dev_config (self, name):
+ if name == "tape":
+ lines = self.do_simh_show("dt")
+ dev_status = self.parse_show_tape_dev(lines)
+
+ if dev_status == "dt": return "dt"
+ else:
+ lines = self.do_simh_show("td")
+ return self.parse_show_tape_dev(lines)
+ elif name == "rx":
+ lines = self.do_simh_show("rx")
+ return self.parse_show_rx_dev(lines)
+ elif name == "tti":
+ lines = self.do_simh_show("tti")
+ return self.parse_show_tti(lines)
+ else: return None
+
+
+ #### do_simh_show ###################################################
+ # Calls show on the device name.
+ # Returns array of lines from output.
+
+ def do_simh_show (self, name):
+ supported_shows = ["dt", "td", "tti", "rx"]
+ if name not in supported_shows: return None
+
+ ucname = name.upper()
+ self.send_cmd("show " + name)
+ self._child.expect(ucname + "\s+(.+)\r")
+ lines = self._child.after.decode().split ("\r")
+ return lines
+
+
+ #### parse_show_tape_dev ############################################
+ # Returns current state of DECtape support.
+ # One of: disabled, td, dt, or None if parse fails.
+
+ def parse_show_tape_dev (self, lines):
+ if lines == None: return None
+ is_enabled_re = re.compile("^(TD|DT)\s+(disabled|(devno=\S+,\s(\d)\s+units))$")
+ m = re.match(is_enabled_re, lines[0])
+
+ if m == None or m.group(1) == None or m.group(2) == None: return None
+ if m.group(2) == "disabled": return "disabled"
+ elif m.group(1) == "TD": return "td"
+ elif m.group(1) == "DT": return "dt"
+ else: return None
+
+
+ #### parse_show_tape_attached ########################################
+ # Returns an ordered list of files attached or None if disabled.
+ def parse_show_tape_attached (self, lines):
+ if lines == None: return None
+ if len(lines) < 2: return None
+ attached = {}
+ attachment_re = re.compile("^\s+(((DT|TD)(\d)(.+),\s+(not\s+attached|attached\s+to\s+(\S+)),(.+))|12b)$")
+ for line in lines[1:]:
+ m = re.match(attachment_re, line)
+ if m == None or m.group(1) == None or m.group(1) == "12b": continue
+ filename = m.group(7)
+ if filename == None: filename = ""
+ attached[m.group(4)] = filename
+ return attached
+
+ #### do_print_lines ###################################################
+ # Debugging aid. Prints what we parsed out of _child.after.
+ def do_print_lines (self, lines):
+ for line in lines:
+ print(line)
+
+ #### simh_configure routines #########################################
+ # These routines affect the state of device configuration in SIMH.
+ # They are intended as robust ways to toggle between incompatible
+ # configurations of SIMH:
+ # Choice of TD8E or TC08 DECtape (SIMH td and dt devices).
+ # Choice of RX01 or RX02 Floppy emulation.
+ # The SIMH rx device sets RX8E for RX01, and RX28 for RX02.
+ # Choice of KSR or 7bit console configuration.
+ #
+ # When re-configuring dt, dt, and rx devices, any attached
+ # images are detached before reconfiguration is attempted.
+ # (SIMH errors out if you don't detach them.)
+ #
+ # The check to see if the change is unnecessary.
+ # For now they return None if no change necessary.
+ #
+ # After re-configuring the device, the SIMH show command is used
+ # to confirm the re-configuration was successful.
+ #
+ # In future, we should add exception handling for no change necessary.
+ # For now, return True if the change was successful and False if not.
+
+
+ def set_tape_config (self, to_tape):
+ if to_tape == "dt": from_tape = "td"
+ elif to_tape == "td": from_tape = "dt"
+ else:
+ print("Cannot set_tape_config for " + to_tape)
+ return False
+
+ if self.verbose:
+ print("Disable: " + from_tape + ", and enable: " + to_tape)
+
+ lines = self.do_simh_show(from_tape)
+ from_status = self.parse_show_tape_dev(lines)
+
+ if from_status == None:
+ print("do_tape_change: Trouble parsing \'show " + from_tape + \
+ "\' output from simh. Giving up on:")
+ self.do_print_lines (lines)
+ return False
+
+ if from_status == "disabled":
+ print(from_tape + " already is disabled.")
+ else:
+ attached_from = self.parse_show_tape_attached(lines)
+ if attached_from == None:
+ print("do_tape_change: Trouble parsing \'show " + from_tape + \
+ "\' output from simh. Giving up on:")
+ self.do_print_lines (lines)
+ return False
+ else:
+ for unit in attached_from.keys():
+ if attached_from[unit] != "":
+ det_comm = "det " + from_tape + unit
+ if self.verbose:
+ print(det_comm + "(Had: " + attached_from[unit] + ")")
+ self.send_cmd(det_comm)
+ self.send_cmd("set " + from_tape + " disabled")
+
+ lines = self.do_simh_show(to_tape)
+ to_status = self.parse_show_tape_dev(lines)
+
+ if to_status == None:
+ print("do_tape_change: Trouble parsing \'show " + to_tape + \
+ "\' output from simh. Giving up on:")
+ self.do_print_lines (lines)
+ return False
+ elif to_status != "disabled":
+ print(to_tape + " already is enabled.")
+ else:
+ self.send_cmd("set " + to_tape + " enabled")
+
+ # Test to confirm to_tape is now enabled.
+
+ lines = self.do_simh_show(to_tape)
+ to_status = self.parse_show_tape_dev(lines)
+
+ if to_status == None:
+ print("Failed enable of " + to_tape + \
+ ". Parse fail on \'show " + to_tape + "\'. Got:")
+ self.do_print_lines (lines)
+ return False
+ elif to_status == "disabled":
+ print("Failed enable of " + to_tape + ". Device still disabled.")
+ return False
+ else:
+ return True
+
+
+ #### parse_show_rx_dev ###############################################
+ # Show the rx device configuration.
+
+ def parse_show_rx_dev (self, lines):
+ if lines == None: return None
+ is_enabled_re = re.compile("^\s*(RX)\s+(disabled|((RX8E|RX28),\s+devno=\S+,\s+(\d)\s+units))$")
+ m = re.match(is_enabled_re, lines[0])
+ if m == None or m.group(2) == None: return None
+ if m.group(2) == "disabled": return "disabled"
+ return m.group(4)
+
+
+ #### parse_show_rx_attached ##########################################
+ # Returns an ordered list of files attached or None if disabled.
+
+ def parse_show_rx_attached (self, lines):
+ if len(lines) < 2: return None
+ attached = {}
+ attachment_re = re.compile("^\s+(((RX)(\d)(.+),\s+(not\s+attached|attached\s+to\s+(\S+)),(.+))|autosize)$")
+ for line in lines[1:]:
+ m = re.match(attachment_re, line)
+ if m == None or m.group(1) == None or m.group(1) == "autosize": continue
+ filename = m.group(7)
+ if filename == None: filename = ""
+ attached[m.group(4)] = filename
+ return attached
+
+
+ #### set_rx_config ####################################################
+
+ def set_rx_config (self, to_rx):
+ to_rx = to_rx.lower()
+ if to_rx == "rx8e": from_rx = "rx28"
+ elif to_rx == "rx01":
+ to_rx = "rx8e"
+ from_rx = "rx28"
+ elif to_rx == "rx28": from_rx = "rx8e"
+ elif to_rx == "rx02":
+ to_rx = "rx28"
+ from_rx = "rx8e"
+ else:
+ print("Cannot set_rx_config for " + to_rx)
+ return False
+
+ if self.verbose:
+ print("Switch rx driver: " + from_rx + ", to: " + to_rx)
+ lines = self.do_simh_show("rx")
+
+ rx_type = self.parse_show_rx_dev (lines)
+ if rx_type == None:
+ print("do_rx_change: Trouble parsing \'show rx\' output from simh. Giving up on:")
+ self.do_print_lines(lines)
+ return False
+ elif rx_type == "disabled":
+ if self.verbose: print("rx is disabled. Enabling...")
+ self.send_cmd("set rx enabled")
+ # Retry getting rx info
+ lines = self.do_simh_show("rx")
+ rx_type = self.parse_show_rx_dev (lines)
+ if rx_type == None:
+ print("do_rx_change after re-enable: Trouble parsing \`show rx\` output from simh. Giving up on:")
+ self.do_print_lines(lines)
+ return False
+ elif rx_type == "disabled":
+ print("do_rx_change re-enable of rx failed. Giving up.")
+ return False
+
+ if rx_type.lower() == to_rx:
+ print("rx device is already set to " + to_rx)
+ return None
+
+ attached_rx= self.parse_show_rx_attached(lines)
+ if attached_rx == None:
+ print("do_rx_change: Trouble parsing /'show rx\' from simh to find rx attachments. Got:")
+ self.do_print_lines(lines)
+ else:
+ for unit in attached_rx.keys():
+ if attached_rx[unit] != "":
+ det_comm = "det rx" + unit
+ if self.verbose:
+ print(det_comm + "(Had: " + attached_rx[unit] + ")")
+ self.send_cmd(det_comm)
+
+ self.send_cmd("set rx " + to_rx)
+
+ # Test to confirm new setting of RX
+ lines = self.do_simh_show("rx")
+ rx_type = self.parse_show_rx_dev (lines)
+
+ if rx_type == None:
+ print("Failed change of rx to " + to_rx + \
+ ". Parse fail on \'show rx\'.")
+ return False
+ elif rx_type.lower() != to_rx:
+ print("Failed change of rx to " + to_rx + ". Instead got: " + \
+ rx_type)
+ return False
+ return True
+
+
+ #### get_tti ##################################################
+ # Returns an ordered list of files attached or None if disabled.
+ def parse_show_tti (self, lines):
+ if lines == None: return None
+ is_enabled_re = re.compile("^(KSR|7b)$")
+ if len(lines) < 2: return None
+
+ # That second line of output contains embedded newlines.
+ m = re.match(is_enabled_re, lines[1].strip())
+ if m == None or m.group(1) == None: return None
+ return m.group(1)
+
+
+
+ #### do_tti_change ###################################################
+
+ def set_tti_config (self, to_tti):
+ if to_tti == "KSR": from_tti = "7b"
+ elif to_tti == "7b": from_tti = "KSR"
+ else:
+ print("Cannot set_tti_config to " + to_tti)
+ return
+
+ if self.verbose:
+ print("Switch tti driver from: " + from_tti + ", to: " + to_tti)
+
+ lines = self.do_simh_show("tti")
+ tti_type = self.parse_show_tti (lines)
+ if tti_type == None:
+ print("do_tti_change: Trouble parsing \'show tti\' output from simh. Giving up on:")
+ self.do_print_lines(lines)
+ return False
+ elif tti_type == to_tti:
+ print("tti device is already set to " + to_tti)
+ return None
+
+ self.send_cmd("set tti " + to_tti)
+
+ # Test to confirm new setting of tti
+ lines = self.do_simh_show("tti")
+ tti_type = self.parse_show_tti (lines)
+
+ if tti_type == None:
+ print("Failed change of tti to " + to_tti + \
+ ". Parse fail on \'show tti\'.")
+ return False
+ elif tti_type != to_tti:
+ print("Failed change of tti to " + to_tti + ". Instead got: " + \
+ tti_type)
+ return False
+ else:
+ return True
+
+
Index: src/SIMH/PDP8/pdp8_cpu.c
==================================================================
--- src/SIMH/PDP8/pdp8_cpu.c
+++ src/SIMH/PDP8/pdp8_cpu.c
@@ -1614,10 +1614,25 @@
saved_MQ = MQ & 07777;
pcq_r->qptr = pcq_p; /* update pc q ptr */
return reason;
} /* end sim_instr */
+/*
+ * This sequence of instructions is a mix that hopefully
+ * represents a resonable instruction set that is a close
+ * estimate to the normal calibrated result.
+ */
+
+static const char *pdp8_clock_precalibrate_commands[] = {
+ "106 100"
+ "-m 100 MQL MQA"
+ "-m 101 ISZ 112",
+ "-m 102 JMP I 106",
+ "-m 103 JMP I 106",
+ "PC 100",
+ NULL};
+
/* Reset routine */
t_stat cpu_reset (DEVICE *dptr)
{
saved_LAC = 0;
@@ -1625,11 +1640,14 @@
saved_DF = IB = saved_PC & 070000;
UF = UB = gtf = emode = 0;
pcq_r = find_reg ("PCQ", NULL, dptr);
if (pcq_r)
pcq_r->qptr = 0;
-else return SCPE_IERR;
+else
+ return SCPE_IERR;
+sim_clock_precalibrate_commands = pdp8_clock_precalibrate_commands;
+sim_vm_initial_ips = 10 * SIM_INITIAL_IPS;
sim_brk_types = SWMASK ('E') | SWMASK('I');
sim_brk_dflt = SWMASK ('E');
return SCPE_OK;
}
Index: src/SIMH/PDP8/pdp8_ct.c
==================================================================
--- src/SIMH/PDP8/pdp8_ct.c
+++ src/SIMH/PDP8/pdp8_ct.c
@@ -327,12 +327,13 @@
if ((old_ust ^ uptr->UST) == (UST_REV|UST_GAP)) { /* rev in gap? */
if (DEBUG_PRS (ct_dev)) fprintf (sim_deb,
">>CT skip gap: op=%o, old_sta = %o, pos=%d\n",
fnc, uptr->UST, uptr->pos);
if (uptr->UST) /* skip file gap */
- sim_tape_rdrecr (uptr, ct_xb, &t, CT_MAXFR);
- else sim_tape_rdrecf (uptr, ct_xb, &t, CT_MAXFR);
+ (void)sim_tape_rdrecr (uptr, ct_xb, &t, CT_MAXFR);
+ else
+ (void)sim_tape_rdrecf (uptr, ct_xb, &t, CT_MAXFR);
}
}
else uptr->UST = 0;
ct_bptr = 0; /* init buffer */
ct_blnt = 0;
Index: src/SIMH/PDP8/pdp8_mt.c
==================================================================
--- src/SIMH/PDP8/pdp8_mt.c
+++ src/SIMH/PDP8/pdp8_mt.c
@@ -273,11 +273,11 @@
mt_updcsta (uptr); /* update status */
return 0;
}
uptr->USTAT = uptr->USTAT & STA_WLK; /* clear status */
if (f == FN_UNLOAD) { /* unload? */
- detach_unit (uptr); /* set offline */
+ sim_tape_detach (uptr); /* set offline */
uptr->USTAT = STA_REW | STA_REM; /* rewinding, off */
mt_set_done (); /* set done */
}
else if (f == FN_REWIND) { /* rewind */
uptr->USTAT = uptr->USTAT | STA_REW; /* rewinding */
Index: src/SIMH/scp.c
==================================================================
--- src/SIMH/scp.c
+++ src/SIMH/scp.c
@@ -299,11 +299,11 @@
#define SCH_EE 4
#define SCH_NE 5
#define SCH_GE 6
#define SCH_LE 7
-#define MAX_DO_NEST_LVL 20 /* DO cmd nesting level */
+#define MAX_DO_NEST_LVL 20 /* DO cmd nesting level limit */
#define SRBSIZ 1024 /* save/restore buffer */
#define SIM_BRK_INILNT 4096 /* bpt tbl length */
#define SIM_BRK_ALLTYP 0xFFFFFFFB
#define UPDATE_SIM_TIME \
if (1) { \
@@ -418,11 +418,11 @@
int32 a_event_time;
do { /* Grab current queue */
q = AIO_QUEUE_VAL;
} while (q != AIO_QUEUE_SET(QUEUE_LIST_END, q));
while (q != QUEUE_LIST_END) { /* List !Empty */
- sim_debug (SIM_DBG_AIO_QUEUE, &sim_scp_dev, "Migrating Asynch event for %s after %d instructions\n", sim_uname(q), q->a_event_time);
+ sim_debug (SIM_DBG_AIO_QUEUE, &sim_scp_dev, "Migrating Asynch event for %s after %d %s\n", sim_uname(q), q->a_event_time, sim_vm_interval_units);
++migrated;
uptr = q;
q = q->a_next;
uptr->a_next = NULL; /* hygiene */
if (uptr->a_activate_call != &sim_activate_notbefore) {
@@ -446,11 +446,11 @@
}
void sim_aio_activate (ACTIVATE_API caller, UNIT *uptr, int32 event_time)
{
AIO_ILOCK;
-sim_debug (SIM_DBG_AIO_QUEUE, &sim_scp_dev, "Queueing Asynch event for %s after %d instructions\n", sim_uname(uptr), event_time);
+sim_debug (SIM_DBG_AIO_QUEUE, &sim_scp_dev, "Queueing Asynch event for %s after %d %s\n", sim_uname(uptr), event_time, sim_vm_interval_units);
if (uptr->a_next) {
uptr->a_activate_call = sim_activate_abs;
}
else {
UNIT *q;
@@ -462,11 +462,11 @@
} while (q != AIO_QUEUE_SET(uptr, q));
}
AIO_IUNLOCK;
sim_asynch_check = 0; /* try to force check */
if (sim_idle_wait) {
- sim_debug (TIMER_DBG_IDLE, &sim_timer_dev, "waking due to event on %s after %d instructions\n", sim_uname(uptr), event_time);
+ sim_debug (TIMER_DBG_IDLE, &sim_timer_dev, "waking due to event on %s after %d %s\n", sim_uname(uptr), event_time, sim_vm_interval_units);
pthread_cond_signal (&sim_asynch_wake);
}
}
#else
t_bool sim_asynch_enabled = FALSE;
@@ -483,19 +483,24 @@
void (*sim_vm_fprint_addr) (FILE *st, DEVICE *dptr, t_addr addr) = NULL;
t_addr (*sim_vm_parse_addr) (DEVICE *dptr, CONST char *cptr, CONST char **tptr) = NULL;
t_value (*sim_vm_pc_value) (void) = NULL;
t_bool (*sim_vm_is_subroutine_call) (t_addr **ret_addrs) = NULL;
t_bool (*sim_vm_fprint_stopped) (FILE *st, t_stat reason) = NULL;
+const char *sim_vm_release;
+const char *sim_vm_release_message;
+const char **sim_clock_precalibrate_commands = NULL;
+
/* Prototypes */
/* Set and show command processors */
t_stat set_dev_radix (DEVICE *dptr, UNIT *uptr, int32 flag, CONST char *cptr);
t_stat set_dev_enbdis (DEVICE *dptr, UNIT *uptr, int32 flag, CONST char *cptr);
t_stat set_dev_debug (DEVICE *dptr, UNIT *uptr, int32 flag, CONST char *cptr);
t_stat set_unit_enbdis (DEVICE *dptr, UNIT *uptr, int32 flag, CONST char *cptr);
+t_stat set_unit_append (DEVICE *dptr, UNIT *uptr, int32 flag, CONST char *cptr);
t_stat ssh_break (FILE *st, const char *cptr, int32 flg);
t_stat show_cmd_fi (FILE *ofile, int32 flag, CONST char *cptr);
t_stat show_config (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, CONST char *cptr);
t_stat show_queue (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, CONST char *cptr);
t_stat show_time (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, CONST char *cptr);
@@ -509,10 +514,12 @@
t_stat show_dev_show_commands (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, CONST char *cptr);
t_stat show_version (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, CONST char *cptr);
t_stat show_default (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, CONST char *cptr);
t_stat show_break (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, CONST char *cptr);
t_stat show_on (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, CONST char *cptr);
+t_stat show_do (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, CONST char *cptr);
+t_stat show_runlimit (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, CONST char *cptr);
t_stat sim_show_send (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, CONST char *cptr);
t_stat sim_show_expect (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, CONST char *cptr);
t_stat show_device (FILE *st, DEVICE *dptr, int32 flag);
t_stat show_unit (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag);
t_stat show_all_mods (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flg, int32 *toks);
@@ -574,10 +581,11 @@
t_stat ex_addr (FILE *ofile, int32 flag, t_addr addr, DEVICE *dptr, UNIT *uptr);
t_stat dep_addr (int32 flag, const char *cptr, t_addr addr, DEVICE *dptr,
UNIT *uptr, int32 dfltinc);
void fprint_fields (FILE *stream, t_value before, t_value after, BITFIELD* bitdefs);
t_stat step_svc (UNIT *ptr);
+t_stat runlimit_svc (UNIT *ptr);
t_stat expect_svc (UNIT *ptr);
t_stat flush_svc (UNIT *ptr);
t_stat shift_args (char *do_arg[], size_t arg_count);
t_stat set_on (int32 flag, CONST char *cptr);
t_stat set_verify (int32 flag, CONST char *cptr);
@@ -586,20 +594,24 @@
t_stat set_asynch (int32 flag, CONST char *cptr);
t_stat sim_show_asynch (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, CONST char *cptr);
t_stat do_cmd_label (int32 flag, CONST char *cptr, CONST char *label);
void int_handler (int signal);
t_stat set_prompt (int32 flag, CONST char *cptr);
+t_stat set_runlimit (int32 flag, CONST char *cptr);
t_stat sim_set_asynch (int32 flag, CONST char *cptr);
static const char *_get_dbg_verb (uint32 dbits, DEVICE* dptr, UNIT *uptr);
-static t_stat sim_library_unit_tests (void);
+static t_stat sim_sanity_check_register_declarations (void);
static t_stat _sim_debug_flush (void);
/* Global data */
+const char *sim_prog_name = NULL; /* pointer to the executable name */
DEVICE *sim_dflt_dev = NULL;
UNIT *sim_clock_queue = QUEUE_LIST_END;
int32 sim_interval = 0;
+const char *sim_vm_interval_units = "instructions"; /* Simulator can change to "cycles" as needed */
+const char *sim_vm_step_unit = "instruction"; /* Simulator can change */
int32 sim_switches = 0;
int32 sim_switch_number = 0;
FILE *sim_ofile = NULL;
TMLN *sim_oline = NULL;
MEMFILE *sim_mfile = NULL;
@@ -624,10 +636,16 @@
int32 sim_brk_ent = 0;
int32 sim_brk_lnt = 0;
int32 sim_brk_ins = 0;
int32 sim_quiet = 0;
int32 sim_step = 0;
+int32 sim_runlimit = 0;
+int32 sim_runlimit_initial = 0;
+double sim_runlimit_d = 0.0;
+double sim_runlimit_d_initial = 0.0;
+int32 sim_runlimit_switches = 0;
+t_bool sim_runlimit_enabled = FALSE;
char *sim_sub_instr = NULL; /* Copy of pre-substitution buffer contents */
char *sim_sub_instr_buf = NULL; /* Buffer address that substitutions were saved in */
size_t sim_sub_instr_size = 0; /* substitution buffer size */
size_t *sim_sub_instr_off = NULL; /* offsets in substitution buffer where original data started */
static double sim_time;
@@ -689,10 +707,35 @@
1, 0, 0, 0, 0, 0,
NULL, NULL, NULL, NULL, NULL, NULL,
NULL, DEV_NOSAVE, 0,
NULL, NULL, NULL, NULL, NULL, NULL,
sim_int_step_description};
+
+static const char *sim_int_runlimit_description (DEVICE *dptr)
+{
+return "Run time limit facility";
+}
+
+static t_stat sim_int_runlimit_reset (DEVICE *dptr)
+{
+if (sim_runlimit_enabled) {
+ if (sim_runlimit_switches & SWMASK ('T'))
+ return sim_activate_after_d (dptr->units, sim_runlimit_d);
+ else
+ return sim_activate (dptr->units, sim_runlimit);
+ }
+return SCPE_OK;
+}
+
+static UNIT sim_runlimit_unit = { UDATA (&runlimit_svc, UNIT_IDLE, 0) };
+DEVICE sim_runlimit_dev = {
+ "INT-RUNLIMIT", &sim_runlimit_unit, NULL, NULL,
+ 1, 0, 0, 0, 0, 0,
+ NULL, NULL, &sim_int_runlimit_reset, NULL, NULL, NULL,
+ NULL, DEV_NOSAVE, 0,
+ NULL, NULL, NULL, NULL, NULL, NULL,
+ sim_int_runlimit_description};
static const char *sim_int_expect_description (DEVICE *dptr)
{
return "Expect facility";
}
@@ -742,59 +785,62 @@
const char save_ver30[] = "V3.0";
const struct scp_error {
const char *code;
const char *message;
} scp_errors[1+SCPE_MAX_ERR-SCPE_BASE] =
- {{"NXM", "Address space exceeded"},
- {"UNATT", "Unit not attached"},
- {"IOERR", "I/O error"},
- {"CSUM", "Checksum error"},
- {"FMT", "Format error"},
- {"NOATT", "Unit not attachable"},
- {"OPENERR", "File open error"},
- {"MEM", "Memory exhausted"},
- {"ARG", "Invalid argument"},
- {"STEP", "Step expired"},
- {"UNK", "Unknown command"},
- {"RO", "Read only argument"},
- {"INCOMP", "Command not completed"},
- {"STOP", "Simulation stopped"},
- {"EXIT", "Goodbye"},
- {"TTIERR", "Console input I/O error"},
- {"TTOERR", "Console output I/O error"},
- {"EOF", "End of file"},
- {"REL", "Relocation error"},
- {"NOPARAM", "No settable parameters"},
- {"ALATT", "Unit already attached"},
- {"TIMER", "Hardware timer error"},
- {"SIGERR", "Signal handler setup error"},
- {"TTYERR", "Console terminal setup error"},
- {"SUB", "Subscript out of range"},
- {"NOFNC", "Command not allowed"},
- {"UDIS", "Unit disabled"},
- {"NORO", "Read only operation not allowed"},
- {"INVSW", "Invalid switch"},
- {"MISVAL", "Missing value"},
- {"2FARG", "Too few arguments"},
- {"2MARG", "Too many arguments"},
- {"NXDEV", "Non-existent device"},
- {"NXUN", "Non-existent unit"},
- {"NXREG", "Non-existent register"},
- {"NXPAR", "Non-existent parameter"},
- {"NEST", "Nested DO command limit exceeded"},
- {"IERR", "Internal error"},
- {"MTRLNT", "Invalid magtape record length"},
- {"LOST", "Console Telnet connection lost"},
- {"TTMO", "Console Telnet connection timed out"},
- {"STALL", "Console Telnet output stall"},
- {"AFAIL", "Assertion failed"},
- {"INVREM", "Invalid remote console command"},
- {"EXPECT", "Expect matched"},
- {"AMBREG", "Ambiguous register name"},
- {"REMOTE", "remote console command"},
- {"INVEXPR", "invalid expression"},
- {"SIGTERM", "SIGTERM received"},
+ {{"NXM", "Address space exceeded"},
+ {"UNATT", "Unit not attached"},
+ {"IOERR", "I/O error"},
+ {"CSUM", "Checksum error"},
+ {"FMT", "Format error"},
+ {"NOATT", "Unit not attachable"},
+ {"OPENERR", "File open error"},
+ {"MEM", "Memory exhausted"},
+ {"ARG", "Invalid argument"},
+ {"STEP", "Step expired"},
+ {"UNK", "Unknown command"},
+ {"RO", "Read only argument"},
+ {"INCOMP", "Command not completed"},
+ {"STOP", "Simulation stopped"},
+ {"EXIT", "Goodbye"},
+ {"TTIERR", "Console input I/O error"},
+ {"TTOERR", "Console output I/O error"},
+ {"EOF", "End of file"},
+ {"REL", "Relocation error"},
+ {"NOPARAM", "No settable parameters"},
+ {"ALATT", "Unit already attached"},
+ {"TIMER", "Hardware timer error"},
+ {"SIGERR", "Signal handler setup error"},
+ {"TTYERR", "Console terminal setup error"},
+ {"SUB", "Subscript out of range"},
+ {"NOFNC", "Command not allowed"},
+ {"UDIS", "Unit disabled"},
+ {"NORO", "Read only operation not allowed"},
+ {"INVSW", "Invalid switch"},
+ {"MISVAL", "Missing value"},
+ {"2FARG", "Too few arguments"},
+ {"2MARG", "Too many arguments"},
+ {"NXDEV", "Non-existent device"},
+ {"NXUN", "Non-existent unit"},
+ {"NXREG", "Non-existent register"},
+ {"NXPAR", "Non-existent parameter"},
+ {"NEST", "Nested DO command limit exceeded"},
+ {"IERR", "Internal error"},
+ {"MTRLNT", "Invalid magtape record length"},
+ {"LOST", "Console Telnet connection lost"},
+ {"TTMO", "Console Telnet connection timed out"},
+ {"STALL", "Console Telnet output stall"},
+ {"AFAIL", "Assertion failed"},
+ {"INVREM", "Invalid remote console command"},
+ {"EXPECT", "Expect matched"},
+ {"AMBREG", "Ambiguous register name"},
+ {"REMOTE", "remote console command"},
+ {"INVEXPR", "invalid expression"},
+ {"SIGTERM", "SIGTERM received"},
+ {"FSSIZE", "File System size larger than disk size"},
+ {"RUNTIME", "Run time limit exhausted"},
+ {"INCOMPDSK", "Incompatible Disk Container"},
};
const size_t size_map[] = { sizeof (int8),
sizeof (int8), sizeof (int16), sizeof (int32), sizeof (int32)
#if defined (USE_INT64)
@@ -823,11 +869,13 @@
0x1FFFFFFFFFFFFFFF, 0x3FFFFFFFFFFFFFFF,
0x7FFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF
#endif
};
-static const char simh_help[] =
+static char *simh_help = ""; /* First invocation of HELP command appends the help pieces */
+
+static const char simh_help1[] =
/***************** 80 character line width template *************************/
"1Commands\n"
#define HLP_RESET "*Commands Resetting Devices"
/***************** 80 character line width template *************************/
"2Resetting Devices\n"
@@ -1021,15 +1069,15 @@
" The CONT command (abbreviated CO) does not reset devices and resumes\n"
" execution at the current PC.\n"
#define HLP_STEP "*Commands Running_A_Simulated_Program STEP"
"3STEP\n"
" The STEP command (abbreviated S) resumes execution at the current PC for\n"
- " the number of instructions given by its argument. If no argument is\n"
- " supplied, one instruction is executed.\n"
+ " the number of %Is given by its argument. If no argument is\n"
+ " supplied, one %I is executed.\n"
"4Switches\n"
" If the STEP command is invoked with the -T switch, the step command will\n"
- " cause execution to run for microseconds rather than instructions.\n"
+ " cause execution to run for microseconds rather than %I.\n"
#define HLP_NEXT "*Commands Running_A_Simulated_Program NEXT"
"3NEXT\n"
" The NEXT command (abbreviated N) resumes execution at the current PC for\n"
" one instruction, attempting to execute through a subroutine calls.\n"
" If the next instruction to be executed is not a subroutine call,\n"
@@ -1099,10 +1147,29 @@
"4Debug\n"
" The DEBUG snd NODEBUG commands are aliases for the \"SET DEBUG\" and\n"
" \"SET NODEBUG\" commands. Additionally, support is provided that is\n"
" equivalent to the \"SET DEBUG=opt1{;opt2}\" and\n"
" \"SET NODEBUG=opt1{;opt2}\" commands.\n\n"
+#define HLP_RUNLIMIT "*Commands Stopping_The_Simulator User_Specified_Stop_Conditions RUNLIMIT"
+ "4RUNLIMIT\n"
+ " A simulator user may want to limit the maximum execution time that a\n"
+ " simulator may run for. This might be appropriate to limit a runaway\n"
+ " diagnostic which didn't achieve explicit success or failure within\n"
+ " some user specified time. The RUNLIMIT command provides ways to\n"
+ " limit execution.\n\n"
+ "++RUNLIMIT n {%C|MICROSECONDS|SECONDS|MINUTES|HOURS}\n"
+ "++NORUNLIMIT\n\n"
+ " Equivalently:\n\n"
+ "++SET RUNLIMIT n {CYCLES|MICROSECONDS|SECONDS|MINUTES|HOURS}\n"
+ "++SET NORUNLIMIT\n\n"
+ " The run limit state can be examined with:\n\n"
+ "++SHOW RUNLIMIT\n\n"
+ " If the units of the run limit are not specified, the default units are\n"
+ " %C. Once an execution run limit has beenn reached, any subsequent\n"
+ " GO, RUN, CONTINUE, STEP or BOOT commands will cause the simulator to\n"
+ " exit. A previously defined RUNLIMIT can be cleared with the NORUNLIMIT\n"
+ " command or the establishment of a new run limit.\n"
/***************** 80 character line width template *************************/
"2Connecting and Disconnecting Devices\n"
" Except for main memory and network devices, units are simulated as\n"
" unstructured binary disk files in the host file system. Before using a\n"
" simulated unit, the user must specify the file to be accessed by that unit.\n"
@@ -1125,10 +1192,15 @@
" to open the file read only. If the file does not exist, or the unit does\n"
" not support read only operation, an error occurs. Input-only devices, such\n"
" as paper-tape readers, and devices with write lock switches, such as disks\n"
" and tapes, support read only operation; other devices do not. If a file is\n"
" attached read only, its contents can be examined but not modified.\n"
+ "5-a\n"
+ " If the -a switch is specified, and the device being attached is a\n"
+ " sequential output only device (like a line printer, paper tape punch,\n"
+ " etc.), the file being attached will be opened in append mode thus adding\n"
+ " to any existing file data beyond what may have already been there.\n"
"5-q\n"
" If the -q switch is specified when creating a new file (-n) or opening one\n"
" read only (-r), any messages announcing these facts will be suppressed.\n"
"5-f\n"
" For simulated magnetic tapes, the ATTACH command can specify the format of\n"
@@ -1210,10 +1282,27 @@
"3COPY\n"
"++COPY sfile dfile copies a file\n"
#define HLP_CP "*Commands Copying_Files CP"
"3CP\n"
"++CP sfile dfile copies a file\n"
+ "2Renaming Files\n"
+#define HLP_RENAME "*Commands Renaming_Files RENAME"
+ "3RENAME\n"
+ "++RENAME origname newname renames a file\n"
+#define HLP_MOVE "*Commands Renaming_Files MOVE"
+ "3MOVE\n"
+ "++MOVE origname newname renames a file\n"
+ "+or\n"
+ "++MV origname newname renames a file\n"
+ "2Creating Directories\n"
+#define HLP_MKDIR "*Commands Creating_Directories MKDIR"
+ "3MKDIR\n"
+ "++MKDIR path creates a directory\n"
+ "2Deleting Directories\n"
+#define HLP_RMDIR "*Commands Deleting_Directories RMDIR"
+ "3RMDIR\n"
+ "++RMDIR path deleting a directory\n"
#define HLP_SET "*Commands SET"
"2SET\n"
/***************** 80 character line width template *************************/
#define HLP_SET_CONSOLE "*Commands SET CONSOLE"
"3Console\n"
@@ -1279,11 +1368,11 @@
"+SET DEBUG debug_file specify the debug destination\n"
"++++++++ (STDOUT,STDERR,LOG or filename)\n"
"+SET NODEBUG disables any currently active debug output\n"
"4Switches\n"
" Debug message output contains a timestamp which indicates the number of\n"
- " simulated instructions which have been executed prior to the debug event.\n\n"
+ " simulated %C which have been executed prior to the debug event.\n\n"
" Debug message output can be enhanced to contain additional, potentially\n"
" useful information.\n"
"5-T\n"
" The -T switch causes debug output to contain a time of day displayed\n"
" as hh:mm:ss.msec.\n"
@@ -1323,21 +1412,21 @@
/***************** 80 character line width template *************************/
#define HLP_SET_THROTTLE "*Commands SET Throttle"
"3Throttle\n"
" Simulator instruction execution rate can be controlled by specifying\n"
" one of the following throttle commands:\n\n"
- "+SET THROTTLE xM execute x million instructions per second\n"
- "+SET THROTTLE xK execute x thousand instructions per second\n"
+ "+SET THROTTLE xM execute x million %C per second\n"
+ "+SET THROTTLE xK execute x thousand %C per second\n"
"+SET THROTTLE x%% occupy x percent of the host capacity\n"
"++++++++executing instructions\n"
"+SET THROTTLE x/t sleep for t milliseconds after executing x\n"
- "++++++++instructions\n\n"
+ "++++++++%C\n\n"
"+SET NOTHROTTLE set simulation rate to maximum\n\n"
" Throttling is only available on host systems that implement a precision\n"
" real-time delay function.\n\n"
" xM, xK and x%% modes require the simulator to execute sufficient\n"
- " instructions to actually calibrate the desired execution rate relative\n"
+ " %C to actually calibrate the desired execution rate relative\n"
" to wall clock time. Very short running programs may complete before\n"
" calibration completes and therefore before the simulated execution rate\n"
" can match the desired rate.\n\n"
" The SET NOTHROTTLE command turns off throttling. The SHOW THROTTLE\n"
" command shows the current settings for throttling and the calibration\n"
@@ -1355,11 +1444,11 @@
#endif
"+SET CLOCK nocatchup disable catchup clock ticks\n"
"+SET CLOCK catchup enable catchup clock ticks\n"
"+SET CLOCK calib=n%% specify idle calibration skip %%\n"
"+SET CLOCK calib=ALWAYS specify calibration independent of idle\n"
- "+SET CLOCK stop=n stop execution after n instructions\n\n"
+ "+SET CLOCK stop=n stop execution after n %C\n\n"
" The SET CLOCK STOP command allows execution to have a bound when\n"
" execution starts with a BOOT, NEXT or CONTINUE command.\n"
#define HLP_SET_ASYNCH "*Commands SET Asynch"
"3Asynch\n"
"+SET ASYNCH enable asynchronous I/O\n"
@@ -1455,12 +1544,13 @@
"+SET arg{,arg...} set device parameters (see show modifiers)\n"
"+SET ENABLED enable unit\n"
"+SET DISABLED disable unit\n"
"+SET arg{,arg...} set unit parameters (see show modifiers)\n"
"+HELP SET displays the device specific set commands\n"
- "++++++++ available\n"
- /***************** 80 character line width template *************************/
+ "++++++++ available\n";
+static const char simh_help2[] =
+ /***************** 80 character line width template *************************/
#define HLP_SHOW "*Commands SHOW"
"2SHOW\n"
"+sh{ow} {-c} br{eak} show breakpoints\n"
"+sh{ow} con{figuration} show configuration\n"
"+sh{ow} cons{ole} {arg} show console options\n"
@@ -1490,10 +1580,12 @@
"+sh{ow} video show video capabilities\n"
#endif
"+sh{ow} clocks show calibrated timer information\n"
"+sh{ow} throttle show throttle info\n"
"+sh{ow} on show on condition actions\n"
+ "+sh{ow} do show do nesting state\n"
+ "+sh{ow} runlimit show execution limit states\n"
"+h{elp} show displays the device specific show commands\n"
"++++++++ available\n"
#define HLP_SHOW_CONFIG "*Commands SHOW"
#define HLP_SHOW_DEVICES "*Commands SHOW"
#define HLP_SHOW_FEATURES "*Commands SHOW"
@@ -1515,10 +1607,12 @@
#define HLP_SHOW_SERIAL "*Commands SHOW"
#define HLP_SHOW_MULTIPLEXER "*Commands SHOW"
#define HLP_SHOW_VIDEO "*Commands SHOW"
#define HLP_SHOW_CLOCKS "*Commands SHOW"
#define HLP_SHOW_ON "*Commands SHOW"
+#define HLP_SHOW_DO "*Commands SHOW"
+#define HLP_SHOW_RUNLIMIT "*Commands SHOW"
#define HLP_SHOW_SEND "*Commands SHOW"
#define HLP_SHOW_EXPECT "*Commands SHOW"
#define HLP_HELP "*Commands HELP"
/***************** 80 character line width template *************************/
"2HELP\n"
@@ -1667,10 +1761,12 @@
"++%%~I%% - expands the value of %%I%% removing any surrounding quotes (\")\n"
"++%%~fI%% - expands the value of %%I%% to a fully qualified path name\n"
"++%%~pI%% - expands the value of %%I%% to a path only\n"
"++%%~nI%% - expands the value of %%I%% to a file name only\n"
"++%%~xI%% - expands the value of %%I%% to a file extension only\n\n"
+ "++%%~tI%% - expands the value of %%I%% to date/time of file\n\n"
+ "++%%~zI%% - expands the value of %%I%% to size of file\n\n"
" The modifiers can be combined to get compound results:\n\n"
"++%%~pnI%% - expands the value of %%I%% to a path and name only\n"
"++%%~nxI%% - expands the value of %%I%% to a file name and extension only\n\n"
" In the above example above %%I%% can be replaced by other\n"
" environment variables or numeric parameters to a DO command\n"
@@ -1695,10 +1791,14 @@
" Labels are lines in a command file which the first non whitespace\n"
" character is a \":\". The target of a goto is the first matching label\n"
" in the current do command file which is encountered. Since labels\n"
" don't do anything else besides being the targets of goto's, they could\n"
" also be used to provide comments in do command files.\n\n"
+ "++GOTO :EOF\n\n"
+ " The target label of :EOF is explicitly defined to mean the end of the\n"
+ " DO command file. This will cause the execution to return from the current\n"
+ " command file.\n\n"
"4Examples\n\n"
"++:: This is a comment\n"
"++echo Some Message to Output\n"
"++:Target\n"
"++:: This is a comment\n"
@@ -1805,10 +1905,12 @@
" Assertion failed\n"
"5 INVREM\n"
" Invalid remote console command\n"
"5 AMBREG\n"
" Ambiguous register\n"
+ "5 RUNTIME\n"
+ " Run time limit exhausted\n"
#define HLP_SHIFT "*Commands Executing_Command_Files SHIFT"
"3SHIFT\n"
"++shift shift the command file's positional parameters\n"
#define HLP_CALL "*Commands Executing_Command_Files CALL"
"3CALL\n"
@@ -1980,20 +2082,24 @@
"++SEND DELAY=n\n\n"
" which will set the default delay value for subsequent SEND commands\n"
" which don't specify an explicit DELAY parameter along with a string\n"
" If a SEND command is processed and no DELAY value has been specified,\n"
" the default value of the delay parameter is 1000.\n"
+ " The value n can be specified with a suffix of k or m which indicates\n"
+ " a multiplier of 1000 or 1000000 respectively\n"
/***************** 80 character line width template *************************/
"4After\n"
- " Specifies an integer (>=0) representing a minimal number of instructions\n"
+ " Specifies an integer (>=0) representing a minimal number of %C\n"
" which must execute before the first character in the string is sent.\n"
" The after parameter value can be set by itself with:\n\n"
"++SEND AFTER=n\n\n"
" which will set the default after value for subsequent SEND commands\n"
" which don't specify an explicit AFTER parameter along with a string\n"
" If a SEND command is processed and no AFTER value has been specified,\n"
" the default value of the delay parameter is the DELAY parameter value.\n"
+ " The value n can be specified with a suffix of k or m which indicates\n"
+ " a multiplier of 1000 or 1000000 respectively\n"
"4Escaping String Data\n"
" The following character escapes are explicitly supported:\n"
"++\\r Sends the ASCII Carriage Return character (Decimal value 13)\n"
"++\\n Sends the ASCII Linefeed character (Decimal value 10)\n"
"++\\f Sends the ASCII Formfeed character (Decimal value 12)\n"
@@ -2011,11 +2117,11 @@
"++\\xh{h} where each h is a hex digit (0-9A-Fa-f)\n"
"4Switches\n"
" Switches can be used to influence the behavior of SEND commands\n\n"
"5-t\n"
" The -t switch indicates that the Delay and After values are in\n"
- " units of microseconds rather than instructions.\n"
+ " units of microseconds rather than %C.\n"
/***************** 80 character line width template *************************/
#define HLP_EXPECT "*Commands Executing_Command_Files Reacting_To_Console_Output"
/***************** 80 character line width template *************************/
"3Reacting To Console Output\n"
" The EXPECT command provides a way to stop execution and take actions\n"
@@ -2039,11 +2145,13 @@
" Once data has matched any expect rule, that data is no longer eligible\n"
" to match other expect rules which may already be defined.\n"
" Data which is output prior to the definition of an expect rule is not\n"
" eligible to be matched against.\n\n"
" The NOEXPECT command removes a previously defined EXPECT command for the\n"
- " console or a specific multiplexer line.\n\n"
+ " console or a specific multiplexer line. A NOEXPECT command, without a\n"
+ " specific mention of a particular EXPECT match string, will remove all\n"
+ " currently defined EXPECT match rules.\n\n"
" The SHOW EXPECT command displays all of the pending EXPECT state for\n"
" the console or a specific multiplexer line.\n"
/***************** 80 character line width template *************************/
"4Switches\n"
" Switches can be used to influence the behavior of EXPECT rules\n\n"
@@ -2058,19 +2166,15 @@
"5-r\n"
" If an expect rule is defined with the -r switch, the string is interpreted\n"
" as a regular expression applied to the output data stream. This regular\n"
" expression may contain parentheses delimited sub-groups.\n\n"
/***************** 80 character line width template *************************/
-#if defined (HAVE_PCREPOSIX_H)
+#if defined (HAVE_PCRE_H)
" The syntax of the regular expressions available are those supported by\n"
" the Perl Compatible Regular Expression package (aka PCRE). As the name\n"
" implies, the syntax is generally the same as Perl regular expressions.\n"
" See http://perldoc.perl.org/perlre.html for more details\n"
-#elif defined (HAVE_REGEX_H)
- " The syntax of the regular expressions available are those supported by\n"
- " your local system's Regular Expression library using the Extended POSIX\n"
- " Regular Expressiona\n"
#else
" Regular expression support is not currently available on your environment.\n"
" This simulator could use regular expression support provided by the\n"
" Perl Compatible Regular Expression (PCRE) package if it was available\n"
" when you simulator was compiled.\n"
@@ -2079,11 +2183,11 @@
" If a regular expression expect rule is defined with the -i switch,\n"
" character matching for that expression will be case independent.\n"
" The -i switch is only valid for regular expression expect rules.\n"
"5-t\n"
" The -t switch indicates that the value specified by the HaltAfter\n"
- " parameter are in units of microseconds rather than instructions.\n"
+ " parameter are in units of microseconds rather than %C.\n"
"4Determining Which Output Matched\n"
" When an expect rule matches data in the output stream, the rule which\n"
" matched is recorded in the environment variable _EXPECT_MATCH_PATTERN.\n"
" If the expect rule was a regular expression rule, then the environment\n"
" variable _EXPECT_MATCH_GROUP_0 is set to the whole string which matched\n"
@@ -2109,11 +2213,11 @@
" as well as octal character values of the form:\n"
"++\\n{n{n}} where each n is an octal digit (0-7)\n"
" and hext character values of the form:\n"
"++\\xh{h} where each h is a hex digit (0-9A-Fa-f)\n"
"4HaltAfter\n"
- " Specifies the number of instructions which should be executed before\n"
+ " Specifies the number of %C which should be executed before\n"
" simulator instruction execution should stop. The default is to stop\n"
" executing instructions immediately (i.e. HALTAFTER=0).\n"
" The default HaltAfter delay, once set, persists for all expect behaviors\n"
" for that device.\n"
" The default HaltAfter parameter value can be set by itself with:\n\n"
@@ -2123,10 +2227,12 @@
" To avoid potentially unpredictable system hehavior that will happen\n"
" if multiple expect rules are in effect and a haltafter value is large\n"
" enough for more than one expect rule to match before an earlier haltafter\n"
" delay has expired, only a single EXPECT rule can be defined if a non-zero\n"
" HaltAfter parameter has been set.\n"
+ " The value n can be specified with a suffix of k or m which indicates\n"
+ " a multiplier of 1000 or 1000000 respectively\n"
/***************** 80 character line width template *************************/
#define HLP_SLEEP "*Commands Executing_Command_Files Pausing_Command_Execution"
"3Pausing Command Execution\n"
" A simulator command file may wait for a specific period of time with the\n\n"
"++SLEEP NUMBER[SUFFIX]...\n\n"
@@ -2300,11 +2406,11 @@
#define HLP_SCREENSHOT "*Commands Screenshot_Video_Window"
"2Screenshot Video Window\n"
" Simulators with Video devices display the simulated video in a window\n"
" on the local system. The contents of that display can be saved in a\n"
" file with the SCREENSHOT command:\n\n"
- " +SCREENSHOT screenshotfile\n\n"
+ "++SCREENSHOT screenshotfile\n\n"
#if defined(HAVE_LIBPNG)
" which will create a screen shot file called screenshotfile.png\n"
#else
" which will create a screen shot file called screenshotfile.bmp\n"
#endif
@@ -2316,11 +2422,32 @@
"++! execute local host command\n"
" If no operating system command is provided, the simulator attempts to\n"
" launch the host operating system's command shell.\n"
" The exit status from the command which was executed is set as the command\n"
" completion status for the ! command. This may influence any enabled ON\n"
- " condition traps\n";
+ " condition traps\n"
+#define HLP_TESTLIB "*Commands Testing_Device_Libraries"
+ "2Testing Device Libraries\n"
+ " A simulator developer may need to invoke the simh internal device library\n"
+ " test routines that exercise the various libraries used by different devices.\n\n"
+ " There are library test routines for devices which use:\n\n"
+ "++sim_disk - Disk devices\n"
+ "++sim_tape - Tape devices\n"
+ "++sim_ether - Ethernet devices\n"
+ "++sim_card - Card Reader/Punch Devices\n"
+ "++sim_tmxr - Terminal Multiplexor Devices\n\n"
+ " The TESTLIB command by itself will invoke library tests for all devices in the\n"
+ " current simulator.\n\n"
+ " The library tests for a specific device can be invoked by specifying the device\n"
+ " name as an argument to the TESTLIB command:\n\n"
+ "++TESTLIB {device} test a specific or all devices\n\n"
+ /***************** 80 character line width template *************************/
+ "3Switches\n"
+ " Switches can be used to influence the behavior of the TESTLIB command\n\n"
+ "4-d\n"
+ " Many tests are capable of producing various amounts of debug output\n"
+ " during their execution. The -d switch enables that output\n";
static CTAB cmd_table[] = {
{ "RESET", &reset_cmd, 0, HLP_RESET, NULL, NULL },
{ "EXAMINE", &exdep_cmd, EX_E, HLP_EXAMINE, NULL, NULL },
@@ -2358,10 +2485,15 @@
{ "CAT", &type_cmd, 0, HLP_CAT, NULL, NULL },
{ "DELETE", &delete_cmd, 0, HLP_DELETE, NULL, NULL },
{ "RM", &delete_cmd, 0, HLP_RM, NULL, NULL },
{ "COPY", ©_cmd, 0, HLP_COPY, NULL, NULL },
{ "CP", ©_cmd, 0, HLP_CP, NULL, NULL },
+ { "RENAME", &rename_cmd, 0, HLP_RENAME, NULL, NULL },
+ { "MOVE", &rename_cmd, 0, HLP_MOVE, NULL, NULL },
+ { "MV", &rename_cmd, 0, HLP_MOVE, NULL, NULL },
+ { "MKDIR", &mkdir_cmd, 0, HLP_MKDIR, NULL, NULL },
+ { "RMDIR", &rmdir_cmd, 0, HLP_RMDIR, NULL, NULL },
{ "SET", &set_cmd, 0, HLP_SET, NULL, NULL },
{ "SHOW", &show_cmd, 0, HLP_SHOW, NULL, NULL },
{ "DO", &do_cmd, 1, HLP_DO, NULL, NULL },
{ "GOTO", &goto_cmd, 1, HLP_GOTO, NULL, NULL },
{ "RETURN", &return_cmd, 0, HLP_RETURN, NULL, NULL },
@@ -2383,10 +2515,13 @@
{ "!", &spawn_cmd, 0, HLP_SPAWN, NULL, NULL },
{ "HELP", &help_cmd, 0, HLP_HELP, NULL, NULL },
#if defined(USE_SIM_VIDEO)
{ "SCREENSHOT", &screenshot_cmd,0, HLP_SCREENSHOT, NULL, NULL },
#endif
+ { "RUNLIMIT", &runlimit_cmd, 1, HLP_RUNLIMIT, NULL, NULL },
+ { "NORUNLIMIT", &runlimit_cmd, 0, HLP_RUNLIMIT, NULL, NULL },
+ { "TESTLIB", &test_lib_cmd, 0, HLP_TESTLIB, NULL, NULL },
{ NULL, NULL, 0, NULL, NULL, NULL }
};
static CTAB set_glob_tab[] = {
{ "CONSOLE", &sim_set_console, 0, HLP_SET_CONSOLE },
@@ -2415,10 +2550,12 @@
{ "MESSAGE", &set_message, 1, HLP_SET_MESSAGE },
{ "NOMESSAGE", &set_message, 0, HLP_SET_MESSAGE },
{ "QUIET", &set_quiet, 1, HLP_SET_QUIET },
{ "NOQUIET", &set_quiet, 0, HLP_SET_QUIET },
{ "PROMPT", &set_prompt, 0, HLP_SET_PROMPT },
+ { "RUNLIMIT", &set_runlimit, 1, HLP_RUNLIMIT },
+ { "NORUNLIMIT", &set_runlimit, 0, HLP_RUNLIMIT },
{ NULL, NULL, 0 }
};
static C1TAB set_dev_tab[] = {
{ "OCTAL", &set_dev_radix, 8 },
@@ -2427,18 +2564,22 @@
{ "BINARY", &set_dev_radix, 2 },
{ "ENABLED", &set_dev_enbdis, 1 },
{ "DISABLED", &set_dev_enbdis, 0 },
{ "DEBUG", &set_dev_debug, 1 },
{ "NODEBUG", &set_dev_debug, 0 },
+ { "APPEND", &set_unit_append, 0 },
+ { "EOF", &set_unit_append, 0 },
{ NULL, NULL, 0 }
};
static C1TAB set_unit_tab[] = {
{ "ENABLED", &set_unit_enbdis, 1 },
{ "DISABLED", &set_unit_enbdis, 0 },
{ "DEBUG", &set_dev_debug, 2+1 },
{ "NODEBUG", &set_dev_debug, 2+0 },
+ { "APPEND", &set_unit_append, 0 },
+ { "EOF", &set_unit_append, 0 },
{ NULL, NULL, 0 }
};
static SHTAB show_glob_tab[] = {
{ "CONFIGURATION", &show_config, 0, HLP_SHOW_CONFIG },
@@ -2467,11 +2608,13 @@
{ "VIDEO", &vid_show, 0, HLP_SHOW_VIDEO },
#endif
{ "CLOCKS", &sim_show_timers, 0, HLP_SHOW_CLOCKS },
{ "SEND", &sim_show_send, 0, HLP_SHOW_SEND },
{ "EXPECT", &sim_show_expect, 0, HLP_SHOW_EXPECT },
- { "ON", &show_on, 0, HLP_SHOW_ON },
+ { "ON", &show_on, -1, HLP_SHOW_ON },
+ { "DO", &show_do, 0, HLP_SHOW_DO },
+ { "RUNLIMIT", &show_runlimit, 0, HLP_SHOW_RUNLIMIT },
{ NULL, NULL, 0 }
};
static SHTAB show_dev_tab[] = {
{ "RADIX", &show_dev_radix, 0 },
@@ -2511,11 +2654,11 @@
setenv(envname, "", 1);
return 0;
}
#endif
-t_stat process_stdin_commands (t_stat stat, char *argv[]);
+t_stat process_stdin_commands (t_stat stat, char *argv[], t_bool do_called);
/* Main command loop */
int main (int argc, char *argv[])
{
@@ -2522,10 +2665,11 @@
char cbuf[4*CBUFSIZE], *cptr, *cptr2;
char nbuf[PATH_MAX + 7];
char **targv = NULL;
int32 i, sw;
t_bool lookswitch;
+t_bool register_check = FALSE;
t_stat stat = SCPE_OK;
#if defined (__MWERKS__) && defined (macintosh)
argc = ccommand (&argv);
#endif
@@ -2538,10 +2682,20 @@
set_prompt (0, "sim>"); /* start with set standard prompt */
*cbuf = 0; /* init arg buffer */
sim_switches = 0; /* init switches */
lookswitch = TRUE;
stdnul = fopen(NULL_DEVICE,"wb");
+sim_prog_name = argv [0]; /* save a pointer to the program name */
+if (argc > 1) { /* Check for special argument to invoke register test */
+ if (sim_strcasecmp (argv[1], "RegisterSanityCheck") == 0) {
+ register_check = TRUE;
+ --argc; /* Remove special argument to avoid confusion later */
+ for (i = 1; i < argc; i++)
+ argv[i] = argv[i+1];
+ argv[i+1] = NULL;
+ }
+ }
for (i = 1; i < argc; i++) { /* loop thru args */
if (argv[i] == NULL) /* paranoia */
continue;
if ((*argv[i] == '-') && lookswitch) { /* switch? */
if (get_switches (argv[i], &sw, NULL) == SW_ERROR) {
@@ -2561,75 +2715,10 @@
strlcat (cbuf, " ", sizeof (cbuf));
sprintf(&cbuf[strlen(cbuf)], "%s%s%s", strchr(argv[i], ' ') ? "\"" : "", argv[i], strchr(argv[i], ' ') ? "\"" : "");
lookswitch = FALSE; /* no more switches */
}
} /* end for */
-sim_quiet = sim_switches & SWMASK ('Q'); /* -q means quiet */
-sim_on_inherit = sim_switches & SWMASK ('O'); /* -o means inherit on state */
-
-
-sim_init_sock (); /* init socket capabilities */
-AIO_INIT; /* init Asynch I/O */
-if (sim_vm_init != NULL) /* call once only */
- (*sim_vm_init)();
-sim_finit (); /* init fio package */
-setenv ("SIM_NAME", sim_name, 1); /* Publish simulator name */
-stop_cpu = FALSE;
-sim_interval = 0;
-sim_time = sim_rtime = 0;
-noqueue_time = 0;
-sim_clock_queue = QUEUE_LIST_END;
-sim_is_running = FALSE;
-sim_log = NULL;
-if (sim_emax <= 0)
- sim_emax = 1;
-if (sim_timer_init ()) {
- fprintf (stderr, "Fatal timer initialization error\n");
- read_line_p ("Hit Return to exit: ", cbuf, sizeof (cbuf) - 1, stdin);
- return EXIT_FAILURE;
- }
-sim_register_internal_device (&sim_scp_dev);
-sim_register_internal_device (&sim_expect_dev);
-sim_register_internal_device (&sim_step_dev);
-sim_register_internal_device (&sim_flush_dev);
-
-if ((stat = sim_ttinit ()) != SCPE_OK) {
- fprintf (stderr, "Fatal terminal initialization error\n%s\n",
- sim_error_text (stat));
- read_line_p ("Hit Return to exit: ", cbuf, sizeof (cbuf) - 1, stdin);
- return EXIT_FAILURE;
- }
-if ((sim_eval = (t_value *) calloc (sim_emax, sizeof (t_value))) == NULL) {
- fprintf (stderr, "Unable to allocate examine buffer\n");
- read_line_p ("Hit Return to exit: ", cbuf, sizeof (cbuf) - 1, stdin);
- return EXIT_FAILURE;
- };
-if (sim_dflt_dev == NULL) /* if no default */
- sim_dflt_dev = sim_devices[0];
-if ((stat = reset_all_p (0)) != SCPE_OK) {
- fprintf (stderr, "Fatal simulator initialization error\n%s\n",
- sim_error_text (stat));
- read_line_p ("Hit Return to exit: ", cbuf, sizeof (cbuf) - 1, stdin);
- return EXIT_FAILURE;
- }
-if ((stat = sim_brk_init ()) != SCPE_OK) {
- fprintf (stderr, "Fatal breakpoint table initialization error\n%s\n",
- sim_error_text (stat));
- read_line_p ("Hit Return to exit: ", cbuf, sizeof (cbuf) - 1, stdin);
- return EXIT_FAILURE;
- }
-signal (SIGINT, int_handler);
-if (!sim_quiet) {
- printf ("\n");
- show_version (stdout, NULL, NULL, 0, NULL);
- }
-show_version (stdnul, NULL, NULL, 1, NULL); /* Quietly set SIM_OSTYPE */
-#if defined (HAVE_PCREPOSIX_H)
-setenv ("SIM_REGEX_TYPE", "PCREPOSIX", 1); /* Publish regex type */
-#elif defined (HAVE_REGEX_H)
-setenv ("SIM_REGEX_TYPE", "REGEX", 1); /* Publish regex type */
-#endif
if (*argv[0]) { /* sim name arg? */
char *np; /* "path.ini" */
strlcpy (nbuf, argv[0], PATH_MAX + 2); /* copy sim name */
if ((np = (char *)match_ext (nbuf, "EXE"))) /* remove .exe */
@@ -2646,11 +2735,102 @@
#ifdef PIDP8I
if (strstr (argv[0], "pidp8i-sim") == 0) use_pidp8i_extensions = 0;
else if (start_pidp8i_gpio_thread (0) != 0) exit (EXIT_FAILURE);
#endif
}
+
+sim_quiet = sim_switches & SWMASK ('Q'); /* -q means quiet */
+sim_on_inherit = sim_switches & SWMASK ('O'); /* -o means inherit on state */
+
+sim_init_sock (); /* init socket capabilities */
+AIO_INIT; /* init Asynch I/O */
+if (sim_vm_init != NULL) /* call once only */
+ (*sim_vm_init)();
+sim_finit (); /* init fio package */
+setenv ("SIM_NAME", sim_name, 1); /* Publish simulator name */
+stop_cpu = FALSE;
+sim_interval = 0;
+sim_time = sim_rtime = 0;
+noqueue_time = 0;
+sim_clock_queue = QUEUE_LIST_END;
+sim_is_running = FALSE;
+sim_log = NULL;
+if (sim_emax <= 0)
+ sim_emax = 1;
+if (sim_timer_init ()) {
+ fprintf (stderr, "Fatal timer initialization error\n");
+ if (sim_ttisatty())
+ read_line_p ("Hit Return to exit: ", cbuf, sizeof (cbuf) - 1, stdin);
+ return EXIT_FAILURE;
+ }
+sim_register_internal_device (&sim_scp_dev);
+sim_register_internal_device (&sim_expect_dev);
+sim_register_internal_device (&sim_step_dev);
+sim_register_internal_device (&sim_flush_dev);
+sim_register_internal_device (&sim_runlimit_dev);
+
+if ((stat = sim_ttinit ()) != SCPE_OK) {
+ fprintf (stderr, "Fatal terminal initialization error\n%s\n",
+ sim_error_text (stat));
+ if (sim_ttisatty())
+ read_line_p ("Hit Return to exit: ", cbuf, sizeof (cbuf) - 1, stdin);
+ return EXIT_FAILURE;
+ }
+if ((sim_eval = (t_value *) calloc (sim_emax, sizeof (t_value))) == NULL) {
+ fprintf (stderr, "Unable to allocate examine buffer\n");
+ if (sim_ttisatty())
+ read_line_p ("Hit Return to exit: ", cbuf, sizeof (cbuf) - 1, stdin);
+ return EXIT_FAILURE;
+ };
+if (sim_dflt_dev == NULL) /* if no default */
+ sim_dflt_dev = sim_devices[0];
+if ((stat = reset_all_p (0)) != SCPE_OK) {
+ fprintf (stderr, "Fatal simulator initialization error\n%s\n",
+ sim_error_text (stat));
+ if (sim_ttisatty())
+ read_line_p ("Hit Return to exit: ", cbuf, sizeof (cbuf) - 1, stdin);
+ return EXIT_FAILURE;
+ }
+if (register_check) {
+ /* This test is explicitly run after the above reset_all_p() so that any devices
+ which dynamically manipulate their register lists have already done that. */
+ sim_printf (" Running internal register sanity checks on %s simulator.\n", sim_name);
+ if ((stat = sim_sanity_check_register_declarations ()) != SCPE_OK) {
+ sim_printf ("Simulator device register sanity check error\n");
+ if (sim_ttisatty())
+ read_line_p ("Hit Return to exit: ", cbuf, sizeof (cbuf) - 1, stdin);
+ return EXIT_FAILURE;
+ }
+ sim_printf ("*** Good Registers in %s simulator.\n", sim_name);
+ if (argc < 2) /* No remaining command arguments? */
+ return EXIT_SUCCESS; /* then we're done */
+ }
+if ((stat = sim_brk_init ()) != SCPE_OK) {
+ fprintf (stderr, "Fatal breakpoint table initialization error\n%s\n",
+ sim_error_text (stat));
+ if (sim_ttisatty())
+ read_line_p ("Hit Return to exit: ", cbuf, sizeof (cbuf) - 1, stdin);
+ return EXIT_FAILURE;
+ }
+/* always check for register definition problems */
+sim_sanity_check_register_declarations ();
+
+signal (SIGINT, int_handler);
+if (!sim_quiet) {
+ printf ("\n");
+ show_version (stdout, NULL, NULL, 0, NULL);
+ }
+sim_timer_precalibrate_execution_rate ();
+show_version (stdnul, NULL, NULL, 1, NULL); /* Quietly set SIM_OSTYPE */
+#if defined (HAVE_PCRE_H)
+setenv ("SIM_REGEX_TYPE", "PCRE", 1); /* Publish regex type */
+#endif
sim_argv = argv;
+
+if (sim_switches & SWMASK ('T')) /* Command Line -T switch */
+ stat = test_lib_cmd (0, "ALL"); /* run library unit tests */
+
cptr = getenv("HOME");
if (cptr == NULL) {
cptr = getenv("HOMEPATH");
cptr2 = getenv("HOMEDRIVE");
}
@@ -2685,15 +2865,12 @@
}
}
if (SCPE_BARE_STATUS(stat) == SCPE_OPENERR) /* didn't exist/can't open? */
stat = SCPE_OK;
-if (sim_switches & SWMASK ('T')) /* Command Line -T switch */
- stat = sim_library_unit_tests (); /* run library unit tests */
-
if (SCPE_BARE_STATUS(stat) != SCPE_EXIT)
- process_stdin_commands (SCPE_BARE_STATUS(stat), argv);
+ process_stdin_commands (SCPE_BARE_STATUS(stat), argv, FALSE);
detach_all (0, TRUE); /* close files */
sim_set_deboff (0, NULL); /* close debug */
sim_set_logoff (0, NULL); /* close log */
sim_set_notelnet (0, NULL); /* close Telnet */
@@ -2709,16 +2886,16 @@
#endif
return sim_exit_status;
}
-t_stat process_stdin_commands (t_stat stat, char *argv[])
+t_stat process_stdin_commands (t_stat stat, char *argv[], t_bool do_called)
{
char cbuf[4*CBUFSIZE], gbuf[CBUFSIZE];
CONST char *cptr;
t_stat stat_nomessage;
-CTAB *cmdp;
+CTAB *cmdp = NULL;
stat = SCPE_BARE_STATUS(stat); /* remove possible flag */
while (stat != SCPE_EXIT) { /* in case exit */
if (stop_cpu) { /* SIGINT happened? */
stop_cpu = FALSE;
@@ -2729,15 +2906,16 @@
}
if (sim_on_actions[sim_do_depth][ON_SIGINT_ACTION])
sim_brk_setact (sim_on_actions[sim_do_depth][ON_SIGINT_ACTION]);
}
#ifdef PIDP8I
- if (cptr = build_pidp8i_scp_cmd (cbuf, sizeof (cbuf)))
+ if (sim_do_ocptr[sim_do_depth] = cptr = build_pidp8i_scp_cmd (cbuf, sizeof (cbuf)))
printf ("Running '%s'...\n", cptr);
else
#endif
- if ((cptr = sim_brk_getact (cbuf, sizeof(cbuf)))) { /* pending action? */
+ sim_do_ocptr[sim_do_depth] = cptr = sim_brk_getact (cbuf, sizeof(cbuf)); /* get bkpt action */
+ if (sim_do_ocptr[sim_do_depth]) { /* pending action? */
if (sim_do_echo)
printf ("%s+ %s\n", sim_prompt, cptr); /* echo */
sim_cptr_is_action[sim_do_depth] = TRUE;
}
else {
@@ -2745,10 +2923,11 @@
printf ("%s", sim_prompt); /* prompt */
cptr = (*sim_vm_read) (cbuf, sizeof(cbuf), stdin);
}
else
cptr = read_line_p (sim_prompt, cbuf, sizeof(cbuf), stdin);/* read with prompt*/
+ sim_do_ocptr[sim_do_depth] = cptr;
sim_cptr_is_action[sim_do_depth] = FALSE;
}
if (cptr == NULL) { /* EOF? or SIGINT? */
if (sim_ttisatty()) {
printf ("\n");
@@ -2763,19 +2942,22 @@
sim_sub_args (cbuf, sizeof(cbuf), argv);
if (sim_log) /* log cmd */
fprintf (sim_log, "%s%s\n", sim_prompt, cptr);
if (sim_deb && (sim_deb != sim_log) && (sim_deb != stdout))
fprintf (sim_deb, "%s%s\n", sim_prompt, cptr);
+ cptr = get_glyph_cmd (cptr, gbuf); /* get command glyph */
+ sim_switches = 0; /* init switches */
if (!sim_cptr_is_action[sim_do_depth]) {
sim_if_cmd_last[sim_do_depth] = sim_if_cmd[sim_do_depth];
sim_if_result_last[sim_do_depth] = sim_if_result[sim_do_depth];
sim_if_result[sim_do_depth] = sim_if_cmd[sim_do_depth] = FALSE;
}
- cptr = get_glyph_cmd (cptr, gbuf); /* get command glyph */
- sim_switches = 0; /* init switches */
- if ((cmdp = find_cmd (gbuf))) /* lookup command */
+ if ((cmdp = find_cmd (gbuf))) { /* lookup command */
+ if (do_called && (cmdp->action == &return_cmd)) /* RETURN command? */
+ break;
stat = cmdp->action (cmdp->arg, cptr); /* if found, exec */
+ }
else
stat = SCPE_UNK;
stat_nomessage = stat & SCPE_NOMESSAGE; /* extract possible message supression flag */
stat_nomessage = stat_nomessage || (!sim_show_message);/* Apply global suppression */
stat = SCPE_BARE_STATUS(stat); /* remove possible flag */
@@ -2788,10 +2970,18 @@
sim_printf ("%s\n", sim_error_text (stat));
}
if (sim_vm_post != NULL)
(*sim_vm_post) (TRUE);
} /* end while */
+if (do_called && cmdp &&
+ (cmdp->action == &return_cmd) && (0 != *cptr)) { /* return command with argument? */
+ sim_string_to_stat (cptr, &stat);
+ sim_last_cmd_stat = stat; /* save explicit status as command error status */
+ if (sim_switches & SWMASK ('Q'))
+ stat |= SCPE_NOMESSAGE; /* suppress error message display (in caller) if requested */
+ return stat; /* return with explicit return status */
+ }
return stat;
}
/* Set prompt routine */
@@ -2890,11 +3080,12 @@
fprintf (st, " HELP dev\n");
fprintf (st, " HELP dev SET\n");
fprintf (st, " HELP dev SHOW\n");
fprintf (st, " HELP dev REGISTERS\n\n");
fprintf (st, "Help is available for the following commands:\n\n ");
-qsort (hlp_cmdp, cmd_cnt, sizeof(*hlp_cmdp), _cmd_name_compare);
+if (hlp_cmdp)
+ qsort (hlp_cmdp, cmd_cnt, sizeof(*hlp_cmdp), _cmd_name_compare);
line_offset = 4;
for (i=0; iname, st);
line_offset += 5 + max_cmdname_size;
if (line_offset + max_cmdname_size > 79) {
@@ -3022,17 +3213,22 @@
MTAB *mptr;
DEBTAB *dep;
t_bool found = FALSE;
t_bool deb_desc_available = FALSE;
char buf[CBUFSIZE], header[CBUFSIZE];
+uint32 enabled_units = dptr->numunits;
+uint32 unit;
sprintf (header, "\n%s device SET commands:\n\n", dptr->name);
+for (unit=0; unit < dptr->numunits; unit++)
+ if (dptr->units[unit].flags & UNIT_DIS)
+ --enabled_units;
if (dptr->modifiers) {
for (mptr = dptr->modifiers; mptr->mask != 0; mptr++) {
if (!MODMASK(mptr,MTAB_VDV) && MODMASK(mptr,MTAB_VUN) && (dptr->numunits != 1))
continue; /* skip unit only extended modifiers */
- if ((dptr->numunits != 1) && !(mptr->mask & MTAB_XTD))
+ if ((enabled_units != 1) && !(mptr->mask & MTAB_XTD))
continue; /* skip unit only simple modifiers */
if (mptr->mstring) {
fprint_header (st, &found, header);
sprintf (buf, "set %s %s%s", sim_dname (dptr), mptr->mstring, (strchr(mptr->mstring, '=')) ? "" : (MODMASK(mptr,MTAB_VALR) ? "=val" : (MODMASK(mptr,MTAB_VALO) ? "{=val}" : "")));
if ((strlen (buf) < 30) || (!mptr->help))
@@ -3069,11 +3265,11 @@
fprintf (st, "%s%s", ((dep == dptr->debflags) ? "" : ";"), dep->name);
fprintf (st, "\n");
fprintf (st, "%-30s\tDisables specific debugging for device %s\n", buf, sim_dname (dptr));
}
}
-if ((dptr->modifiers) && (dptr->units) && (dptr->numunits != 1)) {
+if ((dptr->modifiers) && (dptr->units) && (enabled_units != 1)) {
if (dptr->units->flags & UNIT_DISABLE) {
fprint_header (st, &found, header);
sprintf (buf, "set %sn ENABLE", sim_dname (dptr));
fprintf (st, "%-30s\tEnables unit %sn\n", buf, sim_dname (dptr));
sprintf (buf, "set %sn DISABLE", sim_dname (dptr));
@@ -3111,10 +3307,20 @@
fprintf (st, "%-30s\t%s\n", buf, (strchr (mptr->mstring, '=')) ? ((strlen (buf) > 30) ? "" : mptr->help) : (mptr->help ? mptr->help : ""));
if ((strchr (mptr->mstring, '=')) && (strlen (buf) > 30))
fprintf (st, "%-30s\t%s\n", "", mptr->help);
}
}
+ }
+if (enabled_units) {
+ for (unit=0; unit < dptr->numunits; unit++)
+ if ((!(dptr->units[unit].flags & UNIT_DIS)) &&
+ (dptr->units[unit].flags & UNIT_SEQ) &&
+ (!(dptr->units[unit].flags & UNIT_MUSTBUF))) {
+ sprintf (buf, "set %s%s APPEND", sim_uname (&dptr->units[unit]), (enabled_units > 1) ? "n" : "");
+ fprintf (st, "%-30s\tSets %s%s position to EOF\n", buf, sim_uname (&dptr->units[unit]), (enabled_units > 1) ? "n" : "");
+ break;
+ }
}
if (deb_desc_available) {
fprintf (st, "\n*%s device DEBUG settings:\n", sim_dname (dptr));
for (dep = dptr->debflags; dep->name != NULL; dep++)
fprintf (st, "%4s%-12s%s\n", "", dep->name, dep->desc ? dep->desc : "");
@@ -3131,31 +3337,36 @@
void fprint_show_help_ex (FILE *st, DEVICE *dptr, t_bool silent)
{
MTAB *mptr;
t_bool found = FALSE;
char buf[CBUFSIZE], header[CBUFSIZE];
+uint32 enabled_units = dptr->numunits;
+uint32 unit;
sprintf (header, "\n%s device SHOW commands:\n\n", dptr->name);
+for (unit=0; unit < dptr->numunits; unit++)
+ if (dptr->units[unit].flags & UNIT_DIS)
+ --enabled_units;
if (dptr->modifiers) {
for (mptr = dptr->modifiers; mptr->mask != 0; mptr++) {
if (!MODMASK(mptr,MTAB_VDV) && MODMASK(mptr,MTAB_VUN) && (dptr->numunits != 1))
continue; /* skip unit only extended modifiers */
- if ((dptr->numunits != 1) && !(mptr->mask & MTAB_XTD))
+ if ((enabled_units != 1) && !(mptr->mask & MTAB_XTD))
continue; /* skip unit only simple modifiers */
if ((!mptr->disp) || (!mptr->pstring) || !(*mptr->pstring))
continue;
fprint_header (st, &found, header);
- sprintf (buf, "show %s %s%s", sim_dname (dptr), mptr->pstring, MODMASK(mptr,MTAB_SHP) ? "=arg" : "");
+ sprintf (buf, "show %s %s%s", sim_dname (dptr), mptr->pstring, MODMASK(mptr,MTAB_SHP) ? "{=arg}" : "");
fprintf (st, "%-30s\t%s\n", buf, mptr->help ? mptr->help : "");
}
}
if ((dptr->flags & DEV_DEBUG) || (dptr->debflags)) {
fprint_header (st, &found, header);
sprintf (buf, "show %s DEBUG", sim_dname (dptr));
fprintf (st, "%-30s\tDisplays debugging status for device %s\n", buf, sim_dname (dptr));
}
-if ((dptr->modifiers) && (dptr->units) && (dptr->numunits != 1)) {
+if ((dptr->modifiers) && (dptr->units) && (enabled_units != 1)) {
for (mptr = dptr->modifiers; mptr->mask != 0; mptr++) {
if ((!MODMASK(mptr,MTAB_VUN)) && MODMASK(mptr,MTAB_XTD))
continue; /* skip device only modifiers */
if ((!mptr->disp) || (!mptr->pstring))
continue;
@@ -3291,12 +3502,19 @@
char gbuf[CBUFSIZE], gbuf2[CBUFSIZE];
CTAB *cmdp;
DEVICE *dptr;
UNIT *uptr;
t_stat r;
+static t_bool help_initialized = FALSE;
t_bool explicit_device = FALSE;
+if (!help_initialized) {
+ simh_help = (char *)malloc (1 + strlen (simh_help1) + strlen (simh_help2));
+ strcpy (simh_help, simh_help1);
+ strcat (simh_help, simh_help2);
+ help_initialized = TRUE;
+ }
GET_SWITCHES (cptr); /* get switches */
if (sim_switches & SWMASK ('F'))
flag = flag | SCP_HELP_FLAT;
if (*cptr) {
cptr = get_glyph (cptr, gbuf, 0);
@@ -3398,13 +3616,16 @@
}
if (cmdpa->name == NULL) /* not found? */
sim_printf ("No help available for the %s command\n", cmdp->name);
}
}
+ else {
+ sim_printf ("No such command or device %s\n", gbuf);
+ }
}
else {
- if (dptr->flags & DEV_DISABLE)
+ if (dptr->flags & DEV_DIS)
sim_printf ("Device %s is currently disabled\n", dptr->name);
r = help_dev_help (stdout, dptr, uptr, flag, cptr);
if (sim_log)
help_dev_help (sim_log, dptr, uptr, flag | SCP_HELP_FLAT, cptr);
return r;
@@ -3421,10 +3642,11 @@
/* Spawn command */
t_stat spawn_cmd (int32 flag, CONST char *cptr)
{
t_stat status;
+
if ((cptr == NULL) || (strlen (cptr) == 0))
cptr = getenv("SHELL");
if ((cptr == NULL) || (strlen (cptr) == 0))
cptr = getenv("ComSpec");
#if defined (VMS)
@@ -3447,11 +3669,11 @@
/* Screenshot command */
t_stat screenshot_cmd (int32 flag, CONST char *cptr)
{
if ((cptr == NULL) || (strlen (cptr) == 0))
- return SCPE_ARG;
+ return sim_messagef (SCPE_ARG, "Missing screen shot filename\n");
#if defined (USE_SIM_VIDEO)
return vid_screenshot (cptr);
#else
sim_printf ("No video device\n");
return SCPE_UNK|SCPE_NOMESSAGE;
@@ -3535,21 +3757,21 @@
return do_cmd_label (flag, fcptr, NULL);
}
static char *do_position(void)
{
-static char cbuf[CBUFSIZE];
+static char cbuf[4*CBUFSIZE];
-sprintf (cbuf, "%s%s%s-%d", sim_do_filename[sim_do_depth], sim_do_label[sim_do_depth] ? "::" : "", sim_do_label[sim_do_depth] ? sim_do_label[sim_do_depth] : "", sim_goto_line[sim_do_depth]);
+snprintf (cbuf, sizeof (cbuf), "%s%s%s-%d", sim_do_filename[sim_do_depth], sim_do_label[sim_do_depth] ? "::" : "", sim_do_label[sim_do_depth] ? sim_do_label[sim_do_depth] : "", sim_goto_line[sim_do_depth]);
return cbuf;
}
t_stat do_cmd_label (int32 flag, CONST char *fcptr, CONST char *label)
{
char cbuf[4*CBUFSIZE], gbuf[CBUFSIZE], abuf[4*CBUFSIZE], quote, *c, *do_arg[11];
CONST char *cptr;
-FILE *fpin;
+FILE *fpin = NULL;
CTAB *cmdp = NULL;
int32 echo, nargs, errabort, i;
int32 saved_sim_do_echo = sim_do_echo,
saved_sim_show_message = sim_show_message,
saved_sim_on_inherit = sim_on_inherit,
@@ -3588,11 +3810,12 @@
}
} /* end for */
if (do_arg [0] == NULL) /* need at least 1 */
return SCPE_2FARG;
-if ((fpin = fopen (do_arg[0], "r")) == NULL) { /* file failed to open? */
+if ((strcasecmp (do_arg[0], "") != 0) &&
+ ((fpin = fopen (do_arg[0], "r")) == NULL)) { /* file failed to open? */
strlcpy (cbuf, do_arg[0], sizeof (cbuf)); /* try again with .sim extension */
strlcat (cbuf, ".sim", sizeof (cbuf));
if ((fpin = fopen (cbuf, "r")) == NULL) { /* failed a second time? */
if (flag == 0) /* cmd line file? */
fprintf (stderr, "Can't open file %s\n", do_arg[0]);
@@ -3628,11 +3851,11 @@
sim_debug (SIM_DBG_DO, &sim_scp_dev, "do_cmd_label(%d, flag=%d, '%s', '%s')\n", sim_do_depth, flag, fcptr, label ? label : "");
if (NULL == (c = sim_filepath_parts (cbuf, "f"))) {
stat = SCPE_MEM;
goto Cleanup_Return;
}
-strlcpy( sim_do_filename[sim_do_depth], c,
+strlcpy( sim_do_filename[sim_do_depth], strcasecmp (cbuf, "") ? c : cbuf,
sizeof (sim_do_filename[sim_do_depth])); /* stash away full path of do file name for possible use by 'call' command */
free (c);
sim_do_label[sim_do_depth] = label; /* stash away do label for possible use in messages */
sim_goto_line[sim_do_depth] = 0;
if (label) {
@@ -3646,10 +3869,15 @@
goto Cleanup_Return;
}
}
if (errabort) /* -e flag? */
set_on (1, NULL); /* equivalent to ON ERROR RETURN */
+
+if (strcasecmp (do_arg[0], "") == 0) {
+ stat = process_stdin_commands (SCPE_OK, do_arg, TRUE);
+ goto Cleanup_Return;
+ }
do {
if (stop_cpu) { /* SIGINT? */
if (sim_on_actions[sim_do_depth][ON_SIGINT_ACTION]) {
stop_cpu = FALSE;
@@ -3761,11 +3989,12 @@
}
if (sim_vm_post != NULL)
(*sim_vm_post) (TRUE);
} while (staying);
Cleanup_Return:
-fclose (fpin); /* close file */
+if (fpin)
+ fclose (fpin); /* close file */
sim_gotofile = NULL;
if (flag >= 0) {
sim_do_echo = saved_sim_do_echo; /* restore echo state we entered with */
sim_show_message = saved_sim_show_message; /* restore message display state we entered with */
sim_on_inherit = saved_sim_on_inherit; /* restore ON inheritance state we entered with */
@@ -4227,11 +4456,11 @@
ap = NULL;
++ip;
if (*ip == '~') {
expand_it = TRUE;
++ip;
- for (i=0; (i < (sizeof (parts) - 1)) && (strchr ("fpnx", *ip)); i++, ip++) {
+ for (i=0; (i < (sizeof (parts) - 1)) && (strchr ("fpnxtz", *ip)); i++, ip++) {
parts[i] = *ip;
parts[i + 1] = '\0';
}
}
if ((*ip >= '0') && (*ip <= ('9'))) { /* %n = sub */
@@ -4739,24 +4968,24 @@
return sim_send_clear (snd);
delay = get_default_env_parameter (dev_name, "SIM_SEND_DELAY", SEND_DEFAULT_DELAY);
after = get_default_env_parameter (dev_name, "SIM_SEND_AFTER", delay);
while (*cptr) {
if ((!strncmp(gbuf, "DELAY=", 6)) && (gbuf[6])) {
- delay = (uint32)get_uint (&gbuf[6], 10, 10000000, &r);
+ delay = (uint32)get_uint (&gbuf[6], 10, 2000000000, &r);
if (r != SCPE_OK)
- return sim_messagef (SCPE_ARG, "Invalid Delay Value\n");
+ return sim_messagef (SCPE_ARG, "Invalid Delay Value: %s\n", &gbuf[6]);
cptr = tptr;
tptr = get_glyph (cptr, gbuf, ',');
delay_set = TRUE;
if (!after_set)
after = delay;
continue;
}
if ((!strncmp(gbuf, "AFTER=", 6)) && (gbuf[6])) {
- after = (uint32)get_uint (&gbuf[6], 10, 10000000, &r);
+ after = (uint32)get_uint (&gbuf[6], 10, 2000000000, &r);
if (r != SCPE_OK)
- return sim_messagef (SCPE_ARG, "Invalid After Value\n");
+ return sim_messagef (SCPE_ARG, "Invalid After Value: %s\n", &gbuf[6]);
cptr = tptr;
tptr = get_glyph (cptr, gbuf, ',');
after_set = TRUE;
continue;
}
@@ -4908,17 +5137,27 @@
const char *cptr;
long fpos;
int32 saved_do_echo = sim_do_echo;
int32 saved_goto_line = sim_goto_line[sim_do_depth];
-if (NULL == sim_gotofile) return SCPE_UNK; /* only valid inside of do_cmd */
+if ((NULL == sim_gotofile) ||
+ (0 == strcasecmp (sim_do_filename[sim_do_depth], "")))
+ return SCPE_UNK; /* only valid inside of do_cmd */
+
get_glyph (fcptr, gbuf1, 0);
if ('\0' == gbuf1[0]) /* unspecified goto target */
return sim_messagef (SCPE_ARG, "Missing goto target\n");
fpos = ftell(sim_gotofile); /* Save start position */
if (fpos < 0)
return sim_messagef (SCPE_IERR, "goto ftell error: %s\n", strerror (errno));
+if (strcasecmp(":EOF", gbuf1) == 0) {
+ if (fseek (sim_gotofile, 0, SEEK_END))
+ return sim_messagef (SCPE_IERR, "goto seek error: %s\n", strerror (errno));
+ sim_brk_clract (); /* goto defangs current actions */
+ sim_do_echo = saved_do_echo; /* restore echo mode */
+ return SCPE_OK;
+ }
rewind(sim_gotofile); /* start search for label */
sim_goto_line[sim_do_depth] = 0; /* reset line number */
sim_do_echo = 0; /* Don't echo while searching for label */
while (1) {
cptr = read_line (cbuf, sizeof(cbuf), sim_gotofile);/* get cmd line */
@@ -4950,11 +5189,11 @@
/* and not dispatched here, so if we get here a return has been issued from */
/* interactive input */
t_stat return_cmd (int32 flag, CONST char *fcptr)
{
-return SCPE_UNK; /* only valid inside of do_cmd */
+return sim_messagef (SCPE_UNK, "Invalid Command\n"); /* only valid inside of do_cmd */
}
/* Shift command */
/* The shift command is invalid unless encountered in a do_cmd context, */
/* and in that context, it is handled as a special case inside of do_cmd() */
@@ -4973,17 +5212,17 @@
/* and not dispatched here, so if we get here a call has been issued from */
/* interactive input */
t_stat call_cmd (int32 flag, CONST char *fcptr)
{
-char cbuf[CBUFSIZE], gbuf[CBUFSIZE];
+char cbuf[2*CBUFSIZE], gbuf[CBUFSIZE];
const char *cptr;
if (NULL == sim_gotofile) return SCPE_UNK; /* only valid inside of do_cmd */
cptr = get_glyph (fcptr, gbuf, 0);
if ('\0' == gbuf[0]) return SCPE_ARG; /* unspecified goto target */
-sprintf(cbuf, "%s %s", sim_do_filename[sim_do_depth], cptr);
+snprintf (cbuf, sizeof (cbuf), "%s %s", sim_do_filename[sim_do_depth], cptr);
sim_switches |= SWMASK ('O'); /* inherit ON state and actions */
return do_cmd_label (flag, cbuf, gbuf);
}
/* On command */
@@ -5327,15 +5566,10 @@
if (r != SCPE_OK)
return r;
}
else if (!mptr->desc) /* value desc? */
break;
-// else if (mptr->mask & MTAB_VAL) { /* take a value? */
-// if (!cvptr) return SCPE_MISVAL; /* none? error */
-// r = dep_reg (0, cvptr, (REG *) mptr->desc, 0);
-// if (r != SCPE_OK) return r;
-// }
else if (cvptr) /* = value? */
return SCPE_ARG;
else *((int32 *) mptr->desc) = mptr->match;
} /* end if xtd */
else { /* old style */
@@ -5420,11 +5654,11 @@
if (dptr->flags & DEV_DIS) /* already dsb? ok */
return SCPE_OK;
for (i = 0; i < dptr->numunits; i++) { /* check units */
up = (dptr->units) + i; /* att or active? */
if ((up->flags & UNIT_ATT) || sim_is_active (up))
- return SCPE_NOFNC; /* can't do it */
+ return sim_messagef (SCPE_NOFNC, "%s has attached or busy units\n", sim_dname (dptr)); /* can't do it */
}
dptr->flags = dptr->flags | DEV_DIS; /* disable */
}
if (dptr->reset) /* reset device */
return dptr->reset (dptr);
@@ -5442,11 +5676,11 @@
if (flag) /* enb? enable */
uptr->flags = uptr->flags & ~UNIT_DIS;
else {
if ((uptr->flags & UNIT_ATT) || /* dsb */
sim_is_active (uptr)) /* more tests */
- return SCPE_NOFNC;
+ return sim_messagef (SCPE_NOFNC, "%s is attached or busy\n", sim_uname (uptr));
uptr->flags = uptr->flags | UNIT_DIS; /* disable */
}
return SCPE_OK;
}
@@ -5498,10 +5732,29 @@
if (dep->mask == 0) /* no match? */
r = sim_messagef (SCPE_ARG, "Invalid DEBUG option '%s' for %s device\n", gbuf, dptr->name);
} /* end while */
return r;
}
+
+/* Set sequential unit position to EOF */
+
+t_stat set_unit_append (DEVICE *dptr, UNIT *uptr, int32 flags, CONST char *cptr)
+{
+if (!(uptr->flags & UNIT_SEQ))
+ return sim_messagef (SCPE_NOFNC, "%s is not a sequential device.\n", sim_uname (uptr));
+if (uptr->flags & UNIT_BUF)
+ return sim_messagef (SCPE_NOFNC, "Can't append to a buffered device %s.\n", sim_uname (uptr));
+if (!(uptr->flags & UNIT_ATT))
+ return SCPE_UNATT;
+
+if (0 == sim_fseek (uptr->fileref, 0, SEEK_END)) {
+ uptr->pos = (t_addr)sim_ftell (uptr->fileref); /* Position at end of file */
+ return SCPE_OK;
+ }
+
+return sim_messagef (SCPE_IERR, "%s Can't seek to end of file: %s - %s\n", sim_uname (uptr), uptr->filename, strerror (errno));
+}
/* Show command */
t_stat show_cmd (int32 flag, CONST char *cptr)
{
@@ -5546,13 +5799,13 @@
lvl = MTAB_VDV; /* device match */
GET_SWITCHES (cptr); /* get more switches */
}
else if ((dptr = find_unit (gbuf, &uptr))) { /* unit match? */
if (uptr == NULL) /* invalid unit */
- return SCPE_NXUN;
+ return sim_messagef (SCPE_NXUN, "Non-existent unit: %s\n", gbuf);
if (uptr->flags & UNIT_DIS) /* disabled? */
- return SCPE_UDIS;
+ return sim_messagef (SCPE_UDIS, "Unit disabled: %s\n", gbuf);
shtb = show_unit_tab; /* global table */
lvl = MTAB_VUN; /* unit match */
GET_SWITCHES (cptr); /* get more switches */
}
else if ((shptr = find_shtab (show_glob_tab, gbuf))) { /* global? */
@@ -5561,11 +5814,11 @@
}
else {
if (sim_dflt_dev->modifiers) {
if ((cvptr = strchr (gbuf, '='))) /* = value? */
*cvptr++ = 0;
- for (mptr = sim_dflt_dev->modifiers; mptr->mask != 0; mptr++) {
+ for (mptr = sim_dflt_dev->modifiers; mptr && (mptr->mask != 0); mptr++) {
if ((((mptr->mask & MTAB_VDV) == MTAB_VDV) &&
(mptr->pstring && (MATCH_CMD (gbuf, mptr->pstring) == 0))) ||
(!(mptr->mask & MTAB_VDV) && (mptr->mstring && (MATCH_CMD (gbuf, mptr->mstring) == 0)))) {
dptr = sim_dflt_dev;
lvl = MTAB_VDV; /* device match */
@@ -5578,11 +5831,11 @@
}
if (!dptr) {
if ((shptr = find_shtab (show_dev_tab, gbuf))) /* global match? */
return shptr->action (ofile, sim_dflt_dev, uptr, shptr->arg, cptr);
else
- return SCPE_NXDEV; /* no match */
+ return sim_messagef (SCPE_NXDEV, "Non-existent device: %s\n", gbuf);/* no match */
}
}
if (*cptr == 0) { /* now eol? */
return (lvl == MTAB_VDV)?
@@ -5598,17 +5851,13 @@
for (mptr = dptr->modifiers; mptr && (mptr->mask != 0); mptr++) {
if (((mptr->mask & MTAB_XTD)? /* right level? */
((mptr->mask & lvl) == lvl): (MTAB_VUN & lvl)) &&
((mptr->disp && mptr->pstring && /* named disp? */
(MATCH_CMD (gbuf, mptr->pstring) == 0))
- // ||
- // ((mptr->mask & MTAB_VAL) && /* named value? */
- // mptr->mstring &&
- // (MATCH_CMD (gbuf, mptr->mstring) == 0)))
)) {
- if (cvptr && !(mptr->mask & MTAB_SHP))
- return SCPE_ARG;
+ if (cvptr && !MODMASK(mptr,MTAB_SHP))
+ return sim_messagef (SCPE_ARG, "Invalid Argument: %s=%s\n", gbuf, cvptr);
show_one_mod (ofile, dptr, uptr, mptr, cvptr, 1);
break;
} /* end if */
} /* end for */
if (!mptr || (mptr->mask == 0)) { /* no match? */
@@ -5617,14 +5866,16 @@
r = shptr->action (ofile, dptr, uptr, shptr->arg, cptr);
if (r != SCPE_OK)
return r;
}
- else if (!dptr->modifiers) /* no modifiers? */
- return SCPE_NOPARAM;
- else
- return SCPE_NXPAR;
+ else {
+ if (!dptr->modifiers) /* no modifiers? */
+ return sim_messagef (SCPE_NOPARAM, "%s device has no parameters\n", dptr->name);
+ else
+ return sim_messagef (SCPE_NXPAR, "Non-existent parameter: %s\n", gbuf);
+ }
} /* end if */
} /* end while */
return SCPE_OK;
}
@@ -5732,13 +5983,14 @@
return SCPE_OK;
}
const char *sprint_capac (DEVICE *dptr, UNIT *uptr)
{
-static char capac_buf[((CHAR_BIT * sizeof (t_value) * 4 + 3)/3) + 8];
+static char capac_buf[((CHAR_BIT * sizeof (t_value) * 4 + 3)/3) + 12];
t_addr kval = (uptr->flags & UNIT_BINK)? 1024: 1000;
t_addr mval;
+double remfrac;
t_addr psize = uptr->capac;
const char *scale, *width;
if (sim_switches & SWMASK ('B'))
kval = 1024;
@@ -5747,21 +5999,34 @@
psize = psize * 512;
if ((dptr->dwidth / dptr->aincr) > 8)
width = "W";
else
width = "B";
-if (psize < (kval * 10))
+if ((psize < (kval * 10)) &&
+ (0 != (psize % kval))) {
+ remfrac = 0.0;
scale = "";
-else if (psize < (mval * 10)) {
+ }
+else if ((psize < (mval * 10)) &&
+ (0 != (psize % mval))){
scale = "K";
+ remfrac = ((double)(psize % kval))/kval;
psize = psize / kval;
}
else {
scale = "M";
+ remfrac = ((double)(psize % mval))/mval;
psize = psize / mval;
}
sprint_val (capac_buf, (t_value) psize, 10, T_ADDR_W, PV_LEFT);
+if ((remfrac != 0.0) && (sim_switches & SWMASK ('R'))) {
+ char *plast_char = &capac_buf[strlen (capac_buf) - 1];
+ char save_char = *plast_char;
+
+ sprintf (plast_char, "%0.3f", remfrac);
+ *plast_char = save_char;
+ }
sprintf (&capac_buf[strlen (capac_buf)], "%s%s", scale, width);
return capac_buf;
}
void fprint_capac (FILE *st, DEVICE *dptr, UNIT *uptr)
@@ -5786,10 +6051,14 @@
sprintf (vmin_s, "%d", vmin);
setenv ("SIM_MINOR", vmin_s, 1);
sprintf (vpat_s, "%d", vpat);
setenv ("SIM_PATCH", vpat_s, 1);
fprintf (st, "%s simulator V%d.%d-%d", sim_name, vmaj, vmin, vpat);
+if (sim_vm_release != NULL) { /* if a release string is defined */
+ setenv ("SIM_VM_RELEASE", sim_vm_release, 1);
+ fprintf (st, " Release %s", sim_vm_release); /* then display it */
+ }
if (vdelt) {
sprintf (vdelt_s, "%d", vdelt);
setenv ("SIM_DELTA", vdelt_s, 1);
fprintf (st, " delta %d", vdelt);
}
@@ -5877,23 +6146,32 @@
fprintf (st, "\n Simulator Compiled as %s%s%s on %s at %s %s", cpp, arch, build, __DATE__, __TIME__, S_xstr(SIM_BUILD_OS));
#undef S_str
#undef S_xstr
#endif
#endif
+#if defined (SIM_BUILD_TOOL)
+#define S_xstr(a) S_str(a)
+#define S_str(a) #a
+ fprintf (st, "\n Build Tool: %s", S_xstr(SIM_BUILD_TOOL));
+#undef S_str
+#undef S_xstr
+#else
+ fprintf (st, "\n Build Tool: undefined (probably cmake)");
+#endif
fprintf (st, "\n Memory Access: %s Endian", sim_end ? "Little" : "Big");
fprintf (st, "\n Memory Pointer Size: %d bits", (int)sizeof(dptr)*8);
fprintf (st, "\n %s", sim_toffset_64 ? "Large File (>2GB) support" : "No Large File support");
fprintf (st, "\n SDL Video support: %s", vid_version());
-#if defined (HAVE_PCREPOSIX_H)
+#if defined (HAVE_PCRE_H)
fprintf (st, "\n PCRE RegEx (Version %s) support for EXPECT commands", pcre_version());
-#elif defined (HAVE_REGEX_H)
- fprintf (st, "\n RegEx support for EXPECT commands");
#else
fprintf (st, "\n No RegEx support for EXPECT commands");
#endif
fprintf (st, "\n OS clock resolution: %dms", os_tick_size);
fprintf (st, "\n Time taken by msleep(1): %dms", os_ms_sleep_1);
+ if (eth_version ())
+ fprintf (st, "\n Ethernet packet info: %s", eth_version());
#if defined(__VMS)
if (1) {
char *arch =
#if defined(__ia64)
"I64";
@@ -5980,10 +6258,12 @@
fprintf (st, "%sBuild: %s", flag ? "\n " : " ", S_xstr(SIM_BUILD));
#undef S_str
#undef S_xstr
#endif
fprintf (st, "\n");
+if (sim_vm_release_message != NULL) /* if a release message string is defined */
+ fprintf (st, "\n%s", sim_vm_release_message); /* then display it */
return SCPE_OK;
}
t_stat show_config (FILE *st, DEVICE *dnotused, UNIT *unotused, int32 flag, CONST char *cptr)
{
@@ -6029,26 +6309,24 @@
t_stat show_queue (FILE *st, DEVICE *dnotused, UNIT *unotused, int32 flag, CONST char *cptr)
{
DEVICE *dptr;
UNIT *uptr;
-int32 accum;
MEMFILE buf;
memset (&buf, 0, sizeof (buf));
if (cptr && (*cptr != 0))
return SCPE_2MARG;
if (sim_clock_queue == QUEUE_LIST_END)
- fprintf (st, "%s event queue empty, time = %.0f, executing %s instructios/sec\n",
- sim_name, sim_time, sim_fmt_numeric (sim_timer_inst_per_sec ()));
+ fprintf (st, "%s event queue empty, time = %.0f, executing %s %s/sec\n",
+ sim_name, sim_time, sim_fmt_numeric (sim_timer_inst_per_sec ()), sim_vm_interval_units);
else {
const char *tim = "";
double inst_per_sec = sim_timer_inst_per_sec ();
- fprintf (st, "%s event queue status, time = %.0f, executing %s instructions/sec\n",
- sim_name, sim_time, sim_fmt_numeric (inst_per_sec));
- accum = 0;
+ fprintf (st, "%s event queue status, time = %.0f, executing %s %s/sec\n",
+ sim_name, sim_time, sim_fmt_numeric (inst_per_sec), sim_vm_interval_units);
for (uptr = sim_clock_queue; uptr != QUEUE_LIST_END; uptr = uptr->next) {
if (uptr == &sim_step_unit)
fprintf (st, " Step timer");
else
if (uptr == &sim_expect_unit)
@@ -6060,20 +6338,19 @@
fprintf (st, " unit %d", (int32) (uptr - dptr->units));
}
else
fprintf (st, " Unknown");
if (inst_per_sec != 0.0)
- tim = sim_fmt_secs(((accum + uptr->time) / sim_timer_inst_per_sec ()) + (uptr->usecs_remaining / 1000000.0));
+ tim = sim_fmt_secs(((_sim_activate_queue_time (uptr) - 1) / sim_timer_inst_per_sec ()) + (uptr->usecs_remaining / 1000000.0));
if (uptr->usecs_remaining)
- fprintf (st, " at %d plus %.0f usecs%s%s%s%s\n", accum + uptr->time, uptr->usecs_remaining,
+ fprintf (st, " at %d plus %.0f usecs%s%s%s%s\n", _sim_activate_queue_time (uptr) - 1, uptr->usecs_remaining,
(*tim) ? " (" : "", tim, (*tim) ? " total)" : "",
(uptr->flags & UNIT_IDLE) ? " (Idle capable)" : "");
else
- fprintf (st, " at %d%s%s%s%s\n", accum + uptr->time,
+ fprintf (st, " at %d%s%s%s%s\n", _sim_activate_queue_time (uptr) - 1,
(*tim) ? " (" : "", tim, (*tim) ? ")" : "",
(uptr->flags & UNIT_IDLE) ? " (Idle capable)" : "");
- accum = accum + uptr->time;
}
}
sim_show_clock_queues (st, dnotused, unotused, flag, cptr);
#if defined (SIM_ASYNCH_IO)
pthread_mutex_lock (&sim_asynch_lock);
@@ -6091,11 +6368,11 @@
else fprintf (st, " Unknown");
fprintf (st, " event delay %d\n", uptr->a_event_time);
}
}
fprintf (st, "asynch latency: %d nanoseconds\n", sim_asynch_latency);
-fprintf (st, "asynch instruction latency: %d instructions\n", sim_asynch_inst_latency);
+fprintf (st, "asynch instruction latency: %d %s\n", sim_asynch_inst_latency, sim_vm_interval_units);
pthread_mutex_unlock (&sim_asynch_lock);
sim_mfile = NULL;
fprintf (st, "%*.*s", (int)buf.pos, (int)buf.pos, buf.buf);
free (buf.buf);
#endif /* SIM_ASYNCH_IO */
@@ -6184,36 +6461,82 @@
return SCPE_OK;
}
else return SCPE_NOFNC;
}
-/* Show On actions */
+/* Show On actions for one level (default current level) */
t_stat show_on (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, CONST char *cptr)
{
int32 lvl, i;
if (cptr && (*cptr != 0)) return SCPE_2MARG; /* now eol? */
-for (lvl=sim_do_depth; lvl >= 0; --lvl) {
+if (flag < 0)
+ lvl = sim_do_depth;
+else
+ if (flag > sim_do_depth)
+ return SCPE_ARG;
+ else
+ lvl = flag;
+if (flag == -1) {
if (lvl > 0)
fprintf(st, "On Processing at Do Nest Level: %d", lvl);
else
fprintf(st, "On Processing for input commands");
- fprintf(st, " is %s\n", (sim_on_check[lvl]) ? "enabled" : "disabled");
- for (i=1; i= 0; --lvl) {
+ if (lvl > 0)
+ fprintf(st, "Do Nest Level: %d\n", lvl);
+ else {
+ if (sim_do_filename[lvl][0])
+ fprintf(st, "Initial Input ");
+ else
+ fprintf(st, "Console Input commands\n");
+ }
+ if (sim_do_filename[lvl][0]) {
+ fprintf (st, "File: %s\n", sim_do_filename[lvl]);
+ if (strcasecmp (sim_do_filename[lvl], ""))
+ fprintf (st, "Line: %d\n", sim_goto_line[lvl]);
+ }
+ if (sim_if_cmd[lvl])
+ fprintf (st, "Processing IF command\n");
+ if (sim_if_cmd_last[lvl])
+ fprintf (st, "IF command last\n");
+ if (sim_if_result[lvl])
+ fprintf (st, "IF result\n");
+ if (sim_if_result_last[lvl])
+ fprintf (st, "IF result last\n");
+ if (sim_cptr_is_action[lvl])
+ fprintf (st, "Command is Action\n");
+ fprintf (st, "Command is: %s\n", sim_do_ocptr[lvl]);
+ show_on (st, dptr, uptr, lvl, cptr);
}
if (sim_on_inherit)
fprintf(st, "on state and actions are inherited by nested do commands and subroutines\n");
return SCPE_OK;
}
@@ -6266,22 +6589,15 @@
t_stat show_one_mod (FILE *st, DEVICE *dptr, UNIT *uptr, MTAB *mptr,
CONST char *cptr, int32 flag)
{
t_stat r = SCPE_OK;
-//t_value val;
if (mptr->disp)
r = mptr->disp (st, uptr, mptr->match, (CONST void *)(cptr? cptr: mptr->desc));
-//else if ((mptr->mask & MTAB_XTD) && (mptr->mask & MTAB_VAL)) {
-// REG *rptr = (REG *) mptr->desc;
-// fprintf (st, "%s=", mptr->pstring);
-// val = get_rval (rptr, 0);
-// fprint_val (st, val, rptr->radix, rptr->width,
-// rptr->flags & REG_FMT);
-// }
-else fputs (mptr->pstring, st);
+else
+ fputs (mptr->pstring, st);
if ((r == SCPE_OK) && (flag && !((mptr->mask & MTAB_XTD) && MODMASK(mptr,MTAB_NMO))))
fputc ('\n', st);
return r;
}
@@ -6581,10 +6897,86 @@
stat = sim_dir_scan (sname, sim_copy_entry, ©_state);
if ((stat == SCPE_OK) && (copy_state.count))
return sim_messagef (SCPE_OK, " %3d file(s) copied\n", copy_state.count);
return copy_state.stat;
}
+
+t_stat rename_cmd (int32 flg, CONST char *cptr)
+{
+char sname[CBUFSIZE], dname[CBUFSIZE];
+
+if ((!cptr) || (*cptr == 0))
+ return SCPE_2FARG;
+cptr = get_glyph_quoted (cptr, sname, 0);
+if ((!cptr) || (*cptr == 0))
+ return SCPE_2FARG;
+cptr = get_glyph_quoted (cptr, dname, 0);
+if (0 == rename (sname, dname))
+ return SCPE_OK;
+return sim_messagef (SCPE_ARG, "Can't rename '%s' to '%s': %s\n\n", sname, dname, strerror (errno));
+}
+
+t_stat mkdir_cmd (int32 flg, CONST char *cptr)
+{
+char path[CBUFSIZE];
+char *c;
+struct stat filestat;
+
+GET_SWITCHES (cptr); /* get switches */
+if ((!cptr) || (*cptr == '\0'))
+ return sim_messagef (SCPE_2FARG, "Must specify a directory path\n");
+strlcpy (path, cptr, sizeof (path));
+while ((c = strchr (path, '\\')))
+ *c = '/';
+if (path[strlen (path) - 1] == '/') /* trim any trailing / from the path */
+ path[strlen (path) - 1] = '\0';
+while ((c = strstr (path, "//")))
+ memmove (c, c + 1, strlen (c + 1) + 1); /* clean out any empty directories // */
+if ((!stat (path, &filestat)) && (filestat.st_mode & S_IFDIR))
+ return sim_messagef (SCPE_OK, "directory %s already exists\n", path);
+c = path;
+while ((c = strchr (c, '/'))) {
+ *c = '\0';
+ if (!stat (path, &filestat)) {
+ if (filestat.st_mode & S_IFDIR) {
+ *c = '/'; /* restore / */
+ ++c;
+ continue;
+ }
+ return sim_messagef (SCPE_ARG, "%s is not a directory\n", path);
+ }
+ if (
+#if defined(_WIN32)
+ mkdir (path)
+#else
+ mkdir (path, 0777)
+#endif
+ )
+ return sim_messagef (SCPE_ARG, "Can't create directory: %s - %s\n", path, strerror (errno));
+ *c = '/'; /* restore / */
+ ++c;
+ }
+if (
+#if defined(_WIN32)
+ mkdir (path)
+#else
+ mkdir (path, 0777)
+#endif
+ )
+ return sim_messagef (SCPE_ARG, "Can't create directory: %s - %s\n", path, strerror (errno));
+return SCPE_OK;
+}
+
+t_stat rmdir_cmd (int32 flg, CONST char *cptr)
+{
+GET_SWITCHES (cptr); /* get switches */
+if ((!cptr) || (*cptr == '\0'))
+ return sim_messagef (SCPE_2FARG, "Must specify a directory\n");
+if (rmdir (cptr))
+ return sim_messagef (SCPE_ARG, "Can't remove directory: %s - %s\n", cptr, strerror (errno));
+return SCPE_OK;
+}
/* Debug command */
t_stat debug_cmd (int32 flg, CONST char *cptr)
{
@@ -6729,10 +7121,127 @@
return SCPE_NXDEV;
if (dptr->reset != NULL)
return dptr->reset (dptr);
else return SCPE_OK;
}
+
+t_stat runlimit_cmd (int32 flag, CONST char *cptr)
+{
+char gbuf[CBUFSIZE];
+int32 num;
+t_stat r;
+double usec_factor = 1.0;
+const char *units = "";
+char runlimit[32];
+
+GET_SWITCHES (cptr); /* get switches */
+if (0 == flag) {
+ if (*cptr)
+ return sim_messagef (SCPE_ARG, "NORUNLIMIT expects no arguments: %s\n", cptr);
+ sim_runlimit = 0;
+ sim_runlimit_switches = 0;
+ sim_runlimit_enabled = FALSE;
+ sim_cancel (&sim_runlimit_unit);
+ unsetenv ("SIM_RUNLIMIT");
+ unsetenv ("SIM_RUNLIMIT_UNITS");
+ return SCPE_OK;
+ }
+
+cptr = get_glyph (cptr, gbuf, 0); /* get next glyph */
+num = (int32) get_uint (gbuf, 10, INT_MAX, &r);
+if ((r != SCPE_OK) || (num == 0)) /* error? */
+ return sim_messagef (SCPE_ARG, "Invalid argument: %s\n", gbuf);
+cptr = get_glyph (cptr, gbuf, 0); /* get next glyph */
+if ((gbuf[0] == '\0') ||
+ (MATCH_CMD (gbuf, sim_vm_interval_units) == 0)) {
+ sim_switches &= ~SWMASK ('T');
+ units = sim_vm_interval_units;
+ }
+else {
+ int i;
+ struct {
+ const char *name;
+ double usec_factor;
+ } time_units[] = {
+ {"MICROSECONDS", 1.0},
+ {"USECONDS", 1.0},
+ {"SECONDS", 1000000.0},
+ {"MINUTES", 60*1000000.0},
+ {"HOURS", 60*60*1000000.0},
+ {NULL, 0.0}};
+
+ for (i=0; time_units[i].name; i++) {
+ if (MATCH_CMD (gbuf, time_units[i].name) == 0) {
+ sim_switches |= SWMASK ('T');
+ usec_factor = time_units[i].usec_factor;
+ units = time_units[i].name;
+ break;
+ }
+ }
+ if (time_units[i].name == NULL)
+ return sim_messagef (SCPE_2MARG, "Too many arguments: %s %s\n", gbuf, cptr);
+ }
+if (*cptr)
+ return sim_messagef (SCPE_2MARG, "Too many arguments: %s\n", cptr);
+sim_runlimit_enabled = TRUE;
+sim_cancel (&sim_runlimit_unit);
+sim_runlimit_switches = sim_switches;
+if (sim_runlimit_switches & SWMASK ('T')) {
+ sim_runlimit_d_initial = sim_runlimit_d = num * usec_factor * sim_host_speed_factor ();
+ if (sim_host_speed_factor () > 1.0)
+ sim_messagef (SCPE_OK, "Slow host - adjusting RUNLIMIT from %d %s to %.1f %s\n", num, units, num * sim_host_speed_factor (), units);
+ snprintf (runlimit, sizeof (runlimit), "%.f", num * sim_host_speed_factor ());
+ setenv ("SIM_RUNLIMIT", runlimit, 1);
+ setenv ("SIM_RUNLIMIT_UNITS", units, 1);
+ return sim_activate_after_d (&sim_runlimit_unit, sim_runlimit_d);
+ }
+else {
+ sim_runlimit_initial = sim_runlimit = num;
+ snprintf (runlimit, sizeof (runlimit), "%d", num);
+ setenv ("SIM_RUNLIMIT", runlimit, 1);
+ setenv ("SIM_RUNLIMIT_UNITS", units, 1);
+ return sim_activate (&sim_runlimit_unit, sim_runlimit);
+ }
+}
+
+t_stat set_runlimit (int32 flag, CONST char *cptr)
+{
+return runlimit_cmd (flag, cptr);
+}
+
+t_stat show_runlimit (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, CONST char *cptr)
+{
+if (sim_runlimit_enabled) {
+ if (sim_runlimit_switches & SWMASK ('T')) {
+ double inst_per_sec = sim_timer_inst_per_sec ();
+
+ if (sim_runlimit_d_initial != sim_runlimit_d) {
+ fprintf (st, "%s initially, ", sim_fmt_secs (sim_runlimit_d_initial / 1000000.0));
+ if (sim_is_active (&sim_runlimit_unit))
+ fprintf (st, "and %s remaining\n", sim_fmt_secs (sim_runlimit_d / 1000000.0));
+ else
+ fprintf (st, "expired now\n");
+ }
+ else
+ fprintf (st, "%s\n", sim_fmt_secs (sim_runlimit_d_initial / 1000000.0));
+ }
+ else {
+ if (sim_runlimit_initial != sim_runlimit) {
+ fprintf (st, "%d %s initially, ", sim_runlimit_initial, sim_vm_interval_units);
+ if (sim_is_active (&sim_runlimit_unit))
+ fprintf (st, "and %d %s remaining\n", sim_activate_time (&sim_runlimit_unit), sim_vm_interval_units);
+ else
+ fprintf (st, "expired now\n");
+ }
+ else
+ fprintf (st, "%d %s\n", sim_runlimit_initial, sim_vm_interval_units);
+ }
+ }
+else
+ fprintf (st, "Run Limit Disabled\n");
+return SCPE_OK;
+}
/* Reset devices start..end
Inputs:
start = number of starting device
@@ -6977,11 +7486,11 @@
else {
if (sim_switches & SWMASK ('N')) { /* new file only? */
uptr->fileref = sim_fopen (cptr, "wb+"); /* open new file */
if (uptr->fileref == NULL) /* open fail? */
return attach_err (uptr, SCPE_OPENERR); /* yes, error */
- sim_messagef (SCPE_OK, "%s: creating new file\n", sim_dname (dptr));
+ sim_messagef (SCPE_OK, "%s: creating new file: %s\n", sim_dname (dptr), cptr);
}
else { /* normal */
uptr->fileref = sim_fopen (cptr, "rb+"); /* open r/w */
if (uptr->fileref == NULL) { /* open fail? */
#if defined(EPERM)
@@ -7280,12 +7789,14 @@
if (*cptr == 0) /* must be more */
return SCPE_2FARG;
gbuf[sizeof(gbuf)-1] = '\0';
strlcpy (gbuf, cptr, sizeof(gbuf));
sim_trim_endspc (gbuf);
-if ((sfile = sim_fopen (gbuf, "wb")) == NULL)
- return SCPE_OPENERR;
+if ((sfile = sim_fopen (gbuf, "r+b")) == NULL) { /* try existing file */
+ if ((sfile = sim_fopen (gbuf, "wb")) == NULL) /* create new empty file */
+ return SCPE_OPENERR;
+ }
r = sim_save (sfile);
fclose (sfile);
return r;
}
@@ -7418,10 +7929,17 @@
}
}
fputc ('\n', sfile); /* end registers */
}
fputc ('\n', sfile); /* end devices */
+if (!ferror (sfile)) {
+ t_offset pos = sim_ftell (sfile); /* get current position */
+
+ if (pos < 0) /* error? */
+ return SCPE_IOERR; /* done! */
+ sim_set_fsize (sfile, (t_addr)pos); /* truncate the save file */
+ }
return (ferror (sfile))? SCPE_IOERR: SCPE_OK; /* error during save? */
}
/* Restore command
@@ -7849,10 +8367,15 @@
t_value pcv, orig_pcv;
t_stat r;
DEVICE *dptr;
UNIT *uptr;
+if (sim_runlimit_enabled && /* If the run limit has been hit? */
+ (!sim_is_active (&sim_runlimit_unit))) {
+ sim_messagef (SCPE_RUNTIME, "Execution limit exceeded, can't proceed. Exiting...\n");
+ exit (SCPE_RUNTIME); /* Execution can't proceed */
+ }
GET_SWITCHES (cptr); /* get switches */
sim_step = 0;
if ((flag == RU_RUN) || (flag == RU_GO)) { /* run or go */
orig_pcv = get_rval (sim_PC, 0); /* get current PC value */
if (*cptr != 0) { /* argument? */
@@ -8066,10 +8589,17 @@
} while (1);
if ((SCPE_BARE_STATUS(r) == SCPE_STOP) &&
sigterm_received)
r = SCPE_SIGTERM;
+
+if (sim_runlimit_enabled) {
+ if (sim_runlimit_switches & SWMASK ('T'))
+ sim_runlimit_d = sim_activate_time_usecs (&sim_runlimit_unit);
+ else
+ sim_runlimit = sim_activate_time (&sim_runlimit_unit) - 1;
+ }
if ((SCPE_BARE_STATUS(r) == SCPE_STOP) && /* WRU exit from sim_instr() */
(sim_on_actions[sim_do_depth][SCPE_STOP] == NULL) &&/* without a handler for a STOP condition */
(sim_on_actions[sim_do_depth][0] == NULL))
sim_os_ms_sleep (sim_stop_sleep_ms); /* wait a bit for SIGINT */
@@ -8116,15 +8646,14 @@
t_stat sim_run_boot_prep (int32 flag)
{
t_stat r;
-sim_interval = 0; /* reset queue */
-sim_time = sim_rtime = 0;
-noqueue_time = 0; /* reset queue */
+/* reset queue */
while (sim_clock_queue != QUEUE_LIST_END)
sim_cancel (sim_clock_queue);
+sim_time = sim_rtime = 0;
noqueue_time = sim_interval = 0;
r = reset_all (0);
if ((r == SCPE_OK) && (flag == RU_RUN)) {
if ((run_cmd_did_reset) && (0 == (sim_switches & SWMASK ('Q')))) {
sim_printf ("Resetting all devices... This may not have been your intention.\n");
@@ -8197,10 +8726,18 @@
t_stat step_svc (UNIT *uptr)
{
return SCPE_STEP;
}
+
+/* Unit service for run for timeout, originally scheduled by RUNFOR n command
+ Return runlimit timeout SCP code, will cause simulation to stop */
+
+t_stat runlimit_svc (UNIT *uptr)
+{
+return SCPE_RUNTIME;
+}
/* Unit service to facilitate expect matching to stop simulation.
Return expect SCP code, will cause simulation to stop */
t_stat expect_svc (UNIT *uptr)
@@ -8841,11 +9378,14 @@
return 1;
if (*cptr == 0) /* success */
return dfltinc;
}
if (uptr->flags & UNIT_RO) /* read only? */
- return SCPE_RO;
+ return sim_messagef (SCPE_RO, "%s is read only.\n"
+ "%sse a writable device to change %s\n",
+ sim_uname (uptr), (uptr->flags & UNIT_ROABLE) ? "Attach Read/Write or u" : "U",
+ uptr->filename ? uptr->filename : "it");
mask = width_mask[dptr->dwidth];
GET_RADIX (rdx, dptr->dradix);
if ((reason = parse_sym ((CONST char *)cptr, addr, uptr, sim_eval, sim_switches)) > 0) {
sim_eval[0] = get_uint (cptr, rdx, mask, &reason);
@@ -9354,11 +9894,21 @@
val = strtotv ((CONST char *)cptr, &tptr, radix);
if ((cptr == tptr) || (val > max))
*status = SCPE_ARG;
else {
while (sim_isspace (*tptr)) tptr++;
- if (*tptr != 0)
+ if (sim_toupper (*tptr) == 'K') {
+ val *= 1000;
+ ++tptr;
+ }
+ else {
+ if (sim_toupper (*tptr) == 'M') {
+ val *= 1000000;
+ ++tptr;
+ }
+ }
+ if ((*tptr != 0) || (val > max))
*status = SCPE_ARG;
}
return val;
}
@@ -10783,17 +11333,18 @@
else
reason = SCPE_OK;
}
AIO_EVENT_COMPLETE(uptr, reason);
bare_reason = SCPE_BARE_STATUS (reason);
- if ((bare_reason != SCPE_OK) && /* Provide context for unexpected errors */
- (bare_reason >= SCPE_BASE) &&
- (bare_reason != SCPE_EXPECT) &&
- (bare_reason != SCPE_REMOTE) &&
- (bare_reason != SCPE_MTRLNT) &&
- (bare_reason != SCPE_STOP) &&
- (bare_reason != SCPE_STEP) &&
+ if ((bare_reason != SCPE_OK) && /* Provide context for unexpected errors */
+ (bare_reason >= SCPE_BASE) &&
+ (bare_reason != SCPE_EXPECT) &&
+ (bare_reason != SCPE_REMOTE) &&
+ (bare_reason != SCPE_MTRLNT) &&
+ (bare_reason != SCPE_STOP) &&
+ (bare_reason != SCPE_STEP) &&
+ (bare_reason != SCPE_RUNTIME) &&
(bare_reason != SCPE_EXIT))
sim_messagef (reason, "\nUnexpected internal error while processing event for %s which returned %d - %s\n", sim_uname (uptr), reason, sim_error_text (reason));
} while ((reason == SCPE_OK) &&
(sim_interval <= 0) &&
(sim_clock_queue != QUEUE_LIST_END) &&
@@ -10995,11 +11546,12 @@
if (!uptr->next)
uptr->time = 0;
uptr->usecs_remaining = 0;
if (sim_clock_queue != QUEUE_LIST_END)
sim_interval = sim_clock_queue->time;
-else sim_interval = noqueue_time = NOQUEUE_WAIT;
+else
+ sim_interval = noqueue_time = NOQUEUE_WAIT;
if (uptr->next) {
sim_printf ("Cancel failed for %s\n", sim_uname(uptr));
if (sim_deb)
fclose(sim_deb);
abort ();
@@ -11028,11 +11580,11 @@
uptr = pointer to unit
Outputs:
result = absolute activation time + 1, 0 if inactive
*/
-int32 _sim_activate_time (UNIT *uptr)
+int32 _sim_activate_queue_time (UNIT *uptr)
{
UNIT *cptr;
int32 accum;
accum = 0;
@@ -11042,14 +11594,23 @@
accum = accum + sim_interval;
}
else
accum = accum + cptr->time;
if (cptr == uptr)
- return accum + 1 + (int32)((uptr->usecs_remaining * sim_timer_inst_per_sec ()) / 1000000.0);
+ return accum + 1;
}
return 0;
}
+
+int32 _sim_activate_time (UNIT *uptr)
+{
+int32 accum = _sim_activate_queue_time (uptr);
+
+if (accum)
+ return accum + (int32)((uptr->usecs_remaining * sim_timer_inst_per_sec ()) / 1000000.0);
+return 0;
+}
int32 sim_activate_time (UNIT *uptr)
{
int32 accum;
@@ -11734,13 +12295,13 @@
while (sim_isspace(*cptr))
++cptr;
}
tptr = get_glyph (cptr, gbuf, ',');
if ((!strncmp(gbuf, "HALTAFTER=", 10)) && (gbuf[10])) {
- after = (uint32)get_uint (&gbuf[10], 10, 100000000, &r);
+ after = (uint32)get_uint (&gbuf[10], 10, 2000000000, &r);
if (r != SCPE_OK)
- return sim_messagef (SCPE_ARG, "Invalid Halt After Value\n");
+ return sim_messagef (SCPE_ARG, "Invalid Halt After Value: %s\n", &gbuf[10]);
cptr = tptr;
after_set = TRUE;
}
if ((*cptr != '\0') && (*cptr != '"') && (*cptr != '\''))
return sim_messagef (SCPE_ARG, "String must be quote delimited\n");
@@ -11796,11 +12357,11 @@
free (ep->match); /* deallocate match string */
free (ep->match_pattern); /* deallocate the display format match string */
free (ep->act); /* deallocate action */
#if defined(USE_REGEX)
if (ep->switches & EXP_TYP_REGEX)
- regfree (&ep->regex); /* release compiled regex */
+ pcre_free (ep->regex); /* release compiled regex */
#endif
exp->size -= 1; /* decrement count */
for (i=ep-exp->rules; isize; i++) /* shuffle up remaining rules */
exp->rules[i] = exp->rules[i+1];
if (exp->size == 0) { /* No rules left? */
@@ -11831,11 +12392,11 @@
free (exp->rules[i].match); /* deallocate match string */
free (exp->rules[i].match_pattern); /* deallocate display format match string */
free (exp->rules[i].act); /* deallocate action */
#if defined(USE_REGEX)
if (exp->rules[i].switches & EXP_TYP_REGEX)
- regfree (&exp->rules[i].regex); /* release compiled regex */
+ pcre_free (exp->rules[i].regex); /* release compiled regex */
#endif
}
free (exp->rules);
exp->rules = NULL;
exp->size = 0;
@@ -11863,29 +12424,25 @@
#if !defined (USE_REGEX)
free (match_buf);
return sim_messagef (SCPE_ARG, "RegEx support not available\n");
}
#else /* USE_REGEX */
- int res;
- regex_t re;
+ pcre *re;
+ const char *errmsg;
+ int erroffset, re_nsub;
- memset (&re, 0, sizeof(re));
memcpy (match_buf, match+1, strlen(match)-2); /* extract string without surrounding quotes */
match_buf[strlen(match)-2] = '\0';
- res = regcomp (&re, (char *)match_buf, REG_EXTENDED | ((switches & EXP_TYP_REGEX_I) ? REG_ICASE : 0));
- if (res) {
- size_t err_size = regerror (res, &re, NULL, 0);
- char *err_buf = (char *)calloc (err_size+1, 1);
-
- regerror (res, &re, err_buf, err_size);
- sim_messagef (SCPE_ARG, "Regular Expression Error: %s\n", err_buf);
- free (err_buf);
+ re = pcre_compile ((char *)match_buf, (switches & EXP_TYP_REGEX_I) ? PCRE_CASELESS : 0, &errmsg, &erroffset, NULL);
+ if (re == NULL) {
+ sim_messagef (SCPE_ARG, "Regular Expression Error: %s\n", errmsg);
free (match_buf);
return SCPE_ARG|SCPE_NOMESSAGE;
}
- sim_debug (exp->dbit, exp->dptr, "Expect Regular Expression: \"%s\" has %d sub expressions\n", match_buf, (int)re.re_nsub);
- regfree (&re);
+ (void)pcre_fullinfo(re, NULL, PCRE_INFO_CAPTURECOUNT, &re_nsub);
+ sim_debug (exp->dbit, exp->dptr, "Expect Regular Expression: \"%s\" has %d sub expressions\n", match_buf, re_nsub);
+ pcre_free (re);
}
#endif
else {
if (switches & EXP_TYP_REGEX_I) {
free (match_buf);
@@ -11921,13 +12478,17 @@
free (match_buf);
return SCPE_MEM;
}
if (switches & EXP_TYP_REGEX) {
#if defined(USE_REGEX)
+ const char *errmsg;
+ int erroffset;
+
memcpy (match_buf, match+1, strlen(match)-2); /* extract string without surrounding quotes */
match_buf[strlen(match)-2] = '\0';
- regcomp (&ep->regex, (char *)match_buf, REG_EXTENDED);
+ ep->regex = pcre_compile ((char *)match_buf, (switches & EXP_TYP_REGEX_I) ? PCRE_CASELESS : 0, &errmsg, &erroffset, NULL);
+ (void)pcre_fullinfo(ep->regex, NULL, PCRE_INFO_CAPTURECOUNT, &ep->re_nsub);
#endif
free (match_buf);
match_buf = NULL;
}
else {
@@ -12007,11 +12568,11 @@
fprintf (st, " Match Buffer Size: %d\n", exp->buf_size);
fprintf (st, " Buffer Insert Offset: %d\n", exp->buf_ins);
fprintf (st, " Buffer Contents: %s\n", bstr);
if (default_haltafter)
- fprintf (st, " Default HaltAfter: %u instructions\n", (unsigned)default_haltafter);
+ fprintf (st, " Default HaltAfter: %u %s\n", (unsigned)default_haltafter, sim_vm_interval_units);
free (bstr);
}
if (exp->dptr && (exp->dbit & exp->dptr->dctrl))
fprintf (st, " Expect Debugging via: SET %s DEBUG%s%s\n", sim_dname(exp->dptr), exp->dptr->debflags ? "=" : "", exp->dptr->debflags ? _get_dbg_verb (exp->dbit, exp->dptr, NULL) : "");
fprintf (st, " Match Rules:\n");
@@ -12058,11 +12619,12 @@
for (i=0; i < exp->size; i++) {
ep = &exp->rules[i];
if (ep->switches & EXP_TYP_REGEX) {
#if defined (USE_REGEX)
- regmatch_t *matches;
+ int *ovector = NULL;
+ int rc;
char *cbuf = (char *)exp->buf;
static size_t sim_exp_match_sub_count = 0;
if (tstr)
cbuf = tstr;
@@ -12076,42 +12638,44 @@
strcpy (&tstr[strlen (tstr)], (char *)&exp->buf[off]);
cbuf = tstr;
}
}
++regex_checks;
- matches = (regmatch_t *)calloc ((ep->regex.re_nsub + 1), sizeof(*matches));
+ ovector = (int *)malloc (3 * (ep->re_nsub + 1) * sizeof (*ovector));
if (sim_deb && exp->dptr && (exp->dptr->dctrl & exp->dbit)) {
char *estr = sim_encode_quoted_string (exp->buf, exp->buf_ins);
sim_debug (exp->dbit, exp->dptr, "Checking String: %s\n", estr);
sim_debug (exp->dbit, exp->dptr, "Against RegEx Match Rule: %s\n", ep->match_pattern);
free (estr);
}
- if (!regexec (&ep->regex, cbuf, ep->regex.re_nsub + 1, matches, REG_NOTBOL)) {
+ rc = pcre_exec (ep->regex, NULL, cbuf, exp->buf_ins, 0, PCRE_NOTBOL, ovector, 3 * (ep->re_nsub + 1));
+ if (rc >= 0) {
size_t j;
char *buf = (char *)malloc (1 + exp->buf_ins);
- for (j=0; jregex.re_nsub + 1; j++) {
+ for (j=0; j < (size_t)rc; j++) {
char env_name[32];
sprintf (env_name, "_EXPECT_MATCH_GROUP_%d", (int)j);
- memcpy (buf, &cbuf[matches[j].rm_so], matches[j].rm_eo-matches[j].rm_so);
- buf[matches[j].rm_eo-matches[j].rm_so] = '\0';
+ memcpy (buf, &cbuf[ovector[2 * j]], ovector[2 * j + 1] - ovector[2 * j]);
+ buf[ovector[2 * j + 1] - ovector[2 * j]] = '\0';
setenv (env_name, buf, 1); /* Make the match and substrings available as environment variables */
sim_debug (exp->dbit, exp->dptr, "%s=%s\n", env_name, buf);
}
for (; jregex.re_nsub;
- free (matches);
+ sim_exp_match_sub_count = ep->re_nsub;
+ free (ovector);
+ ovector = NULL;
free (buf);
break;
}
- free (matches);
+ free (ovector);
#endif
}
else {
if (exp->buf_data < ep->size) /* Too little data to match yet? */
continue; /* Yes, Try next one. */
@@ -12232,10 +12796,16 @@
}
memcpy(snd->buffer+snd->insoff, data, size);
snd->insoff += size;
snd->delay = (sim_switches & SWMASK ('T')) ? (uint32)((sim_timer_inst_per_sec()*delay)/1000000.0) : delay;
snd->after = (sim_switches & SWMASK ('T')) ? (uint32)((sim_timer_inst_per_sec()*after)/1000000.0) : after;
+if (sim_switches & SWMASK ('T'))
+ sim_debug (snd->dbit, snd->dptr, "%d bytes queued for input. Delay %d usecs = %d insts, After %d usecs = %d insts\n",
+ (int)size, (int)delay, (int)snd->delay, (int)after, (int)snd->after);
+else
+ sim_debug (snd->dbit, snd->dptr, "%d bytes queued for input. Delay=%d, After=%d\n",
+ (int)size, (int)delay, (int)after);
snd->next_time = sim_gtime() + snd->after;
return SCPE_OK;
}
/* Cancel Queued input data */
@@ -12262,23 +12832,23 @@
}
else
fprintf (st, " No Pending Input Data\n");
if ((snd->next_time - sim_gtime()) > 0) {
if (((snd->next_time - sim_gtime()) > (sim_timer_inst_per_sec()/1000000.0)) && ((sim_timer_inst_per_sec()/1000000.0) > 0.0))
- fprintf (st, " Minimum of %d instructions (%d microseconds) before sending first character\n", (int)(snd->next_time - sim_gtime()),
+ fprintf (st, " Minimum of %d %s (%d microseconds) before sending first character\n", (int)(snd->next_time - sim_gtime()), sim_vm_interval_units,
(int)((snd->next_time - sim_gtime())/(sim_timer_inst_per_sec()/1000000.0)));
else
- fprintf (st, " Minimum of %d instructions before sending first character\n", (int)(snd->next_time - sim_gtime()));
+ fprintf (st, " Minimum of %d %s before sending first character\n", (int)(snd->next_time - sim_gtime()), sim_vm_interval_units);
}
if ((snd->delay > (sim_timer_inst_per_sec()/1000000.0)) && ((sim_timer_inst_per_sec()/1000000.0) > 0.0))
- fprintf (st, " Minimum of %d instructions (%d microseconds) between characters\n", (int)snd->delay, (int)(snd->delay/(sim_timer_inst_per_sec()/1000000.0)));
+ fprintf (st, " Minimum of %d %s (%d microseconds) between characters\n", (int)snd->delay, sim_vm_interval_units, (int)(snd->delay/(sim_timer_inst_per_sec()/1000000.0)));
else
- fprintf (st, " Minimum of %d instructions between characters\n", (int)snd->delay);
+ fprintf (st, " Minimum of %d %s between characters\n", (int)snd->delay, sim_vm_interval_units);
if (after)
- fprintf (st, " Default delay before first character input is %u instructions\n", after);
+ fprintf (st, " Default delay before first character input is %u %s\n", after, sim_vm_interval_units);
if (delay)
- fprintf (st, " Default delay between character input is %u instructions\n", after);
+ fprintf (st, " Default delay between character input is %u %s\n", after, sim_vm_interval_units);
if (snd->dptr && (snd->dbit & snd->dptr->dctrl))
fprintf (st, " Send Debugging via: SET %s DEBUG%s%s\n", sim_dname(snd->dptr), snd->dptr->debflags ? "=" : "", snd->dptr->debflags ? _get_dbg_verb (snd->dbit, snd->dptr, NULL) : "");
return SCPE_OK;
}
@@ -12856,10 +13426,18 @@
}
debug_unterm = 0;
}
j = i + 1;
}
+ else {
+ if (buf[i] == 0) { /* Imbedded \0 character in formatted result? */
+ fprintf (stderr, "sim_debug() formatted result: '%s'\r\n"
+ " has an imbedded \\0 character.\r\n"
+ "DON'T DO THAT!\r\n", buf);
+ abort();
+ }
+ }
}
if (i > j) {
if (!debug_unterm) /* print prefix when required */
_sim_debug_write (debug_prefix, strlen (debug_prefix));
_sim_debug_write (&buf[j], i-j);
@@ -13230,10 +13808,16 @@
appendText (topic, dptr->name, strlen (dptr->name));
break;
case 'S':
appendText (topic, sim_name, strlen (sim_name));
break;
+ case 'C':
+ appendText (topic, sim_vm_interval_units, strlen (sim_vm_interval_units));
+ break;
+ case 'I':
+ appendText (topic, sim_vm_step_unit, strlen (sim_vm_step_unit));
+ break;
case '%':
appendText (topic, "%", 1);
break;
case '+':
appendText (topic, "+", 1);
@@ -13494,11 +14078,11 @@
rewind (tmp);
/* Discard leading blank lines/redundant titles */
for (i =0; i < skiplines; i++)
- fgets (tbuf, sizeof (tbuf), tmp);
+ if (fgets (tbuf, sizeof (tbuf), tmp)) {};
while (fgets (tbuf, sizeof (tbuf), tmp)) {
if (tbuf[0] != '\n')
fputs (" ", st);
fputs (tbuf, st);
@@ -13539,22 +14123,23 @@
}
#define HLP_MATCH_AMBIGUOUS (~0u)
#define HLP_MATCH_WILDCARD (~1U)
#define HLP_MATCH_NONE 0
-static int matchHelpTopicName (TOPIC *topic, const char *token)
+static size_t matchHelpTopicName (TOPIC *topic, const char *token)
{
size_t i, match;
char cbuf[CBUFSIZE], *cptr;
if (!strcmp (token, "*"))
return HLP_MATCH_WILDCARD;
match = 0;
for (i = 0; i < topic->kids; i++) {
- strcpy (cbuf,topic->children[i]->title +
- ((topic->children[i]->flags & HLP_MAGIC_TOPIC)? 1 : 0));
+ strlcpy (cbuf,topic->children[i]->title +
+ ((topic->children[i]->flags & HLP_MAGIC_TOPIC)? 1 : 0),
+ sizeof (cbuf));
cptr = cbuf;
while (*cptr) {
if (blankch (*cptr)) {
*cptr++ = '_';
}
@@ -13717,12 +14302,13 @@
char *p;
char tbuf[CBUFSIZE];
fprintf (st, "\n Additional information available:\n\n");
for (i = 0; i < topic->kids; i++) {
- strcpy (tbuf, topic->children[i]->title +
- ((topic->children[i]->flags & HLP_MAGIC_TOPIC)? 1 : 0));
+ strlcpy (tbuf, topic->children[i]->title +
+ ((topic->children[i]->flags & HLP_MAGIC_TOPIC)? 1 : 0),
+ sizeof (tbuf));
for (p = tbuf; *p; p++) {
if (blankch (*p))
*p = '_';
}
w += 4 + topic->kidwid;
@@ -14318,12 +14904,38 @@
while (*cptr && strchr (OctalDigits, *cptr))
*buf++ = *cptr++;
*buf = '\0';
}
else { /* Decimal Number */
- while (isdigit (*cptr))
- *buf++ = *cptr++;
+ int digits = 0;
+ int commas = 0;
+ const char *cp = cptr;
+
+ /* Ignore commas in decimal numbers */
+ while (isdigit (*cp) || (*cp == ',')) {
+ if (*cp == ',')
+ ++commas;
+ else
+ ++digits;
+ ++cp;
+ }
+ if ((commas > 0) && (commas != (digits - 1)/3)) {
+ *stat = SCPE_INVEXPR;
+ return cptr;
+ }
+ while (commas--) {
+ cp -= 4;
+ if (*cp != ',') {
+ *stat = SCPE_INVEXPR;
+ return cptr;
+ }
+ }
+ while (isdigit (*cptr) || (*cptr == ',')) {
+ if (*cptr != ',')
+ *buf++ = *cptr;
+ ++cptr;
+ }
*buf = '\0';
}
}
}
if (sim_isalpha (*cptr)) { /* Numbers can't be followed by alpha character */
@@ -14639,50 +15251,265 @@
sim_rand_seed = a * lo - r * hi;
if (sim_rand_seed < 0)
sim_rand_seed += RAND_MAX + 1;
return (sim_rand_seed - 1);
}
+
+
+typedef struct MFILE {
+ char *buf;
+ size_t pos;
+ size_t size;
+ } MFILE;
+
+static int Mprintf (MFILE *f, const char* fmt, ...)
+{
+ va_list arglist;
+ int len;
+
+ while (f) {
+ size_t buf_space = (f->size - f->pos);
+
+ va_start (arglist, fmt);
+#if defined(NO_vsnprintf)
+ len = vsprintf (f->buf + f->pos, fmt, arglist);
+#else /* !defined(NO_vsnprintf) */
+ len = vsnprintf (f->buf + f->pos, buf_space, fmt, arglist);
+#endif /* NO_vsnprintf */
+ va_end (arglist);
+
+ if ((len < 0) || (len >= (int)buf_space)) {
+ f->size *= 2;
+ buf_space = (f->size - f->pos);
+ if ((int)buf_space < len + 2)
+ f->size += len + 2;
+ f->buf = (char *)realloc (f->buf, f->size + 1);
+ if (f->buf == NULL) /* out of memory */
+ return -1;
+ f->buf[f->size-1] = '\0';
+ continue;
+ }
+ f->pos += len;
+ break;
+ }
+return 0;
+}
+
+static MFILE *
+MOpen ()
+{
+return (MFILE *)calloc (1, sizeof (MFILE));
+}
+
+void
+MFlush (MFILE *f)
+{
+f->pos = 0;
+}
+
+static int
+FMwrite (FILE *fout, MFILE *fdata)
+{
+int ret = fwrite (fdata->buf, 1, fdata->pos, fout);
+
+MFlush (fdata);
+return ret;
+}
+
+static void
+MClose (MFILE *f)
+{
+free (f->buf);
+free (f);
+}
+
+/*
+ * This sanity check walks through the all of simulator's device registers
+ * to verify that each contains a reasonable description of a method to
+ * access the devices simulator data and that description stays within the
+ * device state variables it is supposed to reference.
+ */
+
+static t_stat sim_sanity_check_register_declarations (void)
+{
+t_stat stat = SCPE_OK;
+int i;
+DEVICE *dptr;
+MFILE *f = MOpen ();
+
+for (i = 0; (dptr = sim_devices[i]) != NULL; i++) {
+ REG *rptr;
+
+ for (rptr = dptr->registers; (rptr != NULL) && (rptr->name != NULL); rptr++) {
+ uint32 bytes = 1;
+ uint32 rsz = SZ_R(rptr);
+ uint32 memsize = ((rptr->flags & REG_FIT) || (rptr->depth > 1)) ? rptr->depth * rsz : 4;
+ DEVICE *udptr = NULL;
+ t_bool Bad;
+
+ while ((bytes << 3) < rptr->offset + rptr->width)
+ bytes <<= 1;
+
+ if (rptr->depth > 1)
+ bytes = rptr->ele_size;
+
+ if (rptr->flags & REG_UNIT) {
+ DEVICE **d;
+
+ for (d = sim_devices; *d != NULL; d++) {
+ if (((UNIT *)rptr->loc >= (*d)->units) &&
+ ((UNIT *)rptr->loc < (*d)->units + (*d)->numunits)) {
+ udptr = *d;
+ break;
+ }
+ }
+ }
+ if (((rptr->width + rptr->offset + CHAR_BIT - 1) / CHAR_BIT) >= sizeof(size_map) / sizeof(size_map[0])) {
+ Bad = TRUE;
+ rsz = 0;
+ }
+ else {
+ Bad = FALSE;
+ rsz = SZ_R(rptr);
+ }
+
+ if (sim_switches & SWMASK ('R')) /* Debug output */
+ sim_printf ("%5s:%-9.9s %s(rdx=%u, wd=%u, off=%u, dep=%u, strsz=%u, objsz=%u, elesz=%u, rsz=%u, %s %s%s%s membytes=%u)\n", dptr->name, rptr->name, rptr->macro,
+ rptr->radix, rptr->width, rptr->offset, rptr->depth, (uint32)rptr->str_size, (uint32)rptr->obj_size, (uint32)rptr->ele_size, rsz, rptr->desc ? rptr->desc : "",
+ (rptr->flags & REG_FIT) ? "REG_FIT" : "", (rptr->flags & REG_VMIO) ? " REG_VMIO" : "", (rptr->flags & REG_STRUCT) ? " REG_STRUCT" : "",
+ memsize);
+
+ MFlush (f);
+ if (rptr->depth == 1) {
+ if (rptr->offset)
+ Mprintf (f, "%s %s:%s used the %s macro to describe a %u bit%s wide field at offset %u\n", sim_name, dptr->name, rptr->name, rptr->macro, rptr->width, (rptr->width == 1) ? "" : "s", rptr->offset);
+ else
+ Mprintf (f, "%s %s:%s used the %s macro to describe a %u bit wide field\n", sim_name, dptr->name, rptr->name, rptr->macro, rptr->width);
+ }
+ else
+ Mprintf (f, "%s %s:%s used the %s macro to describe a %u bit%s wide and %u elements deep array\n", sim_name, dptr->name, rptr->name, rptr->macro, rptr->width, (rptr->width == 1) ? "" : "s", rptr->depth);
+ if (rsz > sizeof (t_value)) {
+ Bad = TRUE;
+ Mprintf (f, "%u bits at offset %u is wider than the maximum allowed width of %u bits\n", rptr->width, rptr->offset, (uint32)(8 * sizeof(t_value)));
+ }
+ if ((rptr->obj_size != 0) && (rptr->ele_size != 0) && (rptr->depth != 0) && (rptr->macro != NULL)) {
+ if (rptr->flags & REG_UNIT) {
+ if (udptr == NULL) {
+ Bad = TRUE;
+ Mprintf (f, "\tthe indicated UNIT can't be found for this %u depth array\n", rptr->depth);
+ }
+ else {
+ if (rptr->depth > udptr->numunits) {
+ Bad = TRUE;
+ Mprintf (f, "\tthe depth of the UNIT array exceeds the number of units on the %s device which is %u\n", dptr->name, udptr->numunits);
+ }
+ if (rptr->obj_size > sizeof (t_value)) {
+ Bad = TRUE;
+ Mprintf (f, "\t%u is larger than the size of the t_value type (%u)\n", (uint32)rptr->obj_size, (uint32)sizeof (t_value));
+ }
+ }
+ }
+ else {
+ bytes *= rptr->depth;
+ if (!Bad)
+ if ((rsz * rptr->depth == rptr->obj_size) ||
+ ((rptr->flags & REG_STRUCT) && (rsz <= rptr->obj_size)) ||
+ ((rptr->depth == 1) &&
+ ((rptr->obj_size == sizeof (t_value)) || (rsz < rptr->obj_size))) ||
+ ((rptr->depth != 1) && (bytes == rptr->obj_size)) ||
+ ((rptr->depth != 1) && (rptr->offset == 0) && (rptr->width == 8) &&
+ ((rptr->depth == rptr->obj_size) || (rptr->depth == rptr->obj_size - 1))) ||
+ ((rptr->depth != 1) && (rptr->offset == 0) && (rptr->obj_size == rptr->ele_size)))
+ continue;
+ Bad = TRUE;
+ Mprintf (f, "\ttherefore SAVE/RESTORE operations will affect %u byte%s of memory\n", bytes, (bytes != 1) ? "s" : "");
+ Mprintf (f, "\twhile the variable lives in %u bytes of memory\n", (uint32)rptr->obj_size);
+ }
+ }
+ else
+ Mprintf (f, "\tthis register entry is not properly initialized\n");
+ if (Bad) {
+ FMwrite (stdout, f);
+ stat = SCPE_IERR;
+ }
+ }
+ }
+MClose (f);
+return stat;
+}
+
/*
* Compiled in unit tests for the various device oriented library
* modules: sim_card, sim_disk, sim_tape, sim_ether, sim_tmxr, etc.
*/
-static t_stat sim_library_unit_tests (void)
+t_stat test_lib_cmd (int32 flag, CONST char *cptr)
{
int i;
+int bad_regs = 0;
DEVICE *dptr;
int32 saved_switches = sim_switches & ~SWMASK ('T');
t_stat stat = SCPE_OK;
+char gbuf[CBUFSIZE];
+cptr = get_glyph (cptr, gbuf, 0);
+if (gbuf[0] == '\0')
+ strcpy (gbuf, "ALL");
if (sim_switches & SWMASK ('D')) {
sim_switches &= ~(SWMASK ('D') | SWMASK ('R') | SWMASK ('F') | SWMASK ('T'));
sim_set_debon (0, "STDOUT");
sim_switches = saved_switches;
}
for (i = 0; (dptr = sim_devices[i]) != NULL; i++) {
t_stat tstat = SCPE_OK;
-
- sim_switches = saved_switches;
- switch (DEV_TYPE(dptr)) {
-#if defined(USE_SIM_CARD)
- case DEV_CARD:
- tstat = sim_card_test (dptr);
- break;
-#endif
- case DEV_DISK:
- tstat = sim_disk_test (dptr);
- break;
- case DEV_ETHER:
- tstat = sim_ether_test (dptr);
- break;
- case DEV_TAPE:
- tstat = sim_tape_test (dptr);
- break;
- default:
- break;
- }
- if (tstat != SCPE_OK)
- stat = tstat;
+ t_bool was_disabled = ((dptr->flags & DEV_DIS) != 0);
+
+ if ((strcmp (gbuf, "ALL") != 0) && (strcmp (gbuf, dptr->name) != 0))
+ continue;
+ if (DEV_TYPE(dptr) == 0) {
+ sim_printf ("Skipping %s - non library device type\n", dptr->name);
+ continue; /* skip unspecified devices */
+ }
+ sim_switches = saved_switches;
+ if (was_disabled)
+ tstat = set_dev_enbdis (dptr, NULL, 1, NULL);
+ if (tstat == SCPE_OK) {
+ switch (DEV_TYPE(dptr)) {
+#if defined(USE_SIM_CARD)
+ case DEV_CARD:
+ tstat = sim_card_test (dptr);
+ break;
+#endif
+ case DEV_DISK:
+ tstat = sim_disk_test (dptr);
+ break;
+ case DEV_ETHER:
+ tstat = sim_ether_test (dptr);
+ break;
+ case DEV_TAPE:
+ tstat = sim_tape_test (dptr);
+ break;
+ case DEV_MUX:
+ tstat = tmxr_sock_test (dptr);
+ break;
+ default:
+ break;
+ }
+ if (was_disabled)
+ set_dev_enbdis (dptr, NULL, 0, NULL);
+ }
+ else
+ tstat = SCPE_OK; /* can't enable, just skip device */
+ if (tstat != SCPE_OK) {
+ stat = tstat;
+ sim_printf ("%s device tests returned: %d - %s\n", dptr->name, tstat, sim_error_text (tstat));
+ if (sim_ttisatty()) {
+ if (get_yn ("Continue with additional tests? [N]", SCPE_STOP) == SCPE_STOP)
+ break;
+ }
+ else
+ break;
+ }
}
return stat;
}
Index: src/SIMH/scp.h
==================================================================
--- src/SIMH/scp.h
+++ src/SIMH/scp.h
@@ -93,10 +93,13 @@
t_stat pwd_cmd (int32 flg, CONST char *cptr);
t_stat dir_cmd (int32 flg, CONST char *cptr);
t_stat type_cmd (int32 flg, CONST char *cptr);
t_stat delete_cmd (int32 flg, CONST char *cptr);
t_stat copy_cmd (int32 flg, CONST char *cptr);
+t_stat rename_cmd (int32 flg, CONST char *cptr);
+t_stat mkdir_cmd (int32 flg, CONST char *cptr);
+t_stat rmdir_cmd (int32 flg, CONST char *cptr);
t_stat brk_cmd (int32 flag, CONST char *ptr);
t_stat do_cmd (int32 flag, CONST char *ptr);
t_stat goto_cmd (int32 flag, CONST char *ptr);
t_stat return_cmd (int32 flag, CONST char *ptr);
t_stat shift_cmd (int32 flag, CONST char *ptr);
@@ -111,10 +114,12 @@
t_stat screenshot_cmd (int32 flag, CONST char *ptr);
t_stat spawn_cmd (int32 flag, CONST char *ptr);
t_stat echo_cmd (int32 flag, CONST char *ptr);
t_stat echof_cmd (int32 flag, CONST char *ptr);
t_stat debug_cmd (int32 flag, CONST char *ptr);
+t_stat runlimit_cmd (int32 flag, CONST char *ptr);
+t_stat test_lib_cmd (int32 flag, CONST char *ptr);
/* Allow compiler to help validate printf style format arguments */
#if !defined __GNUC__
#define GCC_FMT_ATTR(n, m)
#endif
@@ -136,10 +141,11 @@
t_stat sim_activate_after_abs_d (UNIT *uptr, double usecs_walltime);
t_stat _sim_activate_after_abs (UNIT *uptr, double usecs_walltime);
t_stat sim_cancel (UNIT *uptr);
t_bool sim_is_active (UNIT *uptr);
int32 sim_activate_time (UNIT *uptr);
+int32 _sim_activate_queue_time (UNIT *uptr);
int32 _sim_activate_time (UNIT *uptr);
double sim_activate_time_usecs (UNIT *uptr);
t_stat sim_run_boot_prep (int32 flag);
double sim_gtime (void);
uint32 sim_grtime (void);
@@ -326,11 +332,11 @@
#endif
#ifdef CANT_USE_MACRO_VA_ARGS
#define _sim_debug_device sim_debug
void sim_debug (uint32 dbits, DEVICE* dptr, const char *fmt, ...) GCC_FMT_ATTR(3, 4);
#define _sim_debug_unit sim_debug_unit
-void sim_debug_unit (uint32 dbits, DEVICE* dptr, const char *fmt, ...) GCC_FMT_ATTR(3, 4);
+void sim_debug_unit (uint32 dbits, UNIT* uptr, const char *fmt, ...) GCC_FMT_ATTR(3, 4);
#else
void _sim_debug_unit (uint32 dbits, UNIT *uptr, const char* fmt, ...) GCC_FMT_ATTR(3, 4);
void _sim_debug_device (uint32 dbits, DEVICE* dptr, const char* fmt, ...) GCC_FMT_ATTR(3, 4);
#define sim_debug(dbits, dptr, ...) do { if (sim_deb && dptr && ((dptr)->dctrl & (dbits))) _sim_debug_device (dbits, dptr, __VA_ARGS__);} while (0)
#define sim_debug_unit(dbits, uptr, ...) do { if (sim_deb && uptr && (((uptr)->dctrl | (uptr)->dptr->dctrl) & (dbits))) _sim_debug_unit (dbits, uptr, __VA_ARGS__);} while (0)
@@ -383,11 +389,12 @@
extern uint32 sim_brk_types; /* breakpoint info */
extern uint32 sim_brk_dflt;
extern uint32 sim_brk_summ;
extern uint32 sim_brk_match_type;
extern t_addr sim_brk_match_addr;
-extern BRKTYPTAB *sim_brk_type_desc; /* type descriptions */
+extern BRKTYPTAB *sim_brk_type_desc; /* type descriptions */
+extern const char *sim_prog_name; /* executable program name */
extern FILE *stdnul;
extern t_bool sim_asynch_enabled;
#if defined(SIM_ASYNCH_IO)
int sim_aio_update_queue (void);
void sim_aio_activate (ACTIVATE_API caller, UNIT *uptr, int32 event_time);
@@ -394,10 +401,12 @@
#endif
/* VM interface */
extern char sim_name[64];
+extern const char *sim_vm_release;
+extern const char *sim_vm_release_message;
extern DEVICE *sim_devices[];
extern REG *sim_PC;
extern const char *sim_stop_messages[SCPE_BASE];
extern t_stat sim_instr (void);
extern t_stat sim_load (FILE *ptr, CONST char *cptr, CONST char *fnam, int flag);
@@ -408,20 +417,25 @@
int32 sw);
/* The per-simulator init routine is a weak global that defaults to NULL
The other per-simulator pointers can be overrriden by the init routine */
-WEAK extern void (*sim_vm_init) (void);
+extern void (*sim_vm_init) (void);
extern char *(*sim_vm_read) (char *ptr, int32 size, FILE *stream);
extern void (*sim_vm_post) (t_bool from_scp);
extern CTAB *sim_vm_cmd;
extern void (*sim_vm_sprint_addr) (char *buf, DEVICE *dptr, t_addr addr);
extern void (*sim_vm_fprint_addr) (FILE *st, DEVICE *dptr, t_addr addr);
extern t_addr (*sim_vm_parse_addr) (DEVICE *dptr, CONST char *cptr, CONST char **tptr);
extern t_bool (*sim_vm_fprint_stopped) (FILE *st, t_stat reason);
extern t_value (*sim_vm_pc_value) (void);
extern t_bool (*sim_vm_is_subroutine_call) (t_addr **ret_addrs);
+extern const char **sim_clock_precalibrate_commands;
+extern int32 sim_vm_initial_ips; /* base estimate of simulated instructions per second */
+extern const char *sim_vm_interval_units; /* Simulator can change this - default "instructions" */
+extern const char *sim_vm_step_unit; /* Simulator can change this - default "instruction" */
+
/* Core SCP libraries can potentially have unit test routines.
These defines help implement consistent unit test functionality */
#define SIM_TEST_INIT \
Index: src/SIMH/sim_card.c
==================================================================
--- src/SIMH/sim_card.c
+++ src/SIMH/sim_card.c
@@ -139,11 +139,11 @@
0xA02, 0xA01, 0xD00, 0xC80, 0xC40, 0xC20, 0xC10, 0xC08,
/* p q r s t u v w */
0xC04, 0xC02, 0xC01, 0x680, 0x640, 0x620, 0x610, 0x608,
/* x y z { | } ~ del */
/* T79 Y78 Y79 79 */
- 0x604, 0x602, 0x601, 0x406, 0x806, 0x006, 0x005, 0xf000
+ 0x604, 0x602, 0x601, 0x406, 0x806, 0x805, 0x005, 0xf000
};
/* Set for Burrough codes */
static const uint16 ascii_to_hol_029[128] = {
/* Control */
@@ -597,16 +597,16 @@
}
struct _card_buffer {
- char buffer[8192+500]; /* Buffer data */
+ uint8 buffer[8192+500]; /* Buffer data */
int len; /* Amount of data in buffer */
int size; /* Size of last card read */
};
-static int _cmpcard(const char *p, const char *s) {
+static int _cmpcard(const uint8 *p, const char *s) {
int i;
if (p[0] != '~')
return 0;
for(i = 0; i < 3; i++) {
if (tolower(p[i+1]) != s[i])
@@ -627,11 +627,11 @@
if ((uptr->flags & UNIT_CARD_MODE) == MODE_AUTO) {
mode = MODE_TEXT; /* Default is text */
/* Check buffer to see if binary card in it. */
for (i = 0, temp = 0; i < 160 && i len; i+=2)
- temp |= buf->buffer[i];
+ temp |= (uint16)(buf->buffer[i] & 0xFF);
/* Check if every other char < 16 & full buffer */
if ((temp & 0x0f) == 0 && i == 160)
mode = MODE_BIN; /* Probably binary */
/* Check if maybe BCD or CBN */
if (buf->buffer[0] & 0x80) {
@@ -787,13 +787,13 @@
(*image)[0] = CARD_ERR;
return SCPE_OPENERR;
}
/* Move data to buffer */
for (col = i = 0; i < 160;) {
- temp |= buf->buffer[i];
+ temp |= (uint16)(buf->buffer[i] & 0xff);
(*image)[col] = (buf->buffer[i++] >> 4) & 0xF;
- (*image)[col++] |= ((uint16)buf->buffer[i++]) << 4;
+ (*image)[col++] |= ((uint16)buf->buffer[i++] & 0xff) << 4;
}
/* Check if format error */
if (temp & 0xF)
(*image)[0] |= CARD_ERR;
@@ -800,12 +800,12 @@
break;
case MODE_CBN:
sim_debug(DEBUG_CARD, dptr, "cbn\n");
/* Check if first character is a tape mark */
- if (((uint8)buf->buffer[0]) == 0217 &&
- (buf->len == 1 || (((uint8)buf->buffer[1]) & 0200) != 0)) {
+ if (buf->buffer[0] == 0217 &&
+ (buf->len == 1 || (buf->buffer[1] & 0200) != 0)) {
i = 1;
(*image)[0] |= CARD_EOF;
break;
}
@@ -842,11 +842,11 @@
break;
case MODE_BCD:
sim_debug(DEBUG_CARD, dptr, "bcd [");
/* Check if first character is a tape mark */
- if (((uint8)buf->buffer[0]) == 0217 && (((uint8)buf->buffer[1]) & 0200) != 0) {
+ if (buf->buffer[0] == 0217 && (buf->buffer[1] & 0200) != 0) {
i = 1;
(*image)[0] |= CARD_EOF;
break;
}
@@ -883,11 +883,11 @@
sim_debug(DEBUG_CARD, dptr, "ebcdic\n");
if (buf->len < 80)
(*image)[0] |= CARD_ERR;
/* Move data to buffer */
for (i = 0; i < 80 && i < buf->len; i++) {
- temp = buf->buffer[i] & 0xFF;
+ temp = (uint16)(buf->buffer[i]) & 0xFF;
(*image)[i] = ebcdic_to_hol[temp];
}
break;
}
@@ -929,13 +929,13 @@
/* Allocate space for some more cards if needed */
if (data->hopper_cards >= data->hopper_size) {
data->hopper_size += DECK_SIZE;
data->images = (uint16 (*)[1][80])realloc(data->images,
- data->hopper_size * sizeof(*(data->images)));
+ (size_t)data->hopper_size * sizeof(*(data->images)));
memset(&data->images[data->hopper_cards], 0,
- (data->hopper_size - data->hopper_cards) *
+ (size_t)(data->hopper_size - data->hopper_cards) *
sizeof(*(data->images)));
}
/* Process one card */
cards++;
@@ -959,13 +959,13 @@
if (eof) {
/* Allocate space for some more cards if needed */
if (data->hopper_cards >= data->hopper_size) {
data->hopper_size += DECK_SIZE;
data->images = (uint16 (*)[1][80])realloc(data->images,
- data->hopper_size * sizeof(*(data->images)));
+ (size_t)data->hopper_size * sizeof(*(data->images)));
memset(&data->images[data->hopper_cards], 0,
- (data->hopper_size - data->hopper_cards) *
+ (size_t)(data->hopper_size - data->hopper_cards) *
sizeof(*(data->images)));
}
/* Create empty card */
(*data->images)[data->hopper_cards][0] = CARD_EOF;
@@ -1316,11 +1316,11 @@
} else {
uptr->filename = (char *)malloc (32 + strlen (cptr));
sprintf (uptr->filename, "%s-F %s %s", (eof)?"-E ": "", fmt, cptr);
}
r = sim_messagef(SCPE_OK, "%s: %d card Deck Loaded from %s\n",
- sim_uname(uptr), data->hopper_cards - previous_cards, cptr);
+ sim_uname(uptr), (int)(data->hopper_cards - previous_cards), cptr);
} else {
if (uptr->dynflags & UNIT_ATTMULT)
uptr->flags |= UNIT_ATT;
detach_unit(uptr);
return r;
@@ -1428,26 +1428,26 @@
SIM_TEST(attach_cmd (0, cmd));
sprintf (cmd, "%s -S -E File40.deck", dptr->name);
SIM_TEST(attach_cmd (0, cmd));
sprintf (saved_filename, "%s %s", dptr->name, dptr->units->filename);
show_cmd (0, dptr->name);
-sim_printf ("Input Hopper Count: %d\n", sim_card_input_hopper_count(dptr->units));
-sim_printf ("Output Hopper Count: %d\n", sim_card_output_hopper_count(dptr->units));
+sim_printf ("Input Hopper Count: %d\n", (int)sim_card_input_hopper_count(dptr->units));
+sim_printf ("Output Hopper Count: %d\n", (int)sim_card_output_hopper_count(dptr->units));
while (!sim_card_eof (dptr->units))
SIM_TEST(sim_read_card (dptr->units, card_image));
-sim_printf ("Input Hopper Count: %d\n", sim_card_input_hopper_count(dptr->units));
-sim_printf ("Output Hopper Count: %d\n", sim_card_output_hopper_count(dptr->units));
+sim_printf ("Input Hopper Count: %d\n", (int)sim_card_input_hopper_count(dptr->units));
+sim_printf ("Output Hopper Count: %d\n", (int)sim_card_output_hopper_count(dptr->units));
sim_printf ("Detaching %s\n", dptr->name);
SIM_TEST(detach_cmd (0, dptr->name));
show_cmd (0, dptr->name);
-sim_printf ("Input Hopper Count: %d\n", sim_card_input_hopper_count(dptr->units));
-sim_printf ("Output Hopper Count: %d\n", sim_card_output_hopper_count(dptr->units));
+sim_printf ("Input Hopper Count: %d\n", (int)sim_card_input_hopper_count(dptr->units));
+sim_printf ("Output Hopper Count: %d\n", (int)sim_card_output_hopper_count(dptr->units));
sim_printf ("Attaching Saved Filenames: %s\n", saved_filename + strlen(dptr->name));
SIM_TEST(attach_cmd (0, saved_filename));
show_cmd (0, dptr->name);
-sim_printf ("Input Hopper Count: %d\n", sim_card_input_hopper_count(dptr->units));
-sim_printf ("Output Hopper Count: %d\n", sim_card_output_hopper_count(dptr->units));
+sim_printf ("Input Hopper Count: %d\n", (int)sim_card_input_hopper_count(dptr->units));
+sim_printf ("Output Hopper Count: %d\n", (int)sim_card_output_hopper_count(dptr->units));
SIM_TEST(detach_cmd (0, dptr->name));
(void)remove ("file10.deck");
(void)remove ("file20.deck");
(void)remove ("file30.deck");
(void)remove ("file40.deck");
Index: src/SIMH/sim_console.c
==================================================================
--- src/SIMH/sim_console.c
+++ src/SIMH/sim_console.c
@@ -180,10 +180,17 @@
#if defined (_WIN32) || defined (__OS2__) || (defined (__MWERKS__) && defined (macintosh))
int32 sim_del_char = '\b'; /* delete character */
#else
int32 sim_del_char = 0177;
#endif
+t_bool sim_signaled_int_char /* WRU character detected by signal while running */
+#if defined (_WIN32) || defined (_VMS) || defined (__CYGWIN__)
+ = FALSE;
+#else
+ = TRUE;
+#endif
+uint32 sim_last_poll_kbd_time; /* time when sim_poll_kbd was called */
extern TMLN *sim_oline; /* global output socket */
static t_stat sim_con_poll_svc (UNIT *uptr); /* console connection poll routine */
static t_stat sim_con_reset (DEVICE *dptr); /* console reset routine */
static t_stat sim_con_attach (UNIT *uptr, CONST char *ptr); /* console attach routine (save,restore) */
@@ -428,11 +435,11 @@
return SCPE_OK;
}
while (*cptr != 0) {
cptr = get_glyph (cptr, gbuf, ','); /* get modifier */
if ((shptr = find_shtab (show_con_tab, gbuf)))
- shptr->action (st, dptr, uptr, shptr->arg, cptr);
+ shptr->action (st, dptr, uptr, shptr->arg, NULL);
else return SCPE_NOPARAM;
}
return SCPE_OK;
}
@@ -630,13 +637,13 @@
if (rem->smp_reg_count) {
uint32 reg;
DEVICE *dptr = NULL;
if (rem->smp_sample_dither_pct)
- fprintf (st, "Register Bit Sampling is occurring every %d cycles (dithered %d percent)\n", rem->smp_sample_interval, rem->smp_sample_dither_pct);
+ fprintf (st, "Register Bit Sampling is occurring every %d %s (dithered %d percent)\n", rem->smp_sample_interval, sim_vm_interval_units, rem->smp_sample_dither_pct);
else
- fprintf (st, "Register Bit Sampling is occurring every %d cycles\n", rem->smp_sample_interval);
+ fprintf (st, "Register Bit Sampling is occurring every %d %s\n", rem->smp_sample_interval, sim_vm_interval_units);
fprintf (st, " Registers being sampled are: ");
for (reg = 0; reg < rem->smp_reg_count; reg++) {
if (rem->smp_regs[reg].indirect)
fprintf (st, " indirect ");
if (dptr != rem->smp_regs[reg].dptr)
@@ -2200,17 +2207,18 @@
return SCPE_2MARG;
sim_set_logoff (0, NULL); /* close cur log */
r = sim_open_logfile (gbuf, FALSE, &sim_log, &sim_log_ref); /* open log */
if (r != SCPE_OK) /* error? */
return r;
-if (!sim_quiet)
+if ((!sim_quiet) && (!(sim_switches & SWMASK ('Q'))))
fprintf (stdout, "Logging to file \"%s\"\n",
sim_logfile_name (sim_log, sim_log_ref));
fprintf (sim_log, "Logging to file \"%s\"\n",
sim_logfile_name (sim_log, sim_log_ref)); /* start of log */
time(&now);
-fprintf (sim_log, "Logging to file \"%s\" at %s", sim_logfile_name (sim_log, sim_log_ref), ctime(&now));
+if ((!sim_quiet) && (!(sim_switches & SWMASK ('Q'))))
+ fprintf (sim_log, "Logging to file \"%s\" at %s", sim_logfile_name (sim_log, sim_log_ref), ctime(&now));
return SCPE_OK;
}
/* Set nolog routine */
@@ -2218,11 +2226,11 @@
{
if (cptr && (*cptr != 0)) /* now eol? */
return SCPE_2MARG;
if (sim_log == NULL) /* no log? */
return SCPE_OK;
-if (!sim_quiet)
+if ((!sim_quiet) && (!(sim_switches & SWMASK ('Q'))))
fprintf (stdout, "Log file closed\n");
fprintf (sim_log, "Log file closed\n");
sim_close_logfile (&sim_log_ref); /* close log */
sim_log = NULL;
return SCPE_OK;
@@ -2832,10 +2840,11 @@
t_stat sim_poll_kbd (void)
{
t_stat c;
+sim_last_poll_kbd_time = sim_os_msec (); /* record when this poll happened */
if (sim_send_poll_data (&sim_con_send, &c)) /* injected input characters available? */
return c;
if (!sim_rem_master_mode) {
if ((sim_con_ldsc.rxbps) && /* rate limiting && */
(sim_gtime () < sim_con_ldsc.rxnexttime)) /* too soon? */
@@ -4208,11 +4217,11 @@
static t_stat sim_os_putchar (int32 out)
{
char c;
c = out;
-(void)write (1, &c, 1);
+if (write (1, &c, 1)) {};
return SCPE_OK;
}
#endif
Index: src/SIMH/sim_console.h
==================================================================
--- src/SIMH/sim_console.h
+++ src/SIMH/sim_console.h
@@ -125,16 +125,18 @@
int32 sim_tt_inpcvt (int32 c, uint32 mode);
int32 sim_tt_outcvt (int32 c, uint32 mode);
t_stat sim_tt_settabs (UNIT *uptr, int32 val, CONST char *cptr, void *desc);
t_stat sim_tt_showtabs (FILE *st, UNIT *uptr, int32 val, CONST void *desc);
-extern int32 sim_rem_cmd_active_line; /* command in progress on line # */
+extern int32 sim_rem_cmd_active_line; /* command in progress on line # */
-extern int32 sim_int_char; /* interrupt character */
-extern int32 sim_brk_char; /* break character */
-extern int32 sim_tt_pchar; /* printable character mask */
-extern int32 sim_del_char; /* delete character */
+extern int32 sim_int_char; /* interrupt character */
+extern int32 sim_brk_char; /* break character */
+extern int32 sim_tt_pchar; /* printable character mask */
+extern int32 sim_del_char; /* delete character */
+extern t_bool sim_signaled_int_char; /* WRU character detected by signal while running */
+extern uint32 sim_last_poll_kbd_time; /* time when sim_poll_kbd was called */
#ifdef __cplusplus
}
#endif
Index: src/SIMH/sim_defs.h
==================================================================
--- src/SIMH/sim_defs.h
+++ src/SIMH/sim_defs.h
@@ -144,16 +144,12 @@
#endif
#ifdef USE_REGEX
#undef USE_REGEX
#endif
-#if defined(HAVE_PCREPOSIX_H)
-#include
+#if defined(HAVE_PCRE_H)
#include
-#define USE_REGEX 1
-#elif defined(HAVE_REGEX_H)
-#include
#define USE_REGEX 1
#endif
#ifdef __cplusplus
extern "C" {
@@ -316,24 +312,43 @@
#define SIM_NOINLINE __attribute__ ((noinline))
#else
#define SIM_INLINE
#define SIM_NOINLINE
#endif
+
+/* Packed structure support */
+
+#ifdef _MSC_VER
+# define PACKED_BEGIN __pragma( pack(push, 1) )
+# define PACKED_END __pragma( pack(pop) )
+#else
+# define PACKED_BEGIN
+#if defined(_WIN32)
+# define PACKED_END __attribute__((gcc_struct, packed))
+#else
+# define PACKED_END __attribute__((packed))
+#endif
+#endif
+
/* Storage class modifier for weak link definition for sim_vm_init() */
#if defined(__cplusplus)
#if defined(__GNUC__)
#define WEAK __attribute__((weak))
#elif defined(_MSC_VER)
#define WEAK __declspec(selectany)
-#else
-#define WEAK extern
-#endif
-#else
-#define WEAK
-#endif
+#else /* !defined(__GNUC__) && !defined(_MSC_VER) */
+#define WEAK
+#endif /* __GNUC__ */
+#else /* !defined(__cplusplus) */
+#if defined(__GNUC__)
+#define WEAK __attribute__((common))
+#else /* !defined(__GNUC__) */
+#define WEAK
+#endif /* defined(__GNUC__) */
+#endif /* defined(__cplusplus) */
/* System independent definitions */
#define FLIP_SIZE (1 << 16) /* flip buf size */
#if !defined (PATH_MAX) /* usually in limits */
@@ -414,12 +429,15 @@
#define SCPE_EXPECT (SCPE_BASE + 44) /* expect matched */
#define SCPE_AMBREG (SCPE_BASE + 45) /* ambiguous register */
#define SCPE_REMOTE (SCPE_BASE + 46) /* remote console command */
#define SCPE_INVEXPR (SCPE_BASE + 47) /* invalid expression */
#define SCPE_SIGTERM (SCPE_BASE + 48) /* SIGTERM has been received */
+#define SCPE_FSSIZE (SCPE_BASE + 49) /* File System size larger than disk size */
+#define SCPE_RUNTIME (SCPE_BASE + 50) /* Run Time Limit Exhausted */
+#define SCPE_INCOMPDSK (SCPE_BASE + 51) /* Incompatible Disk Container */
-#define SCPE_MAX_ERR (SCPE_BASE + 48) /* Maximum SCPE Error Value */
+#define SCPE_MAX_ERR (SCPE_BASE + 51) /* Maximum SCPE Error Value */
#define SCPE_KFLAG 0x10000000 /* tti data flag */
#define SCPE_BREAK 0x20000000 /* tti break flag */
#define SCPE_NOMESSAGE 0x40000000 /* message display supression flag */
#define SCPE_BARE_STATUS(stat) ((stat) & ~(SCPE_NOMESSAGE|SCPE_KFLAG|SCPE_BREAK))
@@ -646,18 +664,25 @@
/* Unit dynamic flags (dynflags) */
/* These flags are only set dynamically */
-#define UNIT_ATTMULT 0000001 /* Allow multiple attach commands */
-#define UNIT_TM_POLL 0000002 /* TMXR Polling unit */
-#define UNIT_NO_FIO 0000004 /* fileref is NOT a FILE * */
-#define UNIT_DISK_CHK 0000010 /* disk data debug checking (sim_disk) */
-#define UNIT_TMR_UNIT 0000020 /* Unit registered as a calibrated timer */
-#define UNIT_TAPE_MRK 0000040 /* Tape Unit AWS Tapemark */
-#define UNIT_V_DF_TAPE 7 /* Bit offset for Tape Density reservation */
-#define UNIT_S_DF_TAPE 3 /* Bits Reserved for Tape Density */
+#define UNIT_ATTMULT 0000001 /* Allow multiple attach commands */
+#define UNIT_TM_POLL 0000002 /* TMXR Polling unit */
+#define UNIT_NO_FIO 0000004 /* fileref is NOT a FILE * */
+#define UNIT_DISK_CHK 0000010 /* disk data debug checking (sim_disk) */
+#define UNIT_TMR_UNIT 0000200 /* Unit registered as a calibrated timer */
+#define UNIT_TAPE_MRK 0000400 /* Tape Unit Tapemark */
+#define UNIT_TAPE_PNU 0001000 /* Tape Unit Position Not Updated */
+#define UNIT_V_DF_TAPE 10 /* Bit offset for Tape Density reservation */
+#define UNIT_S_DF_TAPE 3 /* Bits Reserved for Tape Density */
+#define UNIT_V_TAPE_FMT 13 /* Bit offset for Tape Format */
+#define UNIT_S_TAPE_FMT 4 /* Bits Reserved for Tape Format */
+#define UNIT_M_TAPE_FMT (((1 << UNIT_S_TAPE_FMT) - 1) << UNIT_V_TAPE_FMT)
+#define UNIT_V_TAPE_ANSI 17 /* Bit offset for ANSI Tape Type */
+#define UNIT_S_TAPE_ANSI 4 /* Bits Reserved for ANSI Tape Type */
+#define UNIT_M_TAPE_ANSI (((1 << UNIT_S_TAPE_ANSI) - 1) << UNIT_V_TAPE_ANSI)
struct BITFIELD {
const char *name; /* field name */
uint32 offset; /* starting bit */
uint32 width; /* width */
@@ -676,10 +701,13 @@
uint32 depth; /* save depth */
const char *desc; /* description */
BITFIELD *fields; /* bit fields */
uint32 qptr; /* circ q ptr */
size_t str_size; /* structure size */
+ size_t obj_size; /* sizeof(*loc) */
+ size_t ele_size; /* sizeof(**loc) or sizeof(*loc) if depth == 1 */
+ const char *macro; /* Initializer Macro Name */
/* NOTE: Flags MUST always be last since it is initialized outside of macro definitions */
uint32 flags; /* flags */
};
/* Register flags */
@@ -739,12 +767,12 @@
t_stat (*valid)(UNIT *up, int32 v, CONST char *cp, void *dp);
/* validation routine */
t_stat (*disp)(FILE *st, UNIT *up, int32 v, CONST void *dp);
/* display routine */
void *desc; /* value descriptor */
- /* REG * if MTAB_VAL */
- /* int * if not */
+ /* pointer to something needed by */
+ /* the validation and/or display routines */
const char *help; /* help string */
};
/* mtab mask flag bits */
@@ -808,11 +836,12 @@
#define EXP_TYP_CLEARALL (SWMASK ('C')) /* clear all rules after matching this rule, default is to once a rule matches, it is removed */
#define EXP_TYP_REGEX (SWMASK ('R')) /* rule pattern is a regular expression */
#define EXP_TYP_REGEX_I (SWMASK ('I')) /* regular expression pattern matching should be case independent */
#define EXP_TYP_TIME (SWMASK ('T')) /* halt delay is in microseconds instead of instructions */
#if defined(USE_REGEX)
- regex_t regex; /* compiled regular expression */
+ pcre *regex; /* compiled regular expression */
+ int re_nsub; /* regular expression sub expression count */
#endif
char *act; /* action string */
};
/* Expect Context */
@@ -879,84 +908,94 @@
*/
#define UDATA(act,fl,cap) NULL,act,NULL,NULL,NULL,0,0,(fl),0,(cap),0,NULL,0,0
/* Internal use ONLY (see below) Generic Register declaration for all fields */
-#define _REGDATANF(nm,loc,rdx,wd,off,dep,desc,flds,qptr,siz) \
- nm, (loc), (rdx), (wd), (off), (dep), (desc), (flds), (qptr), (siz)
+#define _REGDATANF(nm,loc,rdx,wd,off,dep,desc,flds,qptr,siz,elesiz,macro) \
+ nm, (loc), (rdx), (wd), (off), (dep), (desc), (flds), (qptr), (siz), sizeof(*(loc)), (elesiz), #macro
#if defined (__STDC__) || defined (_WIN32) /* Variants which depend on how macro arguments are convered to strings */
/* Generic Register declaration for all fields.
If the register structure is extended, this macro will be retained and a
new internal macro will be provided that populates the new register structure */
#define REGDATA(nm,loc,rdx,wd,off,dep,desc,flds,fl,qptr,siz) \
- _REGDATANF(#nm,&(loc),rdx,wd,off,dep,desc,flds,qptr,siz),(fl)
+ _REGDATANF(#nm,&(loc),rdx,wd,off,dep,desc,flds,qptr,siz,sizeof((loc)),REGDATA),(fl)
#define REGDATAC(nm,loc,rdx,wd,off,dep,desc,flds,fl,qptr,siz) \
- _REGDATANF(#nm,&(loc),rdx,wd,off,dep,desc,flds,qptr,siz),(fl)
+ _REGDATANF(#nm,&(loc),rdx,wd,off,dep,desc,flds,qptr,siz,sizeof((loc)),REGDATAC),(fl)
/* Right Justified Octal Register Data */
#define ORDATA(nm,loc,wd) \
- _REGDATANF(#nm,&(loc),8,wd,0,1,NULL,NULL,0,0)
+ _REGDATANF(#nm,&(loc),8,wd,0,1,NULL,NULL,0,0,sizeof((loc)),ORDATA)
#define ORDATAD(nm,loc,wd,desc) \
- _REGDATANF(#nm,&(loc),8,wd,0,1,desc,NULL,0,0)
+ _REGDATANF(#nm,&(loc),8,wd,0,1,desc,NULL,0,0,sizeof((loc)),ORDATAD)
#define ORDATADF(nm,loc,wd,desc,flds) \
- _REGDATANF(#nm,&(loc),8,wd,0,1,desc,flds,0,0)
+ _REGDATANF(#nm,&(loc),8,wd,0,1,desc,flds,0,0,sizeof((loc)),ORDATADF)
/* Right Justified Decimal Register Data */
#define DRDATA(nm,loc,wd) \
- _REGDATANF(#nm,&(loc),10,wd,0,1,NULL,NULL,0,0)
+ _REGDATANF(#nm,&(loc),10,wd,0,1,NULL,NULL,0,0,sizeof((loc)),DRDATA)
#define DRDATAD(nm,loc,wd,desc) \
- _REGDATANF(#nm,&(loc),10,wd,0,1,desc,NULL,0,0)
+ _REGDATANF(#nm,&(loc),10,wd,0,1,desc,NULL,0,0,sizeof((loc)),DRDATAD)
#define DRDATADF(nm,loc,wd,desc,flds) \
- _REGDATANF(#nm,&(loc),10,wd,0,1,desc,flds,0,0)
+ _REGDATANF(#nm,&(loc),10,wd,0,1,desc,flds,0,0,sizeof((loc)),DRDATADF)
/* Right Justified Hexadecimal Register Data */
#define HRDATA(nm,loc,wd) \
- _REGDATANF(#nm,&(loc),16,wd,0,1,NULL,NULL,0,0)
+ _REGDATANF(#nm,&(loc),16,wd,0,1,NULL,NULL,0,0,sizeof((loc)),HRDATA)
#define HRDATAD(nm,loc,wd,desc) \
- _REGDATANF(#nm,&(loc),16,wd,0,1,desc,NULL,0,0)
+ _REGDATANF(#nm,&(loc),16,wd,0,1,desc,NULL,0,0,sizeof((loc)),HRDATAD)
#define HRDATADF(nm,loc,wd,desc,flds) \
- _REGDATANF(#nm,&(loc),16,wd,0,1,desc,flds,0,0)
+ _REGDATANF(#nm,&(loc),16,wd,0,1,desc,flds,0,0,sizeof((loc)),HRDATADF)
/* Right Justified Binary Register Data */
#define BINRDATA(nm,loc,wd) \
- _REGDATANF(#nm,&(loc),2,wd,0,1,NULL,NULL,0,0)
+ _REGDATANF(#nm,&(loc),2,wd,0,1,NULL,NULL,0,0,sizeof((loc)),BINRDATA)
#define BINRDATAD(nm,loc,wd,desc) \
- _REGDATANF(#nm,&(loc),2,wd,0,1,desc,NULL,0,0)
+ _REGDATANF(#nm,&(loc),2,wd,0,1,desc,NULL,0,0,sizeof((loc)),BINRDATAD)
#define BINRDATADF(nm,loc,wd,desc,flds) \
- _REGDATANF(#nm,&(loc),2,wd,0,1,desc,flds,0,0)
+ _REGDATANF(#nm,&(loc),2,wd,0,1,desc,flds,0,0,sizeof((loc)),BINRDATADF)
/* One-bit binary flag at an arbitrary offset in a 32-bit word Register */
#define FLDATA(nm,loc,pos) \
- _REGDATANF(#nm,&(loc),2,1,pos,1,NULL,NULL,0,0)
+ _REGDATANF(#nm,&(loc),2,1,pos,1,NULL,NULL,0,0,sizeof((loc)),FLDATA)
#define FLDATAD(nm,loc,pos,desc) \
- _REGDATANF(#nm,&(loc),2,1,pos,1,desc,NULL,0,0)
+ _REGDATANF(#nm,&(loc),2,1,pos,1,desc,NULL,0,0,sizeof((loc)),FLDATAD)
#define FLDATADF(nm,loc,pos,desc,flds) \
- _REGDATANF(#nm,&(loc),2,1,pos,1,desc,flds,0,0)
+ _REGDATANF(#nm,&(loc),2,1,pos,1,desc,flds,0,0,sizeof((loc)),FLDATADF)
/* Arbitrary location and Radix Register */
#define GRDATA(nm,loc,rdx,wd,pos) \
- _REGDATANF(#nm,&(loc),rdx,wd,pos,1,NULL,NULL,0,0)
+ _REGDATANF(#nm,&(loc),rdx,wd,pos,1,NULL,NULL,0,0,sizeof((loc)),GRDATA)
#define GRDATAD(nm,loc,rdx,wd,pos,desc) \
- _REGDATANF(#nm,&(loc),rdx,wd,pos,1,desc,NULL,0,0)
+ _REGDATANF(#nm,&(loc),rdx,wd,pos,1,desc,NULL,0,0,sizeof((loc)),GRDATAD)
#define GRDATADF(nm,loc,rdx,wd,pos,desc,flds) \
- _REGDATANF(#nm,&(loc),rdx,wd,pos,1,desc,flds,0,0)
+ _REGDATANF(#nm,&(loc),rdx,wd,pos,1,desc,flds,0,0,sizeof((loc)),GRDATADF)
/* Arrayed register whose data is kept in a standard C array Register */
#define BRDATA(nm,loc,rdx,wd,dep) \
- _REGDATANF(#nm,loc,rdx,wd,0,dep,NULL,NULL,0,0)
+ _REGDATANF(#nm,&(loc),rdx,wd,0,dep,NULL,NULL,0,0,sizeof(*(loc)),BRDATA)
#define BRDATAD(nm,loc,rdx,wd,dep,desc) \
- _REGDATANF(#nm,loc,rdx,wd,0,dep,desc,NULL,0,0)
+ _REGDATANF(#nm,&(loc),rdx,wd,0,dep,desc,NULL,0,0,sizeof(*(loc)),BRDATAD)
#define BRDATADF(nm,loc,rdx,wd,dep,desc,flds) \
- _REGDATANF(#nm,loc,rdx,wd,0,dep,desc,flds,0,0)
+ _REGDATANF(#nm,&(loc),rdx,wd,0,dep,desc,flds,0,0,sizeof(*(loc)),BRDATADF)
+/* Range of memory whose data is successive scalar values accessed like an array Register */
+#define VBRDATA(nm,loc,rdx,wd,dep) \
+ _REGDATANF(#nm,&(loc),rdx,wd,0,dep,NULL,NULL,0,0,sizeof(loc),VBRDATA)
+#define VBRDATAD(nm,loc,rdx,wd,dep,desc) \
+ _REGDATANF(#nm,&(loc),rdx,wd,0,dep,desc,NULL,0,0,sizeof(loc),VBRDATAD)
+#define VBRDATADF(nm,loc,rdx,wd,dep,desc,flds) \
+ _REGDATANF(#nm,&(loc),rdx,wd,0,dep,desc,flds,0,0,sizeof(loc),VBRDATADF)
/* Arrayed register whose data is part of the UNIT structure */
#define URDATA(nm,loc,rdx,wd,off,dep,fl) \
- _REGDATANF(#nm,&(loc),rdx,wd,off,dep,NULL,NULL,0,0),((fl) | REG_UNIT)
+ _REGDATANF(#nm,&(loc),rdx,wd,off,dep,NULL,NULL,0,0,sizeof((loc)),URDATA),((fl) | REG_UNIT)
#define URDATAD(nm,loc,rdx,wd,off,dep,fl,desc) \
- _REGDATANF(#nm,&(loc),rdx,wd,off,dep,desc,NULL,0,0),((fl) | REG_UNIT)
+ _REGDATANF(#nm,&(loc),rdx,wd,off,dep,desc,NULL,0,0,sizeof((loc)),URDATAD),((fl) | REG_UNIT)
#define URDATADF(nm,loc,rdx,wd,off,dep,fl,desc,flds) \
- _REGDATANF(#nm,&(loc),rdx,wd,off,dep,desc,flds,0,0),((fl) | REG_UNIT)
+ _REGDATANF(#nm,&(loc),rdx,wd,off,dep,desc,flds,0,0,sizeof((loc)),URDATADF),((fl) | REG_UNIT)
/* Arrayed register whose data is part of an arbitrary structure */
#define STRDATA(nm,loc,rdx,wd,off,dep,siz,fl) \
- _REGDATANF(#nm,&(loc),rdx,wd,off,dep,NULL,NULL,0,siz),((fl) | REG_STRUCT)
+ _REGDATANF(#nm,&(loc),rdx,wd,off,dep,NULL,NULL,0,siz,sizeof((loc)),STRDATA),((fl) | REG_STRUCT)
#define STRDATAD(nm,loc,rdx,wd,off,dep,siz,fl,desc) \
- _REGDATANF(#nm,&(loc),rdx,wd,off,dep,desc,NULL,0,siz),((fl) | REG_STRUCT)
+ _REGDATANF(#nm,&(loc),rdx,wd,off,dep,desc,NULL,0,siz,sizeof((loc)),STRDATAD),((fl) | REG_STRUCT)
#define STRDATADF(nm,loc,rdx,wd,off,dep,siz,fl,desc,flds) \
- _REGDATANF(#nm,&(loc),rdx,wd,off,dep,desc,flds,0,siz),((fl) | REG_STRUCT)
+ _REGDATANF(#nm,&(loc),rdx,wd,off,dep,desc,flds,0,siz,sizeof((loc)),STRDATADF),((fl) | REG_STRUCT)
+/* Hidden Blob of Data - Only used for SAVE/RESTORE */
+#define SAVEDATA(nm,loc) \
+ _REGDATANF(#nm,&(loc),0,8,0,1,NULL,NULL,0,sizeof(loc),sizeof(loc),SAVEDATA),(REG_HRO)
#define BIT(nm) {#nm, 0xffffffff, 1, NULL, NULL} /* Single Bit definition */
#define BITNC {"", 0xffffffff, 1, NULL, NULL} /* Don't care Bit definition */
#define BITF(nm,sz) {#nm, 0xffffffff, sz, NULL, NULL} /* Bit Field definition */
#define BITNCF(sz) {"", 0xffffffff, sz, NULL, NULL} /* Don't care Bit Field definition */
#define BITFFMT(nm,sz,fmt) {#nm, 0xffffffff, sz, NULL, #fmt} /* Bit Field definition with Output format */
@@ -964,76 +1003,86 @@
#else /* For non-STD-C compiler which can't stringify macro arguments with # */
/* Generic Register declaration for all fields.
If the register structure is extended, this macro will be retained and a
new macro will be provided that populates the new register structure */
#define REGDATA(nm,loc,rdx,wd,off,dep,desc,flds,fl,qptr,siz) \
- _REGDATANF("nm",&(loc),rdx,wd,off,dep,desc,flds,qptr,siz),(fl)
+ _REGDATANF("nm",&(loc),rdx,wd,off,dep,desc,flds,qptr,siz,sizeof((loc)),REGDATA),(fl)
#define REGDATAC(nm,loc,rdx,wd,off,dep,desc,flds,fl,qptr,siz) \
- _REGDATANF("nm",&(loc),rdx,wd,off,dep,desc,flds,qptr,siz),(fl)
+ _REGDATANF("nm",&(loc),rdx,wd,off,dep,desc,flds,qptr,siz,sizeof((loc)),REGDATAC),(fl)
/* Right Justified Octal Register Data */
#define ORDATA(nm,loc,wd) \
- _REGDATANF("nm",&(loc),8,wd,0,1,NULL,NULL,0,0)
+ _REGDATANF("nm",&(loc),8,wd,0,1,NULL,NULL,0,0,sizeof((loc)),ORDATA)
#define ORDATAD(nm,loc,wd,desc) \
- _REGDATANF("nm",&(loc),8,wd,0,1,desc,NULL,0,0)
+ _REGDATANF("nm",&(loc),8,wd,0,1,desc,NULL,0,0,sizeof((loc)),ORDATAD)
#define ORDATADF(nm,loc,wd,desc,flds) \
- _REGDATANF("nm",&(loc),8,wd,0,1,desc,flds,0,0)
+ _REGDATANF("nm",&(loc),8,wd,0,1,desc,flds,0,0,sizeof((loc)),ORDATADF)
/* Right Justified Decimal Register Data */
#define DRDATA(nm,loc,wd) \
- _REGDATANF("nm",&(loc),10,wd,0,1,NULL,NULL,0,0)
+ _REGDATANF("nm",&(loc),10,wd,0,1,NULL,NULL,0,0,sizeof((loc)),DRDATA)
#define DRDATAD(nm,loc,wd,desc) \
- _REGDATANF("nm",&(loc),10,wd,0,1,desc,NULL,0,0)
+ _REGDATANF("nm",&(loc),10,wd,0,1,desc,NULL,0,0,sizeof((loc)),DRDATAD)
#define DRDATADF(nm,loc,wd,desc,flds) \
- _REGDATANF("nm",&(loc),10,wd,0,1,desc,flds,0,0)
+ _REGDATANF("nm",&(loc),10,wd,0,1,desc,flds,0,0,sizeof((loc)),DRDATADF)
/* Right Justified Hexadecimal Register Data */
#define HRDATA(nm,loc,wd) \
- _REGDATANF("nm",&(loc),16,wd,0,1,NULL,NULL,0,0)
+ _REGDATANF("nm",&(loc),16,wd,0,1,NULL,NULL,0,0,sizeof((loc)),HRDATA)
#define HRDATAD(nm,loc,wd,desc) \
- _REGDATANF("nm",&(loc),16,wd,0,1,desc,NULL,0,0)
+ _REGDATANF("nm",&(loc),16,wd,0,1,desc,NULL,0,0,sizeof((loc)),HRDATAD)
#define HRDATADF(nm,loc,wd,desc,flds) \
- _REGDATANF("nm",&(loc),16,wd,0,1,desc,flds,0,0)
+ _REGDATANF("nm",&(loc),16,wd,0,1,desc,flds,0,0,sizeof((loc)),HRDATADF)
/* Right Justified Binary Register Data */
#define BINRDATA(nm,loc,wd) \
- _REGDATANF("nm",&(loc),2,wd,0,1,NULL,NULL,0,0)
+ _REGDATANF("nm",&(loc),2,wd,0,1,NULL,NULL,0,0,sizeof((loc)),BINRDATA)
#define BINRDATAD(nm,loc,wd,desc) \
- _REGDATANF("nm",&(loc),2,wd,0,1,desc,NULL,0,0)
+ _REGDATANF("nm",&(loc),2,wd,0,1,desc,NULL,0,0,sizeof((loc)),BINRDATAD)
#define BINRDATADF(nm,loc,wd,desc,flds) \
- _REGDATANF("nm",&(loc),2,wd,0,1,desc,flds,0,0)
+ _REGDATANF("nm",&(loc),2,wd,0,1,desc,flds,0,0,sizeof((loc)),BINRDATADF)
/* One-bit binary flag at an arbitrary offset in a 32-bit word Register */
#define FLDATA(nm,loc,pos) \
- _REGDATANF("nm",&(loc),2,1,pos,1,NULL,NULL,0,0)
+ _REGDATANF("nm",&(loc),2,1,pos,1,NULL,NULL,0,0,sizeof((loc)),FLDATA)
#define FLDATAD(nm,loc,pos,desc) \
- _REGDATANF("nm",&(loc),2,1,pos,1,desc,NULL,0,0)
+ _REGDATANF("nm",&(loc),2,1,pos,1,desc,NULL,0,0,sizeof((loc)),FLDATAD)
#define FLDATADF(nm,loc,pos,desc,flds) \
- _REGDATANF("nm",&(loc),2,1,pos,1,desc,flds,0,0)
+ _REGDATANF("nm",&(loc),2,1,pos,1,desc,flds,0,0,sizeof((loc)),FLDATADF)
/* Arbitrary location and Radix Register */
#define GRDATA(nm,loc,rdx,wd,pos) \
- _REGDATANF("nm",&(loc),rdx,wd,pos,1,NULL,NULL,0,0)
+ _REGDATANF("nm",&(loc),rdx,wd,pos,1,NULL,NULL,0,0,sizeof((loc)),GRDATA)
#define GRDATAD(nm,loc,rdx,wd,pos,desc) \
- _REGDATANF("nm",&(loc),rdx,wd,pos,1,desc,NULL,0,0)
+ _REGDATANF("nm",&(loc),rdx,wd,pos,1,desc,NULL,0,0,sizeof((loc)),GRDATAD)
#define GRDATADF(nm,loc,rdx,wd,pos,desc,flds) \
- _REGDATANF("nm",&(loc),rdx,wd,pos,1,desc,flds,0,0)
+ _REGDATANF("nm",&(loc),rdx,wd,pos,1,desc,flds,0,0,sizeof((loc)),GRDATADF)
/* Arrayed register whose data is kept in a standard C array Register */
#define BRDATA(nm,loc,rdx,wd,dep) \
- _REGDATANF("nm",loc,rdx,wd,0,dep,NULL,NULL,0,0)
+ _REGDATANF("nm",&(loc),rdx,wd,0,dep,NULL,NULL,0,0,sizeof(*(loc)),BRDATA)
#define BRDATAD(nm,loc,rdx,wd,dep,desc) \
- _REGDATANF("nm",loc,rdx,wd,0,dep,desc,NULL,0,0)
+ _REGDATANF("nm",&(loc),rdx,wd,0,dep,desc,NULL,0,0,sizeof(*(loc)),BRDATAD)
#define BRDATADF(nm,loc,rdx,wd,dep,desc,flds) \
- _REGDATANF("nm",loc,rdx,wd,0,dep,desc,flds,0,0)
+ _REGDATANF("nm",&(loc),rdx,wd,0,dep,desc,flds,0,0,sizeof(*(loc)),BRDATADF)
+/* Range of memory whose data is successive scalar values accessed like an array Register */
+#define VBRDATA(nm,loc,rdx,wd,dep) \
+ _REGDATANF("nm",&(loc),rdx,wd,0,dep,NULL,NULL,0,0,sizeof(loc),VBRDATA)
+#define VBRDATAD(nm,loc,rdx,wd,dep,desc) \
+ _REGDATANF("nm",&(loc),rdx,wd,0,dep,desc,NULL,0,0,sizeof(loc),VBRDATAD)
+#define VBRDATADF(nm,loc,rdx,wd,dep,desc,flds) \
+ _REGDATANF("nm",&(loc),rdx,wd,0,dep,desc,flds,0,0,sizeof(loc),VBRDATADF)
/* Arrayed register whose data is part of the UNIT structure */
#define URDATA(nm,loc,rdx,wd,off,dep,fl) \
- _REGDATANF("nm",&(loc),rdx,wd,off,dep,NULL,NULL,0,0),((fl) | REG_UNIT)
+ _REGDATANF("nm",&(loc),rdx,wd,off,dep,NULL,NULL,0,0,sizeof((loc)),URDATA),((fl) | REG_UNIT)
#define URDATAD(nm,loc,rdx,wd,off,dep,fl,desc) \
- _REGDATANF("nm",&(loc),rdx,wd,off,dep,desc,NULL,0,0),((fl) | REG_UNIT)
+ _REGDATANF("nm",&(loc),rdx,wd,off,dep,desc,NULL,0,0,sizeof((loc)),URDATAD),((fl) | REG_UNIT)
#define URDATADF(nm,loc,rdx,wd,off,dep,fl,desc,flds) \
- _REGDATANF("nm",&(loc),rdx,wd,off,dep,desc,flds,0,0),((fl) | REG_UNIT)
+ _REGDATANF("nm",&(loc),rdx,wd,off,dep,desc,flds,0,0,sizeof((loc)),URDATADF),((fl) | REG_UNIT)
/* Arrayed register whose data is part of an arbitrary structure */
#define STRDATA(nm,loc,rdx,wd,off,dep,siz,fl) \
- _REGDATANF("nm",&(loc),rdx,wd,off,dep,NULL,NULL,0,siz),((fl) | REG_STRUCT)
+ _REGDATANF("nm",&(loc),rdx,wd,off,dep,NULL,NULL,0,siz,sizeof((loc)),STRDATA),((fl) | REG_STRUCT)
#define STRDATAD(nm,loc,rdx,wd,off,dep,siz,fl,desc) \
- _REGDATANF("nm",&(loc),rdx,wd,off,dep,desc,NULL,0,siz),((fl) | REG_STRUCT)
+ _REGDATANF("nm",&(loc),rdx,wd,off,dep,desc,NULL,0,siz,sizeof((loc)),STRDATAD),((fl) | REG_STRUCT)
#define STRDATADF(nm,loc,rdx,wd,off,dep,siz,fl,desc,flds) \
- _REGDATANF("nm",&(loc),rdx,wd,off,dep,desc,flds,0,siz),((fl) | REG_STRUCT)
+ _REGDATANF("nm",&(loc),rdx,wd,off,dep,desc,flds,0,siz,sizeof((loc)),STRDATADF),((fl) | REG_STRUCT)
+/* Hidden Blob of Data - Only used for SAVE/RESTORE */
+#define SAVEDATA(nm,loc) \
+ _REGDATANF("nm",&(loc),0,8,0,1,NULL,NULL,0,sizeof(loc),sizeof(loc)),SAVEDATA),(REG_HRO)
#define BIT(nm) {"nm", 0xffffffff, 1, NULL, NULL} /* Single Bit definition */
#define BITNC {"", 0xffffffff, 1, NULL, NULL} /* Don't care Bit definition */
#define BITF(nm,sz) {"nm", 0xffffffff, sz, NULL, NULL} /* Bit Field definition */
#define BITNCF(sz) {"", 0xffffffff, sz, NULL, NULL} /* Don't care Bit Field definition */
#define BITFFMT(nm,sz,fmt) {"nm", 0xffffffff, sz, NULL, "fmt"}/* Bit Field definition with Output format */
@@ -1078,11 +1127,15 @@
extern int32 sim_asynch_check;
extern int32 sim_asynch_latency;
extern int32 sim_asynch_inst_latency;
/* Thread local storage */
-#if defined(__GNUC__) && !defined(__APPLE__) && !defined(__hpux) && !defined(__OpenBSD__) && !defined(_AIX)
+#if defined(thread_local)
+#define AIO_TLS thread_local
+#elif (__STDC_VERSION__ >= 201112) && !(defined(__STDC_NO_THREADS__))
+#define AIO_TLS _Thread_local
+#elif defined(__GNUC__) && !defined(__APPLE__) && !defined(__hpux) && !defined(__OpenBSD__) && !defined(_AIX)
#define AIO_TLS __thread
#elif defined(_MSC_VER)
#define AIO_TLS __declspec(thread)
#else
/* Other compiler environment, then don't worry about thread local storage. */
@@ -1164,18 +1217,15 @@
/* sim_asynch_queue. This implementation is a completely lock free design */
/* which avoids the potential ABA issues. */
#define AIO_QUEUE_MODE "Lock free asynchronous event queue"
#define AIO_INIT \
do { \
- int tmr; \
sim_asynch_main_threadid = pthread_self(); \
/* Empty list/list end uses the point value (void *)1. \
This allows NULL in an entry's a_next pointer to \
indicate that the entry is not currently in any list */ \
sim_asynch_queue = QUEUE_LIST_END; \
- for (tmr=0; tmra_event_time = event_time; \
uptr->a_activate_call = (ACTIVATE_API)&caller; \
sim_asynch_queue = uptr; \
} \
if (sim_idle_wait) { \
- sim_debug (TIMER_DBG_IDLE, &sim_timer_dev, "waking due to event on %s after %d instructions\n", sim_uname(uptr), event_time);\
+ if (sim_deb) { /* only while debug do lock/unlock overhead */ \
+ AIO_UNLOCK; \
+ sim_debug (TIMER_DBG_IDLE, &sim_timer_dev, "waking due to event on %s after %d instructions\n", sim_uname(uptr), event_time);\
+ AIO_LOCK; \
+ } \
pthread_cond_signal (&sim_asynch_wake); \
} \
AIO_UNLOCK; \
sim_asynch_check = 0; \
return SCPE_OK; \
Index: src/SIMH/sim_disk.c
==================================================================
--- src/SIMH/sim_disk.c
+++ src/SIMH/sim_disk.c
@@ -31,10 +31,11 @@
25-Jan-11 MP Initial Implemementation
Public routines:
sim_disk_attach attach disk unit
+ sim_disk_attach_ex attach disk unit extended parameters
sim_disk_detach detach disk unit
sim_disk_attach_help help routine for attaching disks
sim_disk_rdsect read disk sectors
sim_disk_rdsect_a read disk sectors asynchronously
sim_disk_wrsect write disk sectors
@@ -82,22 +83,84 @@
#include
#if defined SIM_ASYNCH_IO
#include
#endif
+
+/* Newly created SIMH (and possibly RAW) disk containers */
+/* will have this data as the last 512 bytes of the container */
+/* It will not be considered part of the data in the container */
+/* Previously existing containers will have this appended to */
+/* the end of the container if they are opened for write */
+struct simh_disk_footer {
+ uint8 Signature[4]; /* must be 'simh' */
+ uint8 CreatingSimulator[64]; /* name of simulator */
+ uint8 DriveType[16];
+ uint32 SectorSize;
+ uint32 SectorCount;
+ uint32 TransferElementSize;
+ uint8 CreationTime[28]; /* Result of ctime() */
+ uint8 FooterVersion; /* Initially 0 */
+ uint8 AccessFormat; /* 1 - SIMH, 2 - RAW */
+ uint8 Reserved[382]; /* Currently unused */
+ uint32 Checksum; /* CRC32 of the prior 508 bytes */
+ };
+
+/* OS Independent Disk Virtual Disk (VHD) I/O support */
+
+#if (defined (VMS) && !(defined (__ALPHA) || defined (__ia64)))
+#define DONT_DO_VHD_SUPPORT /* VAX/VMS compilers don't have 64 bit integers */
+#endif
+
+#if defined(_WIN32) || defined (__ALPHA) || defined (__ia64) || defined (VMS)
+#ifndef __BYTE_ORDER__
+#define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__
+#endif
+#endif
+#ifndef __BYTE_ORDER__
+#define __BYTE_ORDER__ UNKNOWN
+#endif
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+static uint32
+NtoHl(uint32 value)
+{
+uint8 *l = (uint8 *)&value;
+return (uint32)l[3] | ((uint32)l[2]<<8) | ((uint32)l[1]<<16) | ((uint32)l[0]<<24);
+}
+#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+static uint32
+NtoHl(uint32 value)
+{
+return value;
+}
+#else
+static uint32
+NtoHl(uint32 value)
+{
+uint8 *l = (uint8 *)&value;
+
+if (sim_end)
+ return l[3] | (l[2]<<8) | (l[1]<<16) | (l[0]<<24);
+return value;
+}
+#endif
struct disk_context {
+ t_offset container_size; /* Size of the data portion (of the pseudo disk) */
DEVICE *dptr; /* Device for unit (access to debug flags) */
uint32 dbit; /* debugging bit */
uint32 sector_size; /* Disk Sector Size (of the pseudo disk) */
- uint32 capac_factor; /* Units of Capacity (2 = word, 1 = byte) */
+ uint32 capac_factor; /* Units of Capacity (8 = quadword, 2 = word, 1 = byte) */
uint32 xfer_element_size; /* Disk Bus Transfer size (1 - byte, 2 - word, 4 - longword) */
uint32 storage_sector_size;/* Sector size of the containing storage */
+
uint32 removable; /* Removable device flag */
uint32 is_cdrom; /* Host system CDROM Device */
uint32 media_removed; /* Media not available flag */
uint32 auto_format; /* Format determined dynamically */
+ struct simh_disk_footer
+ *footer;
#if defined _WIN32
HANDLE disk_handle; /* OS specific Raw device handle */
#endif
#if defined SIM_ASYNCH_IO
int asynch_io; /* Asynchronous Interrupt scheduling enabled */
@@ -133,11 +196,11 @@
\
pthread_mutex_lock (&ctx->io_lock); \
\
sim_debug_unit (ctx->dbit, uptr, \
"sim_disk AIO_CALL(op=%d, unit=%d, lba=0x%X, sects=%d)\n",\
- op, (int)(uptr-ctx->dptr->units), _lba, _sects);\
+ op, (int)(uptr - ctx->dptr->units), _lba, _sects);\
\
if (ctx->callback) \
abort(); /* horrible mistake, stop */ \
ctx->io_dop = op; \
ctx->lba = _lba; \
@@ -167,11 +230,11 @@
/* Boost Priority for this I/O thread vs the CPU instruction execution
thread which in general won't be readily yielding the processor when
this thread needs to run */
sim_os_set_thread_priority (PRIORITY_ABOVE_NORMAL);
-sim_debug_unit (ctx->dbit, uptr, "_disk_io(unit=%d) starting\n", (int)(uptr-ctx->dptr->units));
+sim_debug_unit (ctx->dbit, uptr, "_disk_io(unit=%d) starting\n", (int)(uptr - ctx->dptr->units));
pthread_mutex_lock (&ctx->io_lock);
pthread_cond_signal (&ctx->startup_cond); /* Signal we're ready to go */
while (ctx->asynch_io) {
pthread_cond_wait (&ctx->io_cond, &ctx->io_lock);
@@ -194,11 +257,11 @@
pthread_cond_signal (&ctx->io_done);
sim_activate (uptr, ctx->asynch_io_latency);
}
pthread_mutex_unlock (&ctx->io_lock);
-sim_debug_unit (ctx->dbit, uptr, "_disk_io(unit=%d) exiting\n", (int)(uptr-ctx->dptr->units));
+sim_debug_unit (ctx->dbit, uptr, "_disk_io(unit=%d) exiting\n", (int)(uptr - ctx->dptr->units));
return NULL;
}
/* This routine is called in the context of the main simulator thread before
@@ -215,11 +278,11 @@
static void _disk_completion_dispatch (UNIT *uptr)
{
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
DISK_PCALLBACK callback = ctx->callback;
-sim_debug_unit (ctx->dbit, uptr, "_disk_completion_dispatch(unit=%d, dop=%d, callback=%p)\n", (int)(uptr-ctx->dptr->units), ctx->io_dop, (void *)(ctx->callback));
+sim_debug_unit (ctx->dbit, uptr, "_disk_completion_dispatch(unit=%d, dop=%d, callback=%p)\n", (int)(uptr - ctx->dptr->units), ctx->io_dop, (void *)(ctx->callback));
if (ctx->io_dop != DOP_DONE)
abort(); /* horribly wrong, stop */
if (ctx->callback && ctx->io_dop == DOP_DONE) {
@@ -231,11 +294,11 @@
static t_bool _disk_is_active (UNIT *uptr)
{
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
if (ctx) {
- sim_debug_unit (ctx->dbit, uptr, "_disk_is_active(unit=%d, dop=%d)\n", (int)(uptr-ctx->dptr->units), ctx->io_dop);
+ sim_debug_unit (ctx->dbit, uptr, "_disk_is_active(unit=%d, dop=%d)\n", (int)(uptr - ctx->dptr->units), ctx->io_dop);
return (ctx->io_dop != DOP_DONE);
}
return FALSE;
}
@@ -242,11 +305,11 @@
static t_bool _disk_cancel (UNIT *uptr)
{
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
if (ctx) {
- sim_debug_unit (ctx->dbit, uptr, "_disk_cancel(unit=%d, dop=%d)\n", (int)(uptr-ctx->dptr->units), ctx->io_dop);
+ sim_debug_unit (ctx->dbit, uptr, "_disk_cancel(unit=%d, dop=%d)\n", (int)(uptr - ctx->dptr->units), ctx->io_dop);
if (ctx->asynch_io) {
pthread_mutex_lock (&ctx->io_lock);
while (ctx->io_dop != DOP_DONE)
pthread_cond_wait (&ctx->io_done, &ctx->io_lock);
pthread_mutex_unlock (&ctx->io_lock);
@@ -269,24 +332,27 @@
static FILE *sim_vhd_disk_create_diff (const char *szVHDPath, const char *szParentVHDPath);
static FILE *sim_vhd_disk_merge (const char *szVHDPath, char **ParentVHD);
static int sim_vhd_disk_close (FILE *f);
static void sim_vhd_disk_flush (FILE *f);
static t_offset sim_vhd_disk_size (FILE *f);
+static t_stat sim_vhd_disk_info (FILE *f, uint32 *sector_size, uint32 *removable, uint32 *is_cdrom);
static t_stat sim_vhd_disk_rdsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectsread, t_seccnt sects);
static t_stat sim_vhd_disk_wrsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectswritten, t_seccnt sects);
static t_stat sim_vhd_disk_clearerr (UNIT *uptr);
-static t_stat sim_vhd_disk_set_dtype (FILE *f, const char *dtype);
-static const char *sim_vhd_disk_get_dtype (FILE *f);
+static t_stat sim_vhd_disk_set_dtype (FILE *f, const char *dtype, uint32 SectorSize, uint32 xfer_element_size);
+static const char *sim_vhd_disk_get_dtype (FILE *f, uint32 *SectorSize, uint32 *xfer_element_size, char sim_name[64]);
static t_stat sim_os_disk_implemented_raw (void);
static FILE *sim_os_disk_open_raw (const char *rawdevicename, const char *openmode);
static int sim_os_disk_close_raw (FILE *f);
static void sim_os_disk_flush_raw (FILE *f);
static t_offset sim_os_disk_size_raw (FILE *f);
static t_stat sim_os_disk_unload_raw (FILE *f);
static t_bool sim_os_disk_isavailable_raw (FILE *f);
static t_stat sim_os_disk_rdsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectsread, t_seccnt sects);
+static t_stat sim_os_disk_read (UNIT *uptr, t_offset addr, uint8 *buf, uint32 *bytesread, uint32 bytes);
static t_stat sim_os_disk_wrsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectswritten, t_seccnt sects);
+static t_stat sim_os_disk_write (UNIT *uptr, t_offset addr, uint8 *buf, uint32 *byteswritten, uint32 bytes);
static t_stat sim_os_disk_info_raw (FILE *f, uint32 *sector_size, uint32 *removable, uint32 *is_cdrom);
static char *HostPathToVhdPath (const char *szHostPath, char *szVhdPath, size_t VhdPathSize);
static char *VhdPathToHostPath (const char *szVhdPath, char *szHostPath, size_t HostPathSize);
static t_offset get_filesystem_size (UNIT *uptr);
@@ -296,15 +362,15 @@
int32 fmtval; /* Format type value */
t_stat (*impl_fnc)(void); /* Implemented Test Function */
};
static struct sim_disk_fmt fmts[] = {
- { "AUTO", 0, DKUF_F_AUTO, NULL},
- { "SIMH", 0, DKUF_F_STD, NULL},
- { "RAW", 0, DKUF_F_RAW, sim_os_disk_implemented_raw},
- { "VHD", 0, DKUF_F_VHD, sim_vhd_disk_implemented},
- { NULL, 0, 0, NULL}
+ { "AUTO detect", 0, DKUF_F_AUTO, NULL},
+ { "SIMH", 0, DKUF_F_STD, NULL},
+ { "RAW", 0, DKUF_F_RAW, sim_os_disk_implemented_raw},
+ { "VHD", 0, DKUF_F_VHD, sim_vhd_disk_implemented},
+ { NULL, 0, 0, NULL}
};
/* Set disk format */
t_stat sim_disk_set_fmt (UNIT *uptr, int32 val, CONST char *cptr, void *desc)
@@ -311,14 +377,14 @@
{
uint32 f;
if (uptr == NULL)
return SCPE_IERR;
-if (cptr == NULL)
+if ((cptr == NULL) || (*cptr == '\0'))
return SCPE_ARG;
for (f = 0; fmts[f].name; f++) {
- if (fmts[f].name && (strcmp (cptr, fmts[f].name) == 0)) {
+ if (fmts[f].name && (MATCH_CMD (cptr, fmts[f].name) == 0)) {
if ((fmts[f].impl_fnc) && (fmts[f].impl_fnc() != SCPE_OK))
return SCPE_NOFNC;
uptr->flags = (uptr->flags & ~DKUF_FMT) |
(fmts[f].fmtval << DKUF_V_FMT) | fmts[f].uflags;
return SCPE_OK;
@@ -327,21 +393,25 @@
return sim_messagef (SCPE_ARG, "Unknown disk format: %s\n", cptr);
}
/* Show disk format */
-t_stat sim_disk_show_fmt (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
+static const char *sim_disk_fmt (UNIT *uptr)
{
int32 f = DK_GET_FMT (uptr);
size_t i;
for (i = 0; fmts[i].name; i++)
if (fmts[i].fmtval == f) {
- fprintf (st, "%s format", fmts[i].name);
- return SCPE_OK;
+ return fmts[i].name;
}
-fprintf (st, "invalid format");
+return "invalid";
+}
+
+t_stat sim_disk_show_fmt (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
+{
+fprintf (st, "%s format", sim_disk_fmt (uptr));
return SCPE_OK;
}
/* Set disk capacity */
@@ -424,11 +494,11 @@
break;
default:
is_available = FALSE;
break;
}
-sim_debug_unit (ctx->dbit, uptr, "sim_disk_isavailable(unit=%d)=%s\n", (int)(uptr-ctx->dptr->units), is_available ? "true" : "false");
+sim_debug_unit (ctx->dbit, uptr, "sim_disk_isavailable(unit=%d)=%s\n", (int)(uptr - ctx->dptr->units), is_available ? "true" : "false");
return is_available;
}
t_bool sim_disk_isavailable_a (UNIT *uptr, DISK_PCALLBACK callback)
{
@@ -448,26 +518,17 @@
/* Get Disk size */
t_offset sim_disk_size (UNIT *uptr)
{
+struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
t_offset physical_size, filesystem_size;
t_bool saved_quiet = sim_quiet;
-switch (DK_GET_FMT (uptr)) { /* case on format */
- case DKUF_F_STD: /* SIMH format */
- physical_size = sim_fsize_ex (uptr->fileref);
- break;
- case DKUF_F_VHD: /* VHD format */
- physical_size = sim_vhd_disk_size (uptr->fileref);
- break;
- case DKUF_F_RAW: /* Raw Physical Disk Access */
- physical_size = sim_os_disk_size_raw (uptr->fileref);
- break;
- default:
- return (t_offset)-1;
- }
+if ((uptr->flags & UNIT_ATT) == 0)
+ return (t_offset)-1;
+physical_size = ctx->container_size;
sim_quiet = TRUE;
filesystem_size = get_filesystem_size (uptr);
sim_quiet = saved_quiet;
if ((filesystem_size == (t_offset)-1) ||
(filesystem_size < physical_size))
@@ -485,11 +546,11 @@
return SCPE_NOFNC;
#else
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
pthread_attr_t attr;
-sim_debug_unit (ctx->dbit, uptr, "sim_disk_set_async(unit=%d)\n", (int)(uptr-ctx->dptr->units));
+sim_debug_unit (ctx->dbit, uptr, "sim_disk_set_async(unit=%d)\n", (int)(uptr - ctx->dptr->units));
ctx->asynch_io = sim_asynch_enabled;
ctx->asynch_io_latency = latency;
if (ctx->asynch_io) {
pthread_mutex_init (&ctx->io_lock, NULL);
@@ -522,11 +583,11 @@
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
/* make sure device exists */
if (!ctx) return SCPE_UNATT;
-sim_debug_unit (ctx->dbit, uptr, "sim_disk_clr_async(unit=%d)\n", (int)(uptr-ctx->dptr->units));
+sim_debug_unit (ctx->dbit, uptr, "sim_disk_clr_async(unit=%d)\n", (int)(uptr - ctx->dptr->units));
if (ctx->asynch_io) {
pthread_mutex_lock (&ctx->io_lock);
ctx->asynch_io = 0;
pthread_cond_signal (&ctx->io_cond);
@@ -547,35 +608,47 @@
t_offset da;
uint32 err, tbc;
size_t i;
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
-sim_debug_unit (ctx->dbit, uptr, "_sim_disk_rdsect(unit=%d, lba=0x%X, sects=%d)\n", (int)(uptr-ctx->dptr->units), lba, sects);
+sim_debug_unit (ctx->dbit, uptr, "_sim_disk_rdsect(unit=%d, lba=0x%X, sects=%d)\n", (int)(uptr - ctx->dptr->units), lba, sects);
da = ((t_offset)lba) * ctx->sector_size;
tbc = sects * ctx->sector_size;
if (sectsread)
*sectsread = 0;
-err = sim_fseeko (uptr->fileref, da, SEEK_SET); /* set pos */
-if (!err) {
- i = sim_fread (buf, ctx->xfer_element_size, tbc/ctx->xfer_element_size, uptr->fileref);
- if (i < tbc/ctx->xfer_element_size) /* fill */
- memset (&buf[i*ctx->xfer_element_size], 0, tbc-(i*ctx->xfer_element_size));
+while (tbc) {
+ size_t sectbytes;
+
+ err = sim_fseeko (uptr->fileref, da, SEEK_SET); /* set pos */
+ if (err)
+ return SCPE_IOERR;
+ i = sim_fread (buf, 1, tbc, uptr->fileref);
+ if (i < tbc) /* fill */
+ memset (&buf[i], 0, tbc-i);
+ if (sectsread)
+ *sectsread += i / ctx->sector_size;
+ sectbytes = (i / ctx->sector_size) * ctx->sector_size;
err = ferror (uptr->fileref);
- if ((!err) && (sectsread))
- *sectsread = (t_seccnt)((i*ctx->xfer_element_size+ctx->sector_size-1)/ctx->sector_size);
+ if (err)
+ return SCPE_IOERR;
+ tbc -= sectbytes;
+ if ((tbc == 0) || (i == 0))
+ return SCPE_OK;
+ da += sectbytes;
+ buf += sectbytes;
}
-return err;
+return SCPE_OK;
}
t_stat sim_disk_rdsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectsread, t_seccnt sects)
{
t_stat r;
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
t_seccnt sread = 0;
-sim_debug_unit (ctx->dbit, uptr, "sim_disk_rdsect(unit=%d, lba=0x%X, sects=%d)\n", (int)(uptr-ctx->dptr->units), lba, sects);
+sim_debug_unit (ctx->dbit, uptr, "sim_disk_rdsect(unit=%d, lba=0x%X, sects=%d)\n", (int)(uptr - ctx->dptr->units), lba, sects);
if ((sects == 1) && /* Single sector reads */
(lba >= (uptr->capac*ctx->capac_factor)/(ctx->sector_size/((ctx->dptr->flags & DEV_SECTORS) ? 512 : 1)))) {/* beyond the end of the disk */
memset (buf, '\0', ctx->sector_size); /* are bad block management efforts - zero buffer */
if (sectsread)
@@ -586,11 +659,12 @@
if ((0 == (ctx->sector_size & (ctx->storage_sector_size - 1))) || /* Sector Aligned & whole sector transfers */
((0 == ((lba*ctx->sector_size) & (ctx->storage_sector_size - 1))) &&
(0 == ((sects*ctx->sector_size) & (ctx->storage_sector_size - 1))))) {
switch (DK_GET_FMT (uptr)) { /* case on format */
case DKUF_F_STD: /* SIMH format */
- return _sim_disk_rdsect (uptr, lba, buf, sectsread, sects);
+ r = _sim_disk_rdsect (uptr, lba, buf, &sread, sects);
+ break;
case DKUF_F_VHD: /* VHD format */
r = sim_vhd_disk_rdsect (uptr, lba, buf, &sread, sects);
break;
case DKUF_F_RAW: /* Raw Physical Disk Access */
r = sim_os_disk_rdsect (uptr, lba, buf, &sread, sects);
@@ -598,18 +672,18 @@
default:
return SCPE_NOFNC;
}
if (sectsread)
*sectsread = sread;
+ sim_buf_swap_data (buf, ctx->xfer_element_size, (sread * ctx->sector_size) / ctx->xfer_element_size);
if (r != SCPE_OK)
return r;
- sim_buf_swap_data (buf, ctx->xfer_element_size, (sread * ctx->sector_size) / ctx->xfer_element_size);
return r;
}
else { /* Unaligned and/or partial sector transfers */
- uint8 *tbuf = (uint8*) malloc (sects*ctx->sector_size + 2*ctx->storage_sector_size);
- t_lba sspsts = ctx->storage_sector_size/ctx->sector_size; /* sim sectors in a storage sector */
+ uint8 *tbuf = (uint8*) malloc (sects * ctx->sector_size + 2 * ctx->storage_sector_size);
+ t_lba sspsts = ctx->storage_sector_size / ctx->sector_size; /* sim sectors in a storage sector */
t_lba tlba = lba & ~(sspsts - 1);
t_seccnt tsects = sects + (lba - tlba);
tsects = (tsects + (sspsts - 1)) & ~(sspsts - 1);
if (sectsread)
@@ -620,29 +694,24 @@
case DKUF_F_STD: /* SIMH format */
r = _sim_disk_rdsect (uptr, tlba, tbuf, &sread, tsects);
break;
case DKUF_F_VHD: /* VHD format */
r = sim_vhd_disk_rdsect (uptr, tlba, tbuf, &sread, tsects);
- if (r == SCPE_OK)
- sim_buf_swap_data (tbuf, ctx->xfer_element_size, (sread * ctx->sector_size) / ctx->xfer_element_size);
break;
case DKUF_F_RAW: /* Raw Physical Disk Access */
r = sim_os_disk_rdsect (uptr, tlba, tbuf, &sread, tsects);
- if (r == SCPE_OK)
- sim_buf_swap_data (tbuf, ctx->xfer_element_size, (sread * ctx->sector_size) / ctx->xfer_element_size);
break;
default:
free (tbuf);
return SCPE_NOFNC;
}
- if (r == SCPE_OK) {
- memcpy (buf, tbuf + ((lba - tlba) * ctx->sector_size), sects * ctx->sector_size);
- if (sectsread) {
- *sectsread = sread - (lba - tlba);
- if (*sectsread > sects)
- *sectsread = sects;
- }
+ sim_buf_swap_data (tbuf, ctx->xfer_element_size, (sread * ctx->sector_size) / ctx->xfer_element_size);
+ memcpy (buf, tbuf + ((lba - tlba) * ctx->sector_size), sects * ctx->sector_size);
+ if (sectsread) {
+ *sectsread = sread - (lba - tlba);
+ if (*sectsread > sects)
+ *sectsread = sects;
}
free (tbuf);
return r;
}
}
@@ -663,38 +732,40 @@
t_offset da;
uint32 err, tbc;
size_t i;
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
-sim_debug_unit (ctx->dbit, uptr, "_sim_disk_wrsect(unit=%d, lba=0x%X, sects=%d)\n", (int)(uptr-ctx->dptr->units), lba, sects);
+sim_debug_unit (ctx->dbit, uptr, "_sim_disk_wrsect(unit=%d, lba=0x%X, sects=%d)\n", (int)(uptr - ctx->dptr->units), lba, sects);
da = ((t_offset)lba) * ctx->sector_size;
tbc = sects * ctx->sector_size;
if (sectswritten)
*sectswritten = 0;
err = sim_fseeko (uptr->fileref, da, SEEK_SET); /* set pos */
-if (!err) {
- i = sim_fwrite (buf, ctx->xfer_element_size, tbc/ctx->xfer_element_size, uptr->fileref);
- err = ferror (uptr->fileref);
- if ((!err) && (sectswritten))
- *sectswritten = (t_seccnt)((i*ctx->xfer_element_size+ctx->sector_size-1)/ctx->sector_size);
- }
-return err;
+if (err)
+ return SCPE_IOERR;
+i = sim_fwrite (buf, ctx->xfer_element_size, tbc/ctx->xfer_element_size, uptr->fileref);
+if (sectswritten)
+ *sectswritten += (t_seccnt)((i * ctx->xfer_element_size + ctx->sector_size - 1)/ctx->sector_size);
+err = ferror (uptr->fileref);
+if (err)
+ return SCPE_IOERR;
+return SCPE_OK;
}
t_stat sim_disk_wrsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectswritten, t_seccnt sects)
{
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
uint32 f = DK_GET_FMT (uptr);
t_stat r;
uint8 *tbuf = NULL;
-sim_debug_unit (ctx->dbit, uptr, "sim_disk_wrsect(unit=%d, lba=0x%X, sects=%d)\n", (int)(uptr-ctx->dptr->units), lba, sects);
+sim_debug_unit (ctx->dbit, uptr, "sim_disk_wrsect(unit=%d, lba=0x%X, sects=%d)\n", (int)(uptr - ctx->dptr->units), lba, sects);
if (uptr->dynflags & UNIT_DISK_CHK) {
DEVICE *dptr = find_dev_from_unit (uptr);
- uint32 capac_factor = ((dptr->dwidth / dptr->aincr) == 16) ? 2 : 1; /* capacity units (word: 2, byte: 1) */
+ uint32 capac_factor = ((dptr->dwidth / dptr->aincr) >= 32) ? 8 : ((dptr->dwidth / dptr->aincr) == 16) ? 2 : 1; /* capacity units (quadword: 8, word: 2, byte: 1) */
t_lba total_sectors = (t_lba)((uptr->capac*capac_factor)/(ctx->sector_size/((dptr->flags & DEV_SECTORS) ? 512 : 1)));
t_lba sect;
for (sect = 0; sect < sects; sect++) {
t_lba offset;
@@ -1108,26 +1179,26 @@
static t_offset get_ods2_filesystem_size (UNIT *uptr)
{
DEVICE *dptr;
t_addr saved_capac;
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
-t_offset temp_capac = 512 * (t_offset)0xFFFFFFFFu; /* Make sure we can access the largest sector */
-uint32 capac_factor;
+t_offset temp_capac = (sim_toffset_64 ? (t_addr)0xFFFFFFFFu : (t_addr)0x7FFFFFFFu); /* Make sure we can access the largest sector */
ODS2_HomeBlock Home;
ODS2_FileHeader Header;
ODS2_Retreval *Retr;
ODS2_SCB Scb;
uint16 CheckSum1, CheckSum2;
uint32 ScbLbn = 0;
t_offset ret_val = (t_offset)-1;
+t_seccnt sects_read;
if ((dptr = find_dev_from_unit (uptr)) == NULL)
return ret_val;
-capac_factor = ((dptr->dwidth / dptr->aincr) == 16) ? 2 : 1; /* save capacity units (word: 2, byte: 1) */
saved_capac = uptr->capac;
-uptr->capac = (t_addr)(temp_capac/(capac_factor*((dptr->flags & DEV_SECTORS) ? 512 : 1)));
-if (sim_disk_rdsect (uptr, 512 / ctx->sector_size, (uint8 *)&Home, NULL, sizeof (Home) / ctx->sector_size))
+uptr->capac = (t_addr)temp_capac;
+if ((sim_disk_rdsect (uptr, 512 / ctx->sector_size, (uint8 *)&Home, §s_read, sizeof (Home) / ctx->sector_size)) ||
+ (sects_read != (sizeof (Home) / ctx->sector_size)))
goto Return_Cleanup;
CheckSum1 = ODSChecksum (&Home, (uint16)((((char *)&Home.hm2_w_checksum1)-((char *)&Home.hm2_l_homelbn))/2));
CheckSum2 = ODSChecksum (&Home, (uint16)((((char *)&Home.hm2_w_checksum2)-((char *)&Home.hm2_l_homelbn))/2));
if ((Home.hm2_l_homelbn == 0) ||
(Home.hm2_l_alhomelbn == 0) ||
@@ -1143,12 +1214,13 @@
(Home.hm2_w_ibmapsize == 0) ||
(Home.hm2_w_resfiles < 5) ||
(Home.hm2_w_checksum1 != CheckSum1) ||
(Home.hm2_w_checksum2 != CheckSum2))
goto Return_Cleanup;
-if (sim_disk_rdsect (uptr, (Home.hm2_l_ibmaplbn+Home.hm2_w_ibmapsize+1) * (512 / ctx->sector_size),
- (uint8 *)&Header, NULL, sizeof (Header) / ctx->sector_size))
+if ((sim_disk_rdsect (uptr, (Home.hm2_l_ibmaplbn+Home.hm2_w_ibmapsize+1) * (512 / ctx->sector_size),
+ (uint8 *)&Header, §s_read, sizeof (Header) / ctx->sector_size)) ||
+ (sects_read != (sizeof (Header) / ctx->sector_size)))
goto Return_Cleanup;
CheckSum1 = ODSChecksum (&Header, 255);
if (CheckSum1 != *(((uint16 *)&Header)+255)) /* Verify Checksum on BITMAP.SYS file header */
goto Return_Cleanup;
Retr = (ODS2_Retreval *)(((uint16*)(&Header))+Header.fh2_b_mpoffset);
@@ -1166,11 +1238,12 @@
case 3:
ScbLbn = Retr->fm2_r_map_bits3.fm2_l_lbn3;
break;
}
Retr = (ODS2_Retreval *)(((uint16 *)Retr)+Retr->fm2_r_word0_bits.fm2_v_format+1);
-if (sim_disk_rdsect (uptr, ScbLbn * (512 / ctx->sector_size), (uint8 *)&Scb, NULL, sizeof (Scb) / ctx->sector_size))
+if ((sim_disk_rdsect (uptr, ScbLbn * (512 / ctx->sector_size), (uint8 *)&Scb, §s_read, sizeof (Scb) / ctx->sector_size)) ||
+ (sects_read != (sizeof (Scb) / ctx->sector_size)))
goto Return_Cleanup;
CheckSum1 = ODSChecksum (&Scb, 255);
if (CheckSum1 != *(((uint16 *)&Scb)+255)) /* Verify Checksum on Storage Control Block */
goto Return_Cleanup;
if ((Scb.scb_w_cluster != Home.hm2_w_cluster) ||
@@ -1191,27 +1264,27 @@
static t_offset get_ods1_filesystem_size (UNIT *uptr)
{
DEVICE *dptr;
t_addr saved_capac;
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
-t_offset temp_capac = 512 * (t_offset)0xFFFFFFFFu; /* Make sure we can access the largest sector */
-uint32 capac_factor;
+t_addr temp_capac = (sim_toffset_64 ? (t_addr)0xFFFFFFFFu : (t_addr)0x7FFFFFFFu); /* Make sure we can access the largest sector */
ODS1_HomeBlock Home;
ODS1_FileHeader Header;
ODS1_Retreval *Retr;
uint8 scb_buf[512];
ODS1_SCB *Scb = (ODS1_SCB *)scb_buf;
uint16 CheckSum1, CheckSum2;
uint32 ScbLbn;
t_offset ret_val = (t_offset)-1;
+t_seccnt sects_read;
if ((dptr = find_dev_from_unit (uptr)) == NULL)
return ret_val;
-capac_factor = ((dptr->dwidth / dptr->aincr) == 16) ? 2 : 1; /* save capacity units (word: 2, byte: 1) */
saved_capac = uptr->capac;
-uptr->capac = (t_addr)(temp_capac/(capac_factor*((dptr->flags & DEV_SECTORS) ? 512 : 1)));
-if (sim_disk_rdsect (uptr, 512 / ctx->sector_size, (uint8 *)&Home, NULL, sizeof (Home) / ctx->sector_size))
+uptr->capac = temp_capac;
+if ((sim_disk_rdsect (uptr, 512 / ctx->sector_size, (uint8 *)&Home, §s_read, sizeof (Home) / ctx->sector_size)) ||
+ (sects_read != (sizeof (Home) / ctx->sector_size)))
goto Return_Cleanup;
CheckSum1 = ODSChecksum (&Home, (uint16)((((char *)&Home.hm1_w_checksum1)-((char *)&Home.hm1_w_ibmapsize))/2));
CheckSum2 = ODSChecksum (&Home, (uint16)((((char *)&Home.hm1_w_checksum2)-((char *)&Home.hm1_w_ibmapsize))/2));
if ((Home.hm1_w_ibmapsize == 0) ||
(Home.hm1_l_ibmaplbn == 0) ||
@@ -1220,31 +1293,31 @@
((Home.hm1_w_structlev != HM1_C_LEVEL1) && (Home.hm1_w_structlev != HM1_C_LEVEL2)) ||
(Home.hm1_l_ibmaplbn == 0) ||
(Home.hm1_w_checksum1 != CheckSum1) ||
(Home.hm1_w_checksum2 != CheckSum2))
goto Return_Cleanup;
-if (sim_disk_rdsect (uptr, (((Home.hm1_l_ibmaplbn << 16) + ((Home.hm1_l_ibmaplbn >> 16) & 0xFFFF)) + Home.hm1_w_ibmapsize + 1) * (512 / ctx->sector_size),
- (uint8 *)&Header, NULL, sizeof (Header) / ctx->sector_size))
+if ((sim_disk_rdsect (uptr, (((Home.hm1_l_ibmaplbn << 16) + ((Home.hm1_l_ibmaplbn >> 16) & 0xFFFF)) + Home.hm1_w_ibmapsize + 1) * (512 / ctx->sector_size),
+ (uint8 *)&Header, §s_read, sizeof (Header) / ctx->sector_size)) ||
+ (sects_read != (sizeof (Header) / ctx->sector_size)))
goto Return_Cleanup;
CheckSum1 = ODSChecksum (&Header, 255);
if (CheckSum1 != *(((uint16 *)&Header)+255)) /* Verify Checksum on BITMAP.SYS file header */
goto Return_Cleanup;
Retr = (ODS1_Retreval *)(((uint16*)(&Header))+Header.fh1_b_mpoffset);
ScbLbn = (Retr->fm1_pointers[0].fm1_s_fm1def1.fm1_b_highlbn<<16)+Retr->fm1_pointers[0].fm1_s_fm1def1.fm1_w_lowlbn;
-if (sim_disk_rdsect (uptr, ScbLbn * (512 / ctx->sector_size), (uint8 *)Scb, NULL, 512 / ctx->sector_size))
+if ((sim_disk_rdsect (uptr, ScbLbn * (512 / ctx->sector_size), (uint8 *)Scb, §s_read, 512 / ctx->sector_size)) ||
+ (sects_read != (512 / ctx->sector_size)))
goto Return_Cleanup;
if (Scb->scb_b_bitmapblks < 127)
ret_val = (((t_offset)Scb->scb_r_blocks[Scb->scb_b_bitmapblks].scb_w_freeblks << 16) + Scb->scb_r_blocks[Scb->scb_b_bitmapblks].scb_w_freeptr) * 512;
else
ret_val = (((t_offset)Scb->scb_r_blocks[0].scb_w_freeblks << 16) + Scb->scb_r_blocks[0].scb_w_freeptr) * 512;
-if (!sim_quiet) {
- sim_printf ("%s%d: '%s' Contains an ODS1 File system\n", sim_dname (dptr), (int)(uptr-dptr->units), uptr->filename);
- sim_printf ("%s%d: Volume Name: %12.12s ", sim_dname (dptr), (int)(uptr-dptr->units), Home.hm1_t_volname);
- sim_printf ("Format: %12.12s ", Home.hm1_t_format);
- sim_printf ("Sectors In Volume: %u\n", (uint32)(ret_val / 512));
- }
+sim_messagef (SCPE_OK, "%s%d: '%s' Contains an ODS1 File system\n", sim_dname (dptr), (int)(uptr-dptr->units), uptr->filename);
+sim_messagef (SCPE_OK, "%s%d: Volume Name: %12.12s ", sim_dname (dptr), (int)(uptr-dptr->units), Home.hm1_t_volname);
+sim_messagef (SCPE_OK, "Format: %12.12s ", Home.hm1_t_format);
+sim_messagef (SCPE_OK, "Sectors In Volume: %u\n", (uint32)(ret_val / 512));
Return_Cleanup:
uptr->capac = saved_capac;
return ret_val;
}
@@ -1264,24 +1337,24 @@
static t_offset get_ultrix_filesystem_size (UNIT *uptr)
{
DEVICE *dptr;
t_addr saved_capac;
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
-t_offset temp_capac = 512 * (t_offset)0xFFFFFFFFu; /* Make sure we can access the largest sector */
-uint32 capac_factor;
+t_addr temp_capac = (sim_toffset_64 ? (t_addr)0xFFFFFFFFu : (t_addr)0x7FFFFFFFu); /* Make sure we can access the largest sector */
uint8 sector_buf[512];
ultrix_disklabel *Label = (ultrix_disklabel *)(sector_buf + sizeof (sector_buf) - sizeof (ultrix_disklabel));
t_offset ret_val = (t_offset)-1;
int i;
uint32 max_lbn = 0, max_lbn_partnum = 0;
+t_seccnt sects_read;
if ((dptr = find_dev_from_unit (uptr)) == NULL)
return ret_val;
-capac_factor = ((dptr->dwidth / dptr->aincr) == 16) ? 2 : 1; /* save capacity units (word: 2, byte: 1) */
saved_capac = uptr->capac;
-uptr->capac = (t_addr)(temp_capac/(capac_factor*((dptr->flags & DEV_SECTORS) ? 512 : 1)));
-if (sim_disk_rdsect (uptr, 31 * (512 / ctx->sector_size), sector_buf, NULL, 512 / ctx->sector_size))
+uptr->capac = temp_capac;
+if ((sim_disk_rdsect (uptr, 31 * (512 / ctx->sector_size), sector_buf, §s_read, 512 / ctx->sector_size)) ||
+ (sects_read != (512 / ctx->sector_size)))
goto Return_Cleanup;
if ((Label->pt_magic != PT_MAGIC) ||
(Label->pt_valid != PT_VALID))
goto Return_Cleanup;
@@ -1291,17 +1364,707 @@
if (end_lbn > max_lbn) {
max_lbn = end_lbn;
max_lbn_partnum = i;
}
}
-if (!sim_quiet) {
- sim_printf ("%s%d: '%s' Contains Ultrix partitions\n", sim_dname (dptr), (int)(uptr-dptr->units), uptr->filename);
- sim_printf ("Partition with highest sector: %c, Sectors On Disk: %u\n", 'a' + max_lbn_partnum, max_lbn);
- }
+sim_messagef (SCPE_OK, "%s%d: '%s' Contains Ultrix partitions\n", sim_dname (dptr), (int)(uptr-dptr->units), uptr->filename);
+sim_messagef (SCPE_OK, "Partition with highest sector: %c, Sectors On Disk: %u\n", 'a' + max_lbn_partnum, max_lbn);
ret_val = ((t_offset)max_lbn) * 512;
Return_Cleanup:
+uptr->capac = saved_capac;
+return ret_val;
+}
+
+#pragma pack(push,1)
+/*
+ * The first logical block of device cluster 1 is either:
+ * 1. MFD label entry (RSTS versions through 7.x)
+ * 2. Disk Pack label (RSTS version 8.0 and later)
+ */
+typedef struct _RSTS_MFDLABEL {
+ uint16 ml_ulnk;
+ uint16 ml_mbm1;
+ uint16 ml_reserved1;
+ uint16 ml_reserved2;
+ uint16 ml_pcs;
+ uint16 ml_pstat;
+ uint16 ml_packid[2];
+ } RSTS_MFDLABEL;
+
+typedef struct _RSTS_PACKLABEL {
+ uint16 pk_mb01;
+ uint16 pk_mbm1;
+ uint16 pk_mdcn;
+ uint16 pk_plvl;
+#define PK_LVL0 0000
+#define PK_LVL11 0401
+#define PK_LVL12 0402
+ uint16 pk_ppcs;
+ uint16 pk_pstat;
+#define PK_UC_NEW 0020000
+ uint16 pk_packid[2];
+ uint16 pk_tapgvn[2];
+ uint16 pk_bckdat;
+ uint16 pk_bcktim;
+ } RSTS_PACKLABEL;
+
+typedef union _RSTS_ROOT {
+ RSTS_MFDLABEL rt_mfd;
+ RSTS_PACKLABEL rt_pack;
+ uint8 rt_block[512];
+ } RSTS_ROOT;
+
+typedef struct _RSTS_MFDBLOCKETTE {
+ uint16 mb_ulnk;
+ uint16 mb_mbm1;
+ uint16 mb_reserved1;
+ uint16 mb_reserved2;
+ uint16 mb_reserved3;
+ uint16 mb_malnk;
+ uint16 mb_lppn;
+ uint16 mb_lid;
+#define MB_ID 0051064
+ } RSTS_MFDBLOCKETTE;
+#define IS_VALID_RSTS_MFD(b) \
+ ((((b)->mb_ulnk == 0) || ((b)->mb_ulnk == 1)) && \
+ ((b)->mb_mbm1 == 0177777) && \
+ ((b)->mb_reserved1 == 0) && \
+ ((b)->mb_reserved2 == 0) && \
+ ((b)->mb_reserved3 == 0) && \
+ ((b)->mb_lppn == 0177777) && \
+ ((b)->mb_lid == MB_ID))
+
+typedef struct _RSTS_GFDBLOCKETTE {
+ uint16 gb_ulnk;
+ uint16 gb_mbm1;
+ uint16 gb_reserved1;
+ uint16 gb_reserved2;
+ uint16 gb_reserved3;
+ uint16 gb_reserved4;
+ uint16 gb_lppn;
+ uint16 gb_lid;
+#define GB_ID 0026264
+ } RSTS_GFDBLOCKETTE;
+#define IS_VALID_RSTS_GFD(b, g) \
+ ((((b)->gb_ulnk == 0) || ((b)->gb_ulnk == 1)) && \
+ ((b)->gb_mbm1 == 0177777) && \
+ ((b)->gb_reserved1 == 0) && \
+ ((b)->gb_reserved2 == 0) && \
+ ((b)->gb_reserved3 == 0) && \
+ ((b)->gb_reserved4 == 0) && \
+ ((b)->gb_lppn == (((g) << 8) | 0377)) && \
+ ((b)->gb_lid == GB_ID))
+
+typedef struct _RSTS_UFDBLOCKETTE {
+ uint16 ub_ulnk;
+ uint16 ub_mbm1;
+ uint16 ub_reserved1;
+ uint16 ub_reserved2;
+ uint16 ub_reserved3;
+ uint16 ub_reserved4;
+ uint16 ub_lppn;
+ uint16 ub_lid;
+#define UB_ID 0102064
+ } RSTS_UFDBLOCKETTE;
+#define IS_VALID_RSTS_UFD(b, g, u) \
+ (((b)->ub_mbm1 == 0177777) && \
+ ((b)->ub_reserved1 == 0) && \
+ ((b)->ub_reserved2 == 0) && \
+ ((b)->ub_reserved3 == 0) && \
+ ((b)->ub_reserved4 == 0) && \
+ ((b)->ub_lppn == (((g) << 8) | (u))) && \
+ ((b)->ub_lid == UB_ID))
+
+typedef struct _RSTS_UNAME {
+ uint16 un_ulnk;
+ uint16 un_unam;
+ uint16 un_reserved1;
+ uint16 un_reserved2;
+ uint16 un_ustat;
+ uint16 un_uacnt;
+ uint16 un_uaa;
+ uint16 un_uar;
+ } RSTS_UNAME;
+
+typedef struct _RSTS_FNAME {
+ uint16 fn_ulnk;
+ uint16 fn_unam[3];
+ uint16 fn_ustat;
+ uint16 fn_uacnt;
+ uint16 fn_uaa;
+ uint16 fn_uar;
+ } RSTS_FNAME;
+
+typedef struct _RSTS_ACNT {
+ uint16 ac_ulnk;
+ uint16 ac_udla;
+ uint16 ac_usiz;
+ uint16 ac_udc;
+ uint16 ac_utc;
+ uint16 ac_urts[2];
+ uint16 ac_uclus;
+ } RSTS_ACNT;
+
+typedef struct _RSTS_RETR {
+ uint16 rt_ulnk;
+ uint16 rt_uent[7];
+#define RT_ENTRIES 7
+ } RSTS_RETR;
+
+typedef struct _RSTS_DCMAP {
+ uint16 dc_clus;
+#define DC_MASK 0077777
+ uint16 dc_map[7];
+ } RSTS_DCMAP;
+
+/*
+ * Directory link definitions
+ */
+#define DL_USE 0000001
+#define DL_BAD 0000002
+#define DL_CHE 0000004
+#define DL_CLN 0000010
+#define DL_ENO 0000760
+#define DL_CLO 0007000
+#define DL_BLO 0170000
+
+#define DLSH_ENO 4
+#define DLSH_CLO 9
+#define DLSH_BLO 12
+
+#define BLOCKETTE_SZ (8 * sizeof(uint16))
+#define MAP_OFFSET (31 * BLOCKETTE_SZ)
+
+#define SATT0 0073374
+#define SATT1 0076400
+#define SATT2 0075273
+
+#pragma pack(pop)
+
+typedef struct _rstsContext {
+ UNIT *uptr;
+ int dcshift;
+ int pcs;
+ char packid[8];
+ t_seccnt sects;
+ RSTS_DCMAP map;
+} rstsContext;
+
+static char rad50[] = " ABCDEFGHIJKLMNOPQRSTUVWXYZ$.%0123456789";
+
+static void r50Asc(uint16 val, char *buf)
+{
+buf[2] = rad50[val % 050];
+val /= 050;
+buf[1] = rad50[val % 050];
+buf[0] = rad50[val / 050];
+}
+
+static t_stat rstsValidateClusterSize(uint16 size, uint16 minSize)
+{
+int i;
+
+/*
+ * Check that the cluster size is a power of 2 and greater than or equal
+ * to some location dependent value.
+ */
+if (size >= minSize)
+ for (i = 0; i < 16; i++)
+ if (size == (1 << i))
+ return SCPE_OK;
+
+return SCPE_IOERR;
+}
+
+static t_stat rstsReadBlock(rstsContext *context, uint16 cluster, uint16 block, void *buf)
+{
+t_lba blk = (cluster << context->dcshift) + block;
+t_seccnt sects_read;
+
+if ((sim_disk_rdsect(context->uptr, blk * context->sects, (uint8 *)buf, §s_read, context->sects) == SCPE_OK) &&
+ (sects_read == context->sects))
+ return SCPE_OK;
+
+return SCPE_IOERR;
+}
+
+static t_stat rstsReadBlockette(rstsContext *context, uint16 link, void *buf)
+{
+uint16 block = (link & DL_BLO) >> DLSH_BLO;
+uint16 dcn = (link & DL_CLO) >> DLSH_CLO;
+uint16 blockette = (link & DL_ENO) >> DLSH_ENO;
+uint8 temp[512];
+
+if ((dcn != 7) && (blockette != 31) &&
+ (block <= (context->map.dc_clus & DC_MASK))) {
+ if (rstsReadBlock(context, context->map.dc_map[dcn], block, temp) == SCPE_OK) {
+ memcpy(buf, &temp[blockette * BLOCKETTE_SZ], BLOCKETTE_SZ);
+ return SCPE_OK;
+ }
+ }
+return SCPE_IOERR;
+}
+
+static t_stat rstsFind01UFD(rstsContext *context, uint16 *ufd, uint16 *level)
+{
+uint16 dcs = 1 << context->dcshift;
+RSTS_ROOT root;
+uint16 buf[256];
+
+if (rstsReadBlock(context, 1, 0, &root) == SCPE_OK) {
+ /*
+ * First validate fields which are common to both the MFD label and
+ * Pack label - we'll use Pack label offsets here.
+ */
+ if ((root.rt_pack.pk_mbm1 == 0177777) &&
+ (rstsValidateClusterSize(root.rt_pack.pk_ppcs, dcs) == SCPE_OK)) {
+ char ch, *tmp = &context->packid[1];
+ uint16 mfd, gfd;
+
+ context->pcs = root.rt_pack.pk_ppcs;
+
+ r50Asc(root.rt_pack.pk_packid[0], &context->packid[0]);
+ r50Asc(root.rt_pack.pk_packid[1], &context->packid[3]);
+ context->packid[6] = '\0';
+
+ /*
+ * The Pack ID must consist of 1 - 6 alphanumeric characters
+ * padded at the end with spaces.
+ */
+ if (!isalnum(context->packid[0]))
+ return SCPE_IOERR;
+
+ while ((ch = *tmp++) != 0) {
+ if (!isalnum(ch)) {
+ if (ch != ' ')
+ return SCPE_IOERR;
+
+ while (*tmp)
+ if (*tmp++ != ' ')
+ return SCPE_IOERR;
+ break;
+ }
+ }
+
+ /*
+ * Determine the pack revision level and, therefore, the path to
+ * [0,1]satt.sys which will allow us to determine the size of the
+ * pack used by RSTS.
+ */
+ if ((root.rt_pack.pk_pstat & PK_UC_NEW) == 0) {
+ uint16 link = root.rt_mfd.ml_ulnk;
+ RSTS_UNAME uname;
+
+ /*
+ * Old format used by RSTS up through V07.x
+ */
+ if (dcs > 16)
+ return SCPE_IOERR;
+
+ *level = PK_LVL0;
+
+ memcpy(&context->map, &root.rt_block[MAP_OFFSET], BLOCKETTE_SZ);
+
+ /*
+ * Scan the MFD name entries looking for [0,1]. Note there will
+ * always be at least 1 entry.
+ */
+ do {
+ if (rstsReadBlockette(context, link, &uname) != SCPE_OK)
+ break;
+
+ if (uname.un_unam == ((0 << 8) | 1)) {
+ *ufd = uname.un_uar;
+ return SCPE_OK;
+ }
+ } while ((link = uname.un_ulnk) != 0);
+ }
+ else {
+ /*
+ * New format used by RSTS V08 and later
+ */
+ switch (root.rt_pack.pk_plvl) {
+ case PK_LVL11:
+ if (dcs > 16)
+ return SCPE_IOERR;
+ break;
+
+ case PK_LVL12:
+ if (dcs > 64)
+ return SCPE_IOERR;
+ break;
+
+ default:
+ return SCPE_IOERR;
+ }
+ *level = root.rt_pack.pk_plvl;
+
+ mfd = root.rt_pack.pk_mdcn;
+
+ if (rstsReadBlock(context, mfd, 0, buf) == SCPE_OK) {
+ if (IS_VALID_RSTS_MFD((RSTS_MFDBLOCKETTE *)buf)) {
+ if (rstsReadBlock(context, mfd, 1, buf) == SCPE_OK)
+ if ((gfd = buf[0]) != 0)
+ if (rstsReadBlock(context, gfd, 0, buf) == SCPE_OK)
+ if (IS_VALID_RSTS_GFD((RSTS_GFDBLOCKETTE *)buf, 0)) {
+ if (rstsReadBlock(context, gfd, 1, buf) == SCPE_OK)
+ if ((*ufd = buf[1]) != 0)
+ return SCPE_OK;
+ }
+ }
+ }
+ }
+ }
+ }
+return SCPE_IOERR;
+}
+
+static t_stat rstsLoadAndScanSATT(rstsContext *context, uint16 uaa, uint16 uar, t_offset *result)
+{
+t_offset blocks = 0;
+uint8 bitmap[8192];
+int i, j;
+RSTS_ACNT acnt;
+RSTS_RETR retr;
+
+if (uar != 0) {
+ if (rstsReadBlockette(context, uaa, &acnt) == SCPE_OK) {
+ uint16 blocks = acnt.ac_usiz;
+ uint16 offset = 0;
+
+ if ((rstsValidateClusterSize(acnt.ac_uclus, context->pcs) != SCPE_OK) ||
+ (blocks > 16))
+ return SCPE_IOERR;
+
+ memset(bitmap, 0xFF, sizeof(bitmap));
+
+ if (blocks != 0) {
+ do {
+ int i, j;
+ uint16 fcl;
+
+ if (rstsReadBlockette(context, uar, &retr) != SCPE_OK)
+ return SCPE_IOERR;
+
+ for (i = 0; i < RT_ENTRIES; i++) {
+ if ((fcl = retr.rt_uent[i]) == 0)
+ goto scanBitmap;
+
+ for (j = 0; j < acnt.ac_uclus; j++) {
+ if ((blocks == 0) || (offset >= sizeof(bitmap)))
+ goto scanBitmap;
+
+ if (rstsReadBlock(context, fcl, j, &bitmap[offset]) != SCPE_OK)
+ return SCPE_IOERR;
+
+ offset += 512;
+ blocks--;
+ }
+ }
+ } while ((uar = retr.rt_ulnk) != 0);
+
+ scanBitmap:
+ for (i = sizeof(bitmap) - 1; i != 0; i--)
+ if (bitmap[i] != 0xFF) {
+ blocks = i * 8;
+ for (j = 7; j >= 0; j--)
+ if ((bitmap[i] & (1 << j)) == 0) {
+ blocks += j + 1;
+ goto scanDone;
+ }
+ }
+ scanDone:
+ *result = (t_offset)(blocks + 1) * context->pcs;
+ return SCPE_OK;
+ }
+ }
+ }
+return SCPE_IOERR;
+}
+
+static t_offset get_rsts_filesystem_size (UNIT *uptr)
+{
+DEVICE *dptr;
+t_addr saved_capac;
+struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
+t_addr temp_capac = (sim_toffset_64 ? (t_addr)0xFFFFFFFFu : (t_addr)0x7FFFFFFFu); /* Make sure we can access the largest sector */
+uint8 buf[512];
+t_offset ret_val = (t_offset)-1;
+rstsContext context;
+
+if ((dptr = find_dev_from_unit (uptr)) == NULL)
+ return ret_val;
+saved_capac = uptr->capac;
+uptr->capac = temp_capac;
+
+context.uptr = uptr;
+context.sects = 512 / ctx->sector_size;
+
+/*
+ * Check all possible device cluster sizes
+ */
+for (context.dcshift = 0; context.dcshift < 8; context.dcshift++) {
+ uint16 ufd, level;
+
+ /*
+ * We need to find [0,1]SATT.SYS to compute the actual size of the disk.
+ * First find the DCN of the [0,1] UFD.
+ */
+ if (rstsFind01UFD(&context, &ufd, &level) == SCPE_OK) {
+ if (rstsReadBlock(&context, ufd, 0, buf) == SCPE_OK) {
+ if (IS_VALID_RSTS_UFD((RSTS_UFDBLOCKETTE *)buf, 0, 1)) {
+ uint16 link = ((RSTS_UFDBLOCKETTE *)buf)->ub_ulnk;
+ RSTS_FNAME fname;
+
+ memcpy(&context.map, &buf[MAP_OFFSET], BLOCKETTE_SZ);
+
+ /*
+ * Scan the UFD looking for SATT.SYS - the allocation bitmap
+ */
+ do {
+ if (rstsReadBlockette(&context, link, &fname) != SCPE_OK)
+ break;
+
+ if ((fname.fn_unam[0] == SATT0) &&
+ (fname.fn_unam[1] == SATT1) &&
+ (fname.fn_unam[2] == SATT2)) {
+ if (rstsLoadAndScanSATT(&context, fname.fn_uaa, fname.fn_uar, &ret_val) == SCPE_OK) {
+ const char *fmt = "???";
+
+ ret_val *= 512;
+
+ switch (level) {
+ case PK_LVL0:
+ fmt = "0.0";
+ break;
+
+ case PK_LVL11:
+ fmt = "1.1";
+ break;
+
+ case PK_LVL12:
+ fmt = "1.2";
+ break;
+ }
+
+ sim_messagef(SCPE_OK, "%s%d: '%s' Contains a RSTS File system\n", sim_dname (dptr), (int)(uptr-dptr->units), uptr->filename);
+ sim_messagef(SCPE_OK, "%s%d: Pack ID: %6.6s ", sim_dname (dptr), (int)(uptr-dptr->units), context.packid);
+ sim_messagef(SCPE_OK, "Revision Level: %3s ", fmt);
+ sim_messagef(SCPE_OK, "Pack Clustersize: %d\n", context.pcs);
+ sim_messagef(SCPE_OK, "%s%d: Last Unallocated Sector In File System: %u\n", sim_dname (dptr), (int)(uptr-dptr->units), (uint32)(ret_val / 512));
+ goto cleanup_done;
+ }
+ }
+ } while ((link = fname.fn_ulnk) != 0);
+ }
+ }
+ }
+ }
+cleanup_done:
+uptr->capac = saved_capac;
+return ret_val;
+}
+
+#pragma pack(push,1)
+typedef struct _RT11_HomeBlock {
+ uint8 hb_b_bbtable[130];
+ uint8 hb_b_unused1[2];
+ uint8 hb_b_initrestore[38];
+ uint8 hb_b_bup[18];
+ uint8 hb_b_unused2[260];
+ uint16 hb_w_reserved1;
+ uint16 hb_w_reserved2;
+ uint8 hb_b_unused3[14];
+ uint16 hb_w_clustersize;
+ uint16 hb_w_firstdir;
+ uint16 hb_w_sysver;
+#define HB_C_SYSVER_V3A 36521
+#define HB_C_SYSVER_V04 36434
+#define HB_C_SYSVER_V05 36435
+ uint8 hb_b_volid[12];
+ uint8 hb_b_owner[12];
+ uint8 hb_b_sysid[12];
+#define HB_C_SYSID "DECRT11A "
+ uint8 hb_b_unused4[2];
+ uint16 hb_w_checksum;
+ } RT11_HomeBlock;
+
+typedef struct _RT11_DirHeader {
+ uint16 dh_w_count;
+ uint16 dh_w_next;
+ uint16 dh_w_highest;
+#define DH_C_MAXSEG 31
+ uint16 dh_w_extra;
+ uint16 dh_w_start;
+ } RT11_DirHeader;
+
+typedef struct _RT11_DirEntry {
+ uint16 de_w_status;
+#define DE_C_PRE 0000020
+#define DE_C_TENT 0000400
+#define DE_C_EMPTY 0001000
+#define DE_C_PERM 0002000
+#define DE_C_EOS 0004000
+#define DE_C_READ 0040000
+#define DE_C_PROT 0100000
+ uint16 de_w_fname1;
+ uint16 de_w_fname2;
+ uint16 de_w_ftype;
+ uint16 de_w_length;
+ uint16 de_w_jobchannel;
+ uint16 de_w_creationdate;
+ } RT11_DirEntry;
+#pragma pack(pop)
+
+#define RT11_MAXPARTITIONS 256 /* Max partitions supported */
+#define RT11_HOME 1 /* Home block # */
+
+#define RT11_NOPART 0
+#define RT11_SINGLEPART 1
+#define RT11_MULTIPART 2
+
+static int rt11_get_partition_type(RT11_HomeBlock *home, int part)
+{
+if (strncmp((char *)&home->hb_b_sysid, HB_C_SYSID, strlen(HB_C_SYSID)) == 0) {
+ uint16 type = home->hb_w_sysver;
+
+ if (part == 0) {
+ if ((type == HB_C_SYSVER_V3A) || (type == HB_C_SYSVER_V04))
+ return RT11_SINGLEPART;
+ }
+
+ if (type == HB_C_SYSVER_V05)
+ return RT11_MULTIPART;
+ }
+return RT11_NOPART;
+}
+
+static t_offset get_rt11_filesystem_size (UNIT *uptr)
+{
+DEVICE *dptr;
+t_addr saved_capac;
+struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
+t_addr temp_capac = (sim_toffset_64 ? (t_addr)0xFFFFFFFFu : (t_addr)0x7FFFFFFFu); /* Make sure we can access the largest sector */
+uint8 sector_buf[1024];
+RT11_HomeBlock Home;
+t_seccnt sects_read;
+RT11_DirHeader *dir_hdr = (RT11_DirHeader *)sector_buf;
+int partitions = 0;
+int part;
+uint32 base;
+uint32 dir_sec;
+uint16 dir_seg;
+uint16 version = 0;
+t_offset ret_val = (t_offset)-1;
+
+if ((dptr = find_dev_from_unit (uptr)) == NULL)
+ return ret_val;
+saved_capac = uptr->capac;
+uptr->capac = temp_capac;
+
+for (part = 0; part < RT11_MAXPARTITIONS; part++) {
+ uint16 seg_highest;
+ int type;
+
+ base = part << 16;
+
+ if (sim_disk_rdsect(uptr, (base + RT11_HOME) * (512 / ctx->sector_size), (uint8 *)&Home, §s_read, 512 / ctx->sector_size) ||
+ (sects_read != (512 / ctx->sector_size)))
+ goto Return_Cleanup;
+
+ type = rt11_get_partition_type(&Home, part);
+
+ if (type != RT11_NOPART) {
+ uint16 highest = 0;
+ uint8 seg_seen[DH_C_MAXSEG + 1];
+
+ memset(seg_seen, 0, sizeof(seg_seen));
+
+ partitions++;
+
+ dir_seg = 1;
+ do {
+ int offset = sizeof(RT11_DirHeader);
+ int dir_size = sizeof(RT11_DirEntry);
+ uint16 cur_blk;
+
+ if (seg_seen[dir_seg]++ != 0)
+ goto Next_Partition;
+
+ dir_sec = Home.hb_w_firstdir + ((dir_seg - 1) * 2);
+
+ if ((sim_disk_rdsect(uptr, (base + dir_sec) * (512 / ctx->sector_size), sector_buf, §s_read, 1024 / ctx->sector_size)) ||
+ (sects_read != (1024 / ctx->sector_size)))
+ goto Return_Cleanup;
+
+ if (dir_seg == 1) {
+ seg_highest = dir_hdr->dh_w_highest;
+ if (seg_highest > DH_C_MAXSEG)
+ goto Next_Partition;
+ }
+ dir_size += dir_hdr->dh_w_extra;
+ cur_blk = dir_hdr->dh_w_start;
+
+ while ((1024 - offset) >= dir_size) {
+ RT11_DirEntry *dir_entry = (RT11_DirEntry *)§or_buf[offset];
+
+ if (dir_entry->de_w_status & DE_C_EOS)
+ break;
+
+ /*
+ * Within each directory segment the bas address should never
+ * decrease.
+ */
+ if (((cur_blk + dir_entry->de_w_length) & 0xFFFF) < cur_blk)
+ goto Next_Partition;
+
+ cur_blk += dir_entry->de_w_length;
+ offset += dir_size;
+ }
+ if (cur_blk > highest)
+ highest = cur_blk;
+ dir_seg = dir_hdr->dh_w_next;
+
+ if (dir_seg > seg_highest)
+ goto Next_Partition;
+ } while (dir_seg != 0);
+
+ ret_val = (t_offset)((base + highest) * (t_offset)512);
+ version = Home.hb_w_sysver;
+
+ if (type == RT11_SINGLEPART)
+ break;
+ }
+Next_Partition:
+ ;
+ }
+
+Return_Cleanup:
+if (partitions) {
+ const char *parttype;
+
+ switch (version) {
+ case HB_C_SYSVER_V3A:
+ parttype = "V3A";
+ break;
+
+ case HB_C_SYSVER_V04:
+ parttype = "V04";
+ break;
+
+ case HB_C_SYSVER_V05:
+ parttype = "V05";
+ break;
+
+ default:
+ parttype = "???";
+ break;
+ }
+ sim_messagef (SCPE_OK, "%s%d: '%s' Contains RT11 partitions\n", sim_dname (dptr), (int)(uptr-dptr->units), uptr->filename);
+ sim_messagef (SCPE_OK, "%d valid partition%s, Type: %s, Sectors On Disk: %u\n", partitions, partitions == 1 ? "" : "s", parttype, (uint32)(ret_val / 512));
+ }
uptr->capac = saved_capac;
return ret_val;
}
typedef t_offset (*FILESYSTEM_CHECK)(UNIT *uptr);
@@ -1310,13 +2073,19 @@
{
static FILESYSTEM_CHECK checks[] = {
&get_ods2_filesystem_size,
&get_ods1_filesystem_size,
&get_ultrix_filesystem_size,
+ &get_rsts_filesystem_size,
+ &get_rt11_filesystem_size, /* This should be the last entry
+ in the table to reduce the
+ possibility of matching an RT-11
+ container file stored in another
+ filesystem */
NULL
};
-t_offset ret_val;
+t_offset ret_val = (t_offset)-1;
int i;
for (i = 0; checks[i] != NULL; i++) {
ret_val = checks[i] (uptr);
if (ret_val != (t_offset)-1)
@@ -1323,12 +2092,139 @@
break;
}
return ret_val;
}
-t_stat sim_disk_attach (UNIT *uptr, const char *cptr, size_t sector_size, size_t xfer_element_size, t_bool dontautosize,
+static t_stat get_disk_footer (UNIT *uptr)
+{
+struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
+struct simh_disk_footer *f = (struct simh_disk_footer *)calloc (1, sizeof (*f));
+t_offset container_size;
+t_offset sim_fsize_ex (FILE *fptr);
+uint32 bytesread;
+
+if (f == NULL)
+ return SCPE_MEM;
+sim_debug_unit (ctx->dbit, uptr, "get_disk_footer(%s)\n", sim_uname (uptr));
+switch (DK_GET_FMT (uptr)) { /* case on format */
+ case DKUF_F_STD: /* SIMH format */
+ container_size = sim_fsize_ex (uptr->fileref);
+ if ((container_size != (t_offset)-1) && (container_size > sizeof (*f)) &&
+ (sim_fseeko (uptr->fileref, container_size - sizeof (*f), SEEK_SET) == 0) &&
+ (sizeof (*f) == sim_fread (f, 1, sizeof (*f), uptr->fileref)))
+ break;
+ free (f);
+ f = NULL;
+ break;
+ case DKUF_F_RAW: /* RAW format */
+ container_size = sim_os_disk_size_raw (uptr->fileref);
+ if ((container_size != (t_offset)-1) && (container_size > sizeof (*f)) &&
+ (sim_os_disk_read (uptr, container_size - sizeof (*f), (uint8 *)f, &bytesread, sizeof (*f)) == SCPE_OK) &&
+ (bytesread == sizeof (*f)))
+ break;
+ free (f);
+ f = NULL;
+ break;
+ case DKUF_F_VHD: /* VHD format */
+ /* Construct a pseudo simh disk footer*/
+ memcpy (f->Signature, "simh", 4);
+ strncpy ((char *)f->DriveType, sim_vhd_disk_get_dtype (uptr->fileref, &f->SectorSize, &f->TransferElementSize, (char *)f->CreatingSimulator), sizeof (f->DriveType) - 1);
+ f->SectorSize = NtoHl (f->SectorSize);
+ f->TransferElementSize = NtoHl (f->TransferElementSize);
+ if ((f->SectorSize == 0) || (NtoHl (f->SectorSize) == 0x00020000)) { /* Old or mangled format VHD footer */
+ sim_vhd_disk_set_dtype (uptr->fileref, (char *)f->DriveType, ctx->sector_size, ctx->xfer_element_size);
+ sim_vhd_disk_get_dtype (uptr->fileref, &f->SectorSize, &f->TransferElementSize, (char *)f->CreatingSimulator);
+ f->SectorSize = NtoHl (f->SectorSize);
+ f->TransferElementSize = NtoHl (f->TransferElementSize);
+ }
+ container_size = sim_vhd_disk_size (uptr->fileref);
+ f->SectorCount = NtoHl ((uint32)(container_size / NtoHl (f->SectorSize)));
+ container_size += sizeof (*f); /* Adjust since it is removed below */
+ f->AccessFormat = DKUF_F_VHD;
+ strncpy ((char *)f->CreationTime, "\n", sizeof (f->CreationTime));
+ f->Checksum = NtoHl (eth_crc32 (0, f, sizeof (*f) - sizeof (f->Checksum)));
+ break;
+ default:
+ return SCPE_IERR;
+ }
+if (f) {
+ if (f->Checksum != NtoHl (eth_crc32 (0, f, sizeof (*f) - sizeof (f->Checksum)))) {
+ sim_debug_unit (ctx->dbit, uptr, "No footer found on %s format container: %s\n", sim_disk_fmt (uptr), uptr->filename);
+ free (f);
+ f = NULL;
+ }
+ else {
+ ctx->footer = f;
+ container_size -= sizeof (*f);
+ sim_debug_unit (ctx->dbit, uptr, "Footer: %s - %s\n"
+ " Simulator: %s\n"
+ " DriveType: %s\n"
+ " SectorSize: %u\n"
+ " SectorCount: %u\n"
+ " TransferElementSize: %u\n"
+ " FooterVersion: %u\n"
+ " AccessFormat: %u\n"
+ " CreationTime: %s",
+ sim_uname (uptr), uptr->filename,
+ f->CreatingSimulator, f->DriveType, NtoHl(f->SectorSize), NtoHl (f->SectorCount),
+ NtoHl (f->TransferElementSize), f->FooterVersion, f->AccessFormat, f->CreationTime);
+ }
+ }
+sim_debug_unit (ctx->dbit, uptr, "Container Size: %u sectors %u bytes each\n", (uint32)(container_size/ctx->sector_size), ctx->sector_size);
+ctx->container_size = container_size;
+return SCPE_OK;
+}
+
+static t_stat store_disk_footer (UNIT *uptr, const char *dtype)
+{
+DEVICE *dptr;
+struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
+struct simh_disk_footer *f;
+time_t now = time (NULL);
+t_offset total_sectors;
+
+if ((dptr = find_dev_from_unit (uptr)) == NULL)
+ return SCPE_NOATT;
+if (uptr->flags & UNIT_RO)
+ return SCPE_RO;
+f = (struct simh_disk_footer *)calloc (1, sizeof (*f));
+f->AccessFormat = DK_GET_FMT (uptr);
+total_sectors = (((t_offset)uptr->capac) * ctx->capac_factor * ((dptr->flags & DEV_SECTORS) ? 512 : 1)) / ctx->sector_size;
+memcpy (f->Signature, "simh", 4);
+strncpy ((char *)f->CreatingSimulator, sim_name, sizeof (f->CreatingSimulator) - 1);
+strncpy ((char *)f->DriveType, dtype, sizeof (f->DriveType) - 1);
+f->SectorSize = NtoHl (ctx->sector_size);
+f->SectorCount = NtoHl ((uint32)total_sectors);
+f->TransferElementSize = NtoHl (ctx->xfer_element_size);
+strncpy ((char*)f->CreationTime, ctime (&now), sizeof (f->CreationTime) - 1);
+f->Checksum = NtoHl (eth_crc32 (0, f, sizeof (*f) - sizeof (f->Checksum)));
+free (ctx->footer);
+ctx->footer = f;
+switch (f->AccessFormat) {
+ case DKUF_F_STD: /* SIMH format */
+ if (sim_fseeko ((FILE *)uptr->fileref, total_sectors * ctx->sector_size, SEEK_SET) == 0)
+ sim_fwrite (f, sizeof (*f), 1, (FILE *)uptr->fileref);
+ break;
+ case DKUF_F_VHD: /* VHD format */
+ break;
+ case DKUF_F_RAW: /* Raw Physical Disk Access */
+ sim_os_disk_write (uptr, total_sectors * ctx->sector_size, (uint8 *)f, NULL, sizeof (*f));
+ break;
+ default:
+ break;
+ }
+return SCPE_OK;
+}
+
+t_stat sim_disk_attach (UNIT *uptr, const char *cptr, size_t sector_size, size_t xfer_element_size, t_bool dontchangecapac,
uint32 dbit, const char *dtype, uint32 pdp11tracksize, int completion_delay)
+{
+return sim_disk_attach_ex (uptr, cptr, sector_size, xfer_element_size, dontchangecapac, dbit, dtype, pdp11tracksize, completion_delay, NULL);
+}
+
+t_stat sim_disk_attach_ex (UNIT *uptr, const char *cptr, size_t sector_size, size_t xfer_element_size, t_bool dontchangecapac,
+ uint32 dbit, const char *dtype, uint32 pdp11tracksize, int completion_delay, const char **drivetypes)
{
struct disk_context *ctx;
DEVICE *dptr;
char tbuf[4*CBUFSIZE];
FILE *(*open_function)(const char *filename, const char *mode) = sim_fopen;
@@ -1336,17 +2232,29 @@
t_offset (*size_function)(FILE *file);
t_stat (*storage_function)(FILE *file, uint32 *sector_size, uint32 *removable, uint32 *is_cdrom) = NULL;
t_bool created = FALSE, copied = FALSE;
t_bool auto_format = FALSE;
t_offset container_size, filesystem_size, current_unit_size;
+size_t size_tmp;
if (uptr->flags & UNIT_DIS) /* disabled? */
return SCPE_UDIS;
if (!(uptr->flags & UNIT_ATTABLE)) /* not attachable? */
return SCPE_NOATT;
if ((dptr = find_dev_from_unit (uptr)) == NULL)
return SCPE_NOATT;
+switch (xfer_element_size) {
+ default:
+ return sim_messagef (SCPE_ARG, "Unsupported transfer element size: %u\n", (uint32)xfer_element_size);
+ case 1: case 2: case 4: case 8:
+ break;
+ }
+size_tmp = 64;
+while ((size_tmp != sector_size) && (size_tmp < 4096))
+ size_tmp = size_tmp << 1;
+if (sector_size != size_tmp)
+ return sim_messagef (SCPE_ARG, "Invalid sector size: %u - must be a power of 2 between 64 and 4096\n", (uint32)sector_size);
if (sim_switches & SWMASK ('F')) { /* format spec? */
char gbuf[CBUFSIZE];
cptr = get_glyph (cptr, gbuf, 0); /* get spec */
if (*cptr == 0) /* must be more */
return SCPE_2FARG;
@@ -1365,11 +2273,11 @@
if (*cptr == 0) /* must be more */
return SCPE_2FARG;
vhd = sim_vhd_disk_create_diff (gbuf, cptr);
if (vhd) {
sim_vhd_disk_close (vhd);
- return sim_disk_attach (uptr, gbuf, sector_size, xfer_element_size, dontautosize, dbit, dtype, pdp11tracksize, completion_delay);
+ return sim_disk_attach (uptr, gbuf, sector_size, xfer_element_size, dontchangecapac, dbit, dtype, pdp11tracksize, completion_delay);
}
return sim_messagef (SCPE_ARG, "Unable to create differencing VHD: %s\n", gbuf);
}
if (sim_switches & SWMASK ('C')) { /* create vhd disk & copy contents? */
char gbuf[CBUFSIZE];
@@ -1384,18 +2292,18 @@
if (*cptr == 0) /* must be more */
return SCPE_2FARG;
sim_switches |= SWMASK ('R') | SWMASK ('E');
sim_quiet = TRUE;
/* First open the source of the copy operation */
- r = sim_disk_attach (uptr, cptr, sector_size, xfer_element_size, dontautosize, dbit, dtype, pdp11tracksize, completion_delay);
+ r = sim_disk_attach_ex (uptr, cptr, sector_size, xfer_element_size, dontchangecapac, dbit, dtype, pdp11tracksize, completion_delay, drivetypes);
sim_quiet = saved_sim_quiet;
if (r != SCPE_OK) {
sim_switches = saved_sim_switches;
- return sim_messagef (r, "Can't open source VHD: %s\n", cptr);
+ return sim_messagef (r, "Can't open copy source: %s - %s\n", cptr, sim_error_text (r));
}
sim_messagef (SCPE_OK, "%s%d: creating new virtual disk '%s'\n", sim_dname (dptr), (int)(uptr-dptr->units), gbuf);
- capac_factor = ((dptr->dwidth / dptr->aincr) == 16) ? 2 : 1; /* capacity units (word: 2, byte: 1) */
+ capac_factor = ((dptr->dwidth / dptr->aincr) >= 32) ? 8 : ((dptr->dwidth / dptr->aincr) == 16) ? 2 : 1; /* capacity units (quadword: 8, word: 2, byte: 1) */
vhd = sim_vhd_disk_create (gbuf, ((t_offset)uptr->capac)*capac_factor*((dptr->flags & DEV_SECTORS) ? 512 : 1));
if (!vhd) {
return sim_messagef (r, "%s%d: can't create virtual disk '%s'\n", sim_dname (dptr), (int)(uptr-dptr->units), gbuf);
}
else {
@@ -1402,76 +2310,85 @@
uint8 *copy_buf = (uint8*) malloc (1024*1024);
t_lba lba;
t_seccnt sectors_per_buffer = (t_seccnt)((1024*1024)/sector_size);
t_lba total_sectors = (t_lba)((uptr->capac*capac_factor)/(sector_size/((dptr->flags & DEV_SECTORS) ? 512 : 1)));
t_seccnt sects = sectors_per_buffer;
+ t_seccnt sects_read;
if (!copy_buf) {
sim_vhd_disk_close(vhd);
(void)remove (gbuf);
return SCPE_MEM;
}
- for (lba = 0; (lba < total_sectors) && (r == SCPE_OK); lba += sects) {
- sim_messagef (SCPE_OK, "%s%d: Copied %dMB. %d%% complete.\r", sim_dname (dptr), (int)(uptr-dptr->units), (int)((((float)lba)*sector_size)/1000000), (int)((((float)lba)*100)/total_sectors));
+ sim_messagef (SCPE_OK, "Copying %u sectors each %u bytes in size\n", (uint32)total_sectors, (uint32)sector_size);
+ for (lba = 0; (lba < total_sectors) && (r == SCPE_OK); lba += sects_read) {
sects = sectors_per_buffer;
if (lba + sects > total_sectors)
sects = total_sectors - lba;
- r = sim_disk_rdsect (uptr, lba, copy_buf, NULL, sects);
- if (r == SCPE_OK) {
+ r = sim_disk_rdsect (uptr, lba, copy_buf, §s_read, sects);
+ if ((r == SCPE_OK) && (sects_read > 0)) {
uint32 saved_unit_flags = uptr->flags;
FILE *save_unit_fileref = uptr->fileref;
+ t_seccnt sects_written;
sim_disk_set_fmt (uptr, 0, "VHD", NULL);
uptr->fileref = vhd;
- r = sim_disk_wrsect (uptr, lba, copy_buf, NULL, sects);
+ r = sim_disk_wrsect (uptr, lba, copy_buf, §s_written, sects_read);
uptr->fileref = save_unit_fileref;
uptr->flags = saved_unit_flags;
+ if (sects_read != sects_written)
+ r = SCPE_IOERR;
+ sim_messagef (SCPE_OK, "%s%d: Copied %u/%u sectors. %d%% complete.\r", sim_dname (dptr), (int)(uptr-dptr->units), (uint32)(lba + sects_read), (uint32)total_sectors, (int)((((float)lba)*100)/total_sectors));
}
}
if (r == SCPE_OK)
- sim_messagef (SCPE_OK, "\n%s%d: Copied %dMB. Done.\n", sim_dname (dptr), (int)(uptr-dptr->units), (int)(((t_offset)lba*sector_size)/1000000));
+ sim_messagef (SCPE_OK, "\n%s%d: Copied %u sectors. Done.\n", sim_dname (dptr), (int)(uptr-dptr->units), (uint32)total_sectors);
else
sim_messagef (r, "\n%s%d: Error copying: %s.\n", sim_dname (dptr), (int)(uptr-dptr->units), sim_error_text (r));
if ((r == SCPE_OK) && (sim_switches & SWMASK ('V'))) {
uint8 *verify_buf = (uint8*) malloc (1024*1024);
+ t_seccnt sects_read, verify_read;
if (!verify_buf) {
sim_vhd_disk_close(vhd);
(void)remove (gbuf);
free (copy_buf);
return SCPE_MEM;
}
- for (lba = 0; (lba < total_sectors) && (r == SCPE_OK); lba += sects) {
- sim_messagef (SCPE_OK, "%s%d: Verified %dMB. %d%% complete.\r", sim_dname (dptr), (int)(uptr-dptr->units), (int)((((float)lba)*sector_size)/1000000), (int)((((float)lba)*100)/total_sectors));
+ for (lba = 0; (lba < total_sectors) && (r == SCPE_OK); lba += sects_read) {
+ sim_messagef (SCPE_OK, "%s%d: Verified %u/%u sectors. %d%% complete.\r", sim_dname (dptr), (int)(uptr-dptr->units), (uint32)lba, (uint32)total_sectors, (int)((((float)lba)*100)/total_sectors));
sects = sectors_per_buffer;
if (lba + sects > total_sectors)
sects = total_sectors - lba;
- r = sim_disk_rdsect (uptr, lba, copy_buf, NULL, sects);
+ r = sim_disk_rdsect (uptr, lba, copy_buf, §s_read, sects);
if (r == SCPE_OK) {
uint32 saved_unit_flags = uptr->flags;
FILE *save_unit_fileref = uptr->fileref;
sim_disk_set_fmt (uptr, 0, "VHD", NULL);
uptr->fileref = vhd;
- r = sim_disk_rdsect (uptr, lba, verify_buf, NULL, sects);
+ r = sim_disk_rdsect (uptr, lba, verify_buf, &verify_read, sects_read);
uptr->fileref = save_unit_fileref;
uptr->flags = saved_unit_flags;
if (r == SCPE_OK) {
- if (0 != memcmp (copy_buf, verify_buf, 1024*1024))
+ if ((sects_read != verify_read) ||
+ (0 != memcmp (copy_buf, verify_buf, verify_read*sector_size)))
r = SCPE_IOERR;
}
}
+ if (r != SCPE_OK)
+ break;
}
if (!sim_quiet) {
if (r == SCPE_OK)
- sim_messagef (r, "\n%s%d: Verified %dMB. Done.\n", sim_dname (dptr), (int)(uptr-dptr->units), (int)(((t_offset)lba*sector_size)/1000000));
+ sim_messagef (r, "\n%s%d: Verified %u sectors. Done.\n", sim_dname (dptr), (int)(uptr-dptr->units), (uint32)total_sectors);
else {
t_lba i;
uint32 save_dctrl = dptr->dctrl;
FILE *save_sim_deb = sim_deb;
- for (i = 0; i < (1024*1024/sector_size); ++i)
+ for (i = 0; i < sects_read; ++i)
if (0 != memcmp (copy_buf+i*sector_size, verify_buf+i*sector_size, sector_size))
break;
sim_printf ("\n%s%d: Verification Error on lbn %d.\n", sim_dname (dptr), (int)(uptr-dptr->units), lba+i);
dptr->dctrl = 0xFFFFFFFF;
sim_deb = stdout;
@@ -1509,11 +2426,11 @@
vhd = sim_vhd_disk_merge (gbuf, &Parent);
if (vhd) {
t_stat r;
sim_vhd_disk_close (vhd);
- r = sim_disk_attach (uptr, Parent, sector_size, xfer_element_size, dontautosize, dbit, dtype, pdp11tracksize, completion_delay);
+ r = sim_disk_attach (uptr, Parent, sector_size, xfer_element_size, dontchangecapac, dbit, dtype, pdp11tracksize, completion_delay);
free (Parent);
return r;
}
return SCPE_ARG;
}
@@ -1525,10 +2442,11 @@
sim_disk_set_fmt (uptr, 0, "VHD", NULL); /* set file format to VHD */
sim_vhd_disk_close (uptr->fileref); /* close vhd file*/
uptr->fileref = NULL;
open_function = sim_vhd_disk_open;
size_function = sim_vhd_disk_size;
+ storage_function = sim_vhd_disk_info;
break;
}
if (NULL != (uptr->fileref = sim_os_disk_open_raw (cptr, "rb"))) {
sim_disk_set_fmt (uptr, 0, "RAW", NULL); /* set file format to RAW */
sim_os_disk_close_raw (uptr->fileref); /* close raw file*/
@@ -1548,10 +2466,11 @@
break;
case DKUF_F_VHD: /* VHD format */
open_function = sim_vhd_disk_open;
create_function = sim_vhd_disk_create;
size_function = sim_vhd_disk_size;
+ storage_function = sim_os_disk_info_raw;
break;
case DKUF_F_RAW: /* Raw Physical Disk Access */
open_function = sim_os_disk_open_raw;
size_function = sim_os_disk_size_raw;
storage_function = sim_os_disk_info_raw;
@@ -1563,16 +2482,16 @@
uptr->disk_ctx = ctx = (struct disk_context *)calloc(1, sizeof(struct disk_context));
if ((uptr->filename == NULL) || (uptr->disk_ctx == NULL))
return _err_return (uptr, SCPE_MEM);
strlcpy (uptr->filename, cptr, CBUFSIZE); /* save name */
ctx->sector_size = (uint32)sector_size; /* save sector_size */
-ctx->capac_factor = ((dptr->dwidth / dptr->aincr) == 16) ? 2 : 1; /* save capacity units (word: 2, byte: 1) */
+ctx->capac_factor = ((dptr->dwidth / dptr->aincr) >= 32) ? 8 : ((dptr->dwidth / dptr->aincr) == 16) ? 2 : 1; /* save capacity units (quadword: 8, word: 2, byte: 1) */
ctx->xfer_element_size = (uint32)xfer_element_size; /* save xfer_element_size */
ctx->dptr = dptr; /* save DEVICE pointer */
ctx->dbit = dbit; /* save debug bit */
ctx->media_removed = 0; /* default present */
-sim_debug_unit (ctx->dbit, uptr, "sim_disk_attach(unit=%d,filename='%s')\n", (int)(uptr-ctx->dptr->units), uptr->filename);
+sim_debug_unit (ctx->dbit, uptr, "sim_disk_attach(unit=%d,filename='%s')\n", (int)(uptr - ctx->dptr->units), uptr->filename);
ctx->auto_format = auto_format; /* save that we auto selected format */
ctx->storage_sector_size = (uint32)sector_size; /* Default */
if ((sim_switches & SWMASK ('R')) || /* read only? */
((uptr->flags & UNIT_RO) != 0)) {
if (((uptr->flags & UNIT_ROABLE) == 0) && /* allowed? */
@@ -1608,21 +2527,44 @@
sim_messagef (SCPE_OK, "%s%d: creating new file\n", sim_dname (dptr), (int)(uptr-dptr->units));
created = TRUE;
}
} /* end if null */
} /* end else */
-if (DK_GET_FMT (uptr) == DKUF_F_VHD) {
- if ((created) && dtype)
- sim_vhd_disk_set_dtype (uptr->fileref, dtype);
- if (dtype && strcmp (dtype, sim_vhd_disk_get_dtype (uptr->fileref))) {
+if ((DK_GET_FMT (uptr) == DKUF_F_VHD) || (ctx->footer)) {
+ uint32 sector_size, xfer_element_size;
+ char created_name[64];
+ const char *container_dtype = ctx->footer ? (char *)ctx->footer->DriveType : sim_vhd_disk_get_dtype (uptr->fileref, §or_size, &xfer_element_size, created_name);
+
+ if (ctx->footer) {
+ sector_size = NtoHl (ctx->footer->SectorSize);
+ xfer_element_size = NtoHl (ctx->footer->TransferElementSize);
+ }
+ if ((DK_GET_FMT (uptr) == DKUF_F_VHD) && created && dtype)
+ sim_vhd_disk_set_dtype (uptr->fileref, dtype, ctx->sector_size, ctx->xfer_element_size);
+ if (dtype) {
char cmd[32];
+ t_stat r = SCPE_OK;
- sprintf (cmd, "%s%d %s", dptr->name, (int)(uptr-dptr->units), sim_vhd_disk_get_dtype (uptr->fileref));
- set_cmd (0, cmd);
+ if (((sector_size == 0) || (sector_size == ctx->sector_size)) &&
+ ((xfer_element_size == 0) || (xfer_element_size == ctx->xfer_element_size))) {
+ sprintf (cmd, "%s%d %s", dptr->name, (int)(uptr-dptr->units), sim_vhd_disk_get_dtype (uptr->fileref, NULL, NULL, NULL));
+ r = set_cmd (0, cmd);
+ if (r != SCPE_OK)
+ r = sim_messagef (r, "Can't set %s%d to drive type %s\n", dptr->name, (int)(uptr-dptr->units), sim_vhd_disk_get_dtype (uptr->fileref, NULL, NULL, NULL));
+ }
+ else
+ r = sim_messagef (SCPE_INCOMPDSK, "Disk created by the %s simulator is incompatible with the %s simulator\n", created_name, sim_name);
+ if (r != SCPE_OK) {
+ uptr->flags |= UNIT_ATT;
+ sim_disk_detach (uptr); /* report error now */
+ sprintf (cmd, "%s%d %s", dptr->name, (int)(uptr-dptr->units), dtype);/* restore original dtype */
+ set_cmd (0, cmd);
+ return r;
+ }
}
}
-uptr->flags = uptr->flags | UNIT_ATT;
+uptr->flags |= UNIT_ATT;
uptr->pos = 0;
/* Get Device attributes if they are available */
if (storage_function)
storage_function (uptr->fileref, &ctx->storage_sector_size, &ctx->removable, &ctx->is_cdrom);
@@ -1630,12 +2572,12 @@
if ((created) && (!copied)) {
t_stat r = SCPE_OK;
uint8 *secbuf = (uint8 *)calloc (128, ctx->sector_size); /* alloc temp sector buf */
/*
- On a newly created disk, we write a zero sector to the last and the
- first sectors. This serves 3 purposes:
+ On a newly created disk, we write zeros to the whole disk.
+ This serves 3 purposes:
1) it avoids strange allocation delays writing newly allocated
storage at the end of the disk during simulator operation
2) it allocates storage for the whole disk at creation time to
avoid strange failures which may happen during simulator execution
if the containing disk is full
@@ -1659,67 +2601,74 @@
sim_disk_detach (uptr); /* report error now */
(void)remove (cptr); /* remove the created file */
return SCPE_OPENERR;
}
if (sim_switches & SWMASK ('I')) { /* Initialize To Sector Address */
- uint8 *init_buf = (uint8*) malloc (1024*1024);
+ size_t init_buf_size = 1024*1024;
+ uint8 *init_buf = (uint8*) malloc (init_buf_size);
t_lba lba, sect;
- uint32 capac_factor = ((dptr->dwidth / dptr->aincr) == 16) ? 2 : 1; /* capacity units (word: 2, byte: 1) */
- t_seccnt sectors_per_buffer = (t_seccnt)((1024*1024)/sector_size);
+ uint32 capac_factor = ((dptr->dwidth / dptr->aincr) >= 32) ? 8 : ((dptr->dwidth / dptr->aincr) == 16) ? 2 : 1; /* capacity units (quadword: 8, word: 2, byte: 1) */
+ t_seccnt sectors_per_buffer = (t_seccnt)((init_buf_size)/sector_size);
t_lba total_sectors = (t_lba)((uptr->capac*capac_factor)/(sector_size/((dptr->flags & DEV_SECTORS) ? 512 : 1)));
t_seccnt sects = sectors_per_buffer;
if (!init_buf) {
sim_disk_detach (uptr); /* report error now */
(void)remove (cptr);
return SCPE_MEM;
}
+ sim_messagef (SCPE_OK, "Initializing %u sectors each %u bytes in size with the sector address\n", (uint32)total_sectors, (uint32)sector_size);
for (lba = 0; (lba < total_sectors) && (r == SCPE_OK); lba += sects) {
+ t_seccnt sects_written;
+
sects = sectors_per_buffer;
if (lba + sects > total_sectors)
sects = total_sectors - lba;
for (sect = 0; sect < sects; sect++) {
t_lba offset;
for (offset = 0; offset < sector_size; offset += sizeof(uint32))
*((uint32 *)&init_buf[sect*sector_size + offset]) = (uint32)(lba + sect);
}
- r = sim_disk_wrsect (uptr, lba, init_buf, NULL, sects);
- if (r != SCPE_OK) {
+ r = sim_disk_wrsect (uptr, lba, init_buf, §s_written, sects);
+ if ((r != SCPE_OK) || (sects != sects_written)) {
free (init_buf);
sim_disk_detach (uptr); /* report error now */
(void)remove (cptr); /* remove the created file */
- return SCPE_OPENERR;
+ return sim_messagef (SCPE_OPENERR, "Error initializing each sector with its address: %s\n",
+ (r == SCPE_OK) ? sim_error_text (r) : "sectors written not what was requested");
}
- sim_messagef (SCPE_OK, "%s%d: Initialized To Sector Address %dMB. %d%% complete.\r", sim_dname (dptr), (int)(uptr-dptr->units), (int)((((float)lba)*sector_size)/1000000), (int)((((float)lba)*100)/total_sectors));
+ sim_messagef (SCPE_OK, "%s%d: Initialized To Sector Address %u/%u sectors. %d%% complete.\r", sim_dname (dptr), (int)(uptr-dptr->units), (uint32)(lba + sects_written), (uint32)total_sectors, (int)((((float)lba)*100)/total_sectors));
}
- sim_messagef (SCPE_OK, "%s%d: Initialized To Sector Address %dMB. 100%% complete.\n", sim_dname (dptr), (int)(uptr-dptr->units), (int)((((float)lba)*sector_size)/1000000));
+ sim_messagef (SCPE_OK, "%s%d: Initialized To Sector Address %u sectors. 100%% complete. \n", sim_dname (dptr), (int)(uptr-dptr->units), (uint32)total_sectors);
free (init_buf);
}
if (pdp11tracksize)
sim_disk_pdp11_bad_block (uptr, pdp11tracksize, sector_size/sizeof(uint16));
}
-
if (sim_switches & SWMASK ('K')) {
t_stat r = SCPE_OK;
t_lba lba, sect;
- uint32 capac_factor = ((dptr->dwidth / dptr->aincr) == 16) ? 2 : 1; /* capacity units (word: 2, byte: 1) */
+ uint32 capac_factor = ((dptr->dwidth / dptr->aincr) >= 32) ? 8 : ((dptr->dwidth / dptr->aincr) == 16) ? 2 : 1; /* capacity units (word: 2, byte: 1) */
t_seccnt sectors_per_buffer = (t_seccnt)((1024*1024)/sector_size);
t_lba total_sectors = (t_lba)((uptr->capac*capac_factor)/(sector_size/((dptr->flags & DEV_SECTORS) ? 512 : 1)));
t_seccnt sects = sectors_per_buffer;
+ t_seccnt sects_verify;
uint8 *verify_buf = (uint8*) malloc (1024*1024);
if (!verify_buf) {
sim_disk_detach (uptr); /* report error now */
return SCPE_MEM;
}
- for (lba = 0; (lba < total_sectors) && (r == SCPE_OK); lba += sects) {
+ for (lba = 0; (lba < total_sectors) && (r == SCPE_OK); lba += sects_verify) {
sects = sectors_per_buffer;
if (lba + sects > total_sectors)
sects = total_sectors - lba;
- r = sim_disk_rdsect (uptr, lba, verify_buf, NULL, sects);
+ r = sim_disk_rdsect (uptr, lba, verify_buf, §s_verify, sects);
if (r == SCPE_OK) {
- for (sect = 0; sect < sects; sect++) {
+ if (sects != sects_verify)
+ sim_printf ("\n%s%d: Verification Error on lbn %d(0x%X) of %d(0x%X) Requested %u sectors, read %u sectors.\n", sim_dname (dptr), (int)(uptr-dptr->units), (int)(lba+sect), (int)(lba+sect), (int)total_sectors, (int)total_sectors, sects, sects_verify);
+ for (sect = 0; sect < sects_verify; sect++) {
t_lba offset;
t_bool sect_error = FALSE;
for (offset = 0; offset < sector_size; offset += sizeof(uint32)) {
if (*((uint32 *)&verify_buf[sect*sector_size + offset]) != (uint32)(lba + sect)) {
@@ -1738,48 +2687,77 @@
dptr->dctrl = save_dctrl;
sim_deb = save_sim_deb;
}
}
}
- sim_messagef (SCPE_OK, "%s%d: Verified containing Sector Address %dMB. %d%% complete.\r", sim_dname (dptr), (int)(uptr-dptr->units), (int)((((float)lba)*sector_size)/1000000), (int)((((float)lba)*100)/total_sectors));
- }
- sim_messagef (SCPE_OK, "%s%d: Verified containing Sector Address %dMB. 100%% complete.\n", sim_dname (dptr), (int)(uptr-dptr->units), (int)((((float)lba)*sector_size)/1000000));
- free (verify_buf);
- uptr->dynflags |= UNIT_DISK_CHK;
- }
-
-filesystem_size = get_filesystem_size (uptr);
-container_size = size_function (uptr->fileref);
-current_unit_size = ((t_offset)uptr->capac)*ctx->capac_factor*((dptr->flags & DEV_SECTORS) ? 512 : 1);
-if (container_size && (container_size != (t_offset)-1)) {
- if (dontautosize) {
- t_addr saved_capac = uptr->capac;
-
- if ((filesystem_size != (t_offset)-1) &&
- (filesystem_size > current_unit_size)) {
- if (!sim_quiet) {
- uptr->capac = (t_addr)(filesystem_size/(ctx->capac_factor*((dptr->flags & DEV_SECTORS) ? 512 : 1)));
- sim_printf ("%s%d: The file system on the disk %s is larger than simulated device (%s > ", sim_dname (dptr), (int)(uptr-dptr->units), cptr, sprint_capac (dptr, uptr));
- uptr->capac = saved_capac;
- sim_printf ("%s)\n", sprint_capac (dptr, uptr));
- }
- sim_disk_detach (uptr);
- return SCPE_OPENERR;
- }
- if ((container_size < current_unit_size) &&
- ((DKUF_F_VHD == DK_GET_FMT (uptr)) || (0 != (uptr->flags & UNIT_RO)))) {
- if (!sim_quiet) {
- uptr->capac = (t_addr)(container_size/(ctx->capac_factor*((dptr->flags & DEV_SECTORS) ? 512 : 1)));
- sim_printf ("%s%d: non expandable disk container '%s' is smaller than simulated device (%s < ", sim_dname (dptr), (int)(uptr-dptr->units), cptr, sprint_capac (dptr, uptr));
- uptr->capac = saved_capac;
- sim_printf ("%s)\n", sprint_capac (dptr, uptr));
- }
- sim_disk_detach (uptr);
- return SCPE_OPENERR;
- }
- }
- else { /* Autosize */
+ sim_messagef (SCPE_OK, "%s%d: Verified containing Sector Address %u/%u sectors. %d%% complete.\r", sim_dname (dptr), (int)(uptr-dptr->units), (uint32)lba, (uint32)total_sectors, (int)((((float)lba)*100)/total_sectors));
+ }
+ sim_messagef (SCPE_OK, "%s%d: Verified containing Sector Address %u sectors. 100%% complete. \n", sim_dname (dptr), (int)(uptr-dptr->units), (uint32)lba);
+ free (verify_buf);
+ uptr->dynflags |= UNIT_DISK_CHK;
+ }
+
+if (get_disk_footer (uptr) != SCPE_OK) {
+ sim_disk_detach (uptr);
+ return SCPE_OPENERR;
+ }
+filesystem_size = get_filesystem_size (uptr);
+container_size = sim_disk_size (uptr);
+current_unit_size = ((t_offset)uptr->capac)*ctx->capac_factor*((dptr->flags & DEV_SECTORS) ? 512 : 1);
+if (container_size && (container_size != (t_offset)-1)) {
+ if (dontchangecapac) {
+ t_addr saved_capac = uptr->capac;
+
+ if (filesystem_size == (t_offset)-1) /* No file system found? */
+ filesystem_size = container_size; /* Assume full container */
+ if (drivetypes != NULL) {
+ /* Walk through all potential drive types until we find one the right size */
+ while (*drivetypes != NULL) {
+ char cmd[CBUFSIZE];
+ t_stat st;
+
+ uptr->flags &= ~UNIT_ATT; /* temporarily mark as un-attached */
+ sprintf (cmd, "%s %s", sim_uname (uptr), *drivetypes);
+ st = set_cmd (0, cmd);
+ uptr->flags |= UNIT_ATT; /* restore attached indicator */
+ if (st == SCPE_OK)
+ current_unit_size = ((t_offset)uptr->capac)*ctx->capac_factor*((dptr->flags & DEV_SECTORS) ? 512 : 1);
+ if (current_unit_size >= filesystem_size)
+ break;
+ ++drivetypes;
+ }
+ if (filesystem_size > current_unit_size) {
+ if (!sim_quiet) {
+ uptr->capac = (t_addr)(filesystem_size/(ctx->capac_factor*((dptr->flags & DEV_SECTORS) ? 512 : 1)));
+ sim_printf ("%s%d: The file system on the disk %s is larger than simulated device (%s > ", sim_dname (dptr), (int)(uptr-dptr->units), cptr, sprint_capac (dptr, uptr));
+ uptr->capac = saved_capac;
+ sim_printf ("%s)\n", sprint_capac (dptr, uptr));
+ }
+ sim_disk_detach (uptr);
+ return SCPE_FSSIZE;
+ }
+ }
+ if ((container_size != current_unit_size) &&
+ ((DKUF_F_VHD == DK_GET_FMT (uptr)) || (0 != (uptr->flags & UNIT_RO)) ||
+ (ctx->footer))) {
+ if (!sim_quiet) {
+ int32 saved_switches = sim_switches;
+ const char *container_dtype = ctx->footer ? (const char *)ctx->footer->DriveType : "";
+
+ sim_switches = SWMASK ('R');
+ uptr->capac = (t_addr)(container_size/(ctx->capac_factor*((dptr->flags & DEV_SECTORS) ? 512 : 1)));
+ sim_printf ("%s%d: non expandable %s disk container '%s' is %s than simulated device (%s %s ",
+ sim_dname (dptr), (int)(uptr-dptr->units), container_dtype, cptr, (container_size < current_unit_size) ? "smaller" : "larger", sprint_capac (dptr, uptr), (container_size < current_unit_size) ? "<" : ">");
+ uptr->capac = saved_capac;
+ sim_printf ("%s)\n", sprint_capac (dptr, uptr));
+ sim_switches = saved_switches;
+ }
+ sim_disk_detach (uptr);
+ return SCPE_OPENERR;
+ }
+ }
+ else { /* Autosize by changing capacity */
if (filesystem_size != (t_offset)-1) { /* Known file system data size AND */
if (filesystem_size > container_size) /* Data size greater than container size? */
container_size = filesystem_size + /* Use file system data size */
(pdp11tracksize * sector_size);/* plus any bad block data beyond the file system */
}
@@ -1790,10 +2768,13 @@
container_size = current_unit_size; /* Use MAX of container or current device size */
}
uptr->capac = (t_addr)(container_size/(ctx->capac_factor*((dptr->flags & DEV_SECTORS) ? 512 : 1))); /* update current size */
}
}
+
+if (dtype && (created || (ctx->footer == NULL)))
+ store_disk_footer (uptr, dtype);
#if defined (SIM_ASYNCH_IO)
sim_disk_set_async (uptr, completion_delay);
#endif
uptr->io_flush = _sim_disk_io_flush;
@@ -1814,11 +2795,11 @@
return SCPE_UNATT;
ctx = (struct disk_context *)uptr->disk_ctx;
fileref = uptr->fileref;
-sim_debug_unit (ctx->dbit, uptr, "sim_disk_detach(unit=%d,filename='%s')\n", (int)(uptr-ctx->dptr->units), uptr->filename);
+sim_debug_unit (ctx->dbit, uptr, "sim_disk_detach(unit=%d,filename='%s')\n", (int)(uptr - ctx->dptr->units), uptr->filename);
switch (DK_GET_FMT (uptr)) { /* case on format */
case DKUF_F_STD: /* Simh */
close_function = fclose;
break;
@@ -1847,10 +2828,11 @@
uptr->flags &= ~(UNIT_ATT | UNIT_RO);
uptr->dynflags &= ~(UNIT_NO_FIO | UNIT_DISK_CHK);
free (uptr->filename);
uptr->filename = NULL;
uptr->fileref = NULL;
+free (ctx->footer);
free (uptr->disk_ctx);
uptr->disk_ctx = NULL;
uptr->io_flush = NULL;
if (auto_format)
sim_disk_set_fmt (uptr, 0, "AUTO", NULL); /* restore file format */
@@ -1859,20 +2841,57 @@
return SCPE_OK;
}
t_stat sim_disk_attach_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
{
+static struct example_fields {
+ const char *dname;
+ const char *dtype;
+ const char *dsize;
+ const char *dtype2;
+ const char *dsize2;
+ const char *dtype3;
+ const char *dsize3;
+ const char *dtype4;
+ const char *dsize4;
+ } ex_data[] = {
+ {"RQ", "RD54", "159MB", "RX50", "409KB", "RA81", "456MB", "RA92", "1505MB"},
+ {"RP", "RM03", "33MW", "RM03", "33MW", "RP07", "258MW", "RM03", "15MW"},
+ {"RP", "RM03", "39MW", "RM03", "39MW", "RP07", "110MW", "RM03", "15MW"},
+ };
+struct example_fields *ex = &ex_data[0];
+
+if (strcmp (dptr->name, "RP") == 0)
+ ex = &ex_data[1];
+if (strstr (sim_name, "-10")) {
+ ex = &ex_data[2];
+ if (strstr (sim_name, "PDP") == NULL)
+ ex->dname = "RPA";
+ }
+
fprintf (st, "%s Disk Attach Help\n\n", dptr->name);
-fprintf (st, "Disk container files can be one of 3 different types:\n\n");
-fprintf (st, " SIMH A disk is an unstructured binary file of the size appropriate\n");
-fprintf (st, " for the disk drive being simulated\n");
-fprintf (st, " VHD Virtual Disk format which is described in the \"Microsoft\n");
-fprintf (st, " Virtual Hard Disk (VHD) Image Format Specification\". The\n");
-fprintf (st, " VHD implementation includes support for 1) Fixed (Preallocated)\n");
-fprintf (st, " disks, 2) Dynamically Expanding disks, and 3) Differencing disks.\n");
-fprintf (st, " RAW platform specific access to physical disk or CDROM drives\n\n");
+fprintf (st, "Disk container files can be one of several different types:\n\n");
+if (strstr (sim_name, "-10") == NULL) {
+ fprintf (st, " SIMH A disk is an unstructured binary file of the size appropriate\n");
+ fprintf (st, " for the disk drive being simulated accessed by C runtime APIs\n");
+ fprintf (st, " VHD Virtual Disk format which is described in the \"Microsoft\n");
+ fprintf (st, " Virtual Hard Disk (VHD) Image Format Specification\". The\n");
+ fprintf (st, " VHD implementation includes support for 1) Fixed (Preallocated)\n");
+ fprintf (st, " disks, 2) Dynamically Expanding disks, and 3) Differencing disks.\n");
+ fprintf (st, " RAW platform specific access to physical disk or CDROM drives\n\n");
+ }
+else {
+ fprintf (st, " SIMH A disk is an unstructured binary file of 64bit integers\n"
+ " access by C runtime APIs\n");
+ fprintf (st, " VHD A disk is an unstructured binary file of 64bit integers\n"
+ " contained in a VHD container\n");
+ fprintf (st, " RAW A disk is an unstructured binary file of 64bit integers\n"
+ " accessed by direct read/write APIs\n");
+ fprintf (st, " DBD9 Compatible with KLH10 is a packed big endian word\n");
+ fprintf (st, " DLD9 Compatible with KLH10 is a packed little endian word\n\n");
+ }
fprintf (st, "Virtual (VHD) Disks supported conform to \"Virtual Hard Disk Image Format\n");
fprintf (st, "Specification\", Version 1.0 October 11, 2006.\n");
fprintf (st, "Dynamically expanding disks never change their \"Virtual Size\", but they don't\n");
fprintf (st, "consume disk space on the containing storage until the virtual sectors in the\n");
fprintf (st, "disk are actually written to (i.e. a 2GB Dynamic disk container file with only\n");
@@ -1881,23 +2900,40 @@
fprintf (st, "which describes the drive size and the simh device type in use when the VHD\n");
fprintf (st, "was created. This metadata is therefore available whenever that VHD is\n");
fprintf (st, "attached to an emulated disk device in the future so the device type and\n");
fprintf (st, "size can be automatically be configured.\n\n");
-if (0 == (uptr-dptr->units)) {
- if (dptr->numunits > 1) {
- uint32 i;
+if (dptr->numunits > 1) {
+ uint32 i, attachable_count = 0, out_count = 0, skip_count;
- for (i=0; i < dptr->numunits; ++i)
- if (dptr->units[i].flags & UNIT_ATTABLE)
+ for (i=0; i < dptr->numunits; ++i)
+ if ((dptr->units[i].flags & UNIT_ATTABLE) &&
+ !(dptr->units[i].flags & UNIT_DIS))
+ ++attachable_count;
+ for (i=0; (i < dptr->numunits) && (out_count < 2); ++i)
+ if ((dptr->units[i].flags & UNIT_ATTABLE) &&
+ !(dptr->units[i].flags & UNIT_DIS)) {
+ fprintf (st, " sim> ATTACH {switches} %s%d diskfile\n", dptr->name, i);
+ ++out_count;
+ }
+ if (attachable_count > 4) {
+ fprintf (st, " .\n");
+ fprintf (st, " .\n");
+ fprintf (st, " .\n");
+ }
+ skip_count = attachable_count - 2;
+ for (i=0; i < dptr->numunits; ++i)
+ if ((dptr->units[i].flags & UNIT_ATTABLE) &&
+ !(dptr->units[i].flags & UNIT_DIS)) {
+ if (skip_count == 0)
fprintf (st, " sim> ATTACH {switches} %s%d diskfile\n", dptr->name, i);
- }
- else
- fprintf (st, " sim> ATTACH {switches} %s diskfile\n", dptr->name);
+ else
+ --skip_count;
+ }
}
else
- fprintf (st, " sim> ATTACH {switches} %s diskfile\n\n", dptr->name);
+ fprintf (st, " sim> ATTACH {switches} %s diskfile\n", dptr->name);
fprintf (st, "\n%s attach command switches\n", dptr->name);
fprintf (st, " -R Attach Read Only.\n");
fprintf (st, " -E Must Exist (if not specified an attempt to create the indicated\n");
fprintf (st, " disk container will be attempted).\n");
fprintf (st, " -F Open the indicated disk container in a specific format (default\n");
@@ -1918,62 +2954,66 @@
fprintf (st, " disk)\n");
fprintf (st, " -M Merge a Differencing VHD into its parent VHD disk\n");
fprintf (st, " -O Override consistency checks when attaching differencing disks\n");
fprintf (st, " which have unexpected parent disk GUID or timestamps\n\n");
fprintf (st, " -U Fix inconsistencies which are overridden by the -O switch\n");
-fprintf (st, " -Y Answer Yes to prompt to overwrite last track (on disk create)\n");
-fprintf (st, " -N Answer No to prompt to overwrite last track (on disk create)\n");
+if (strstr (sim_name, "-10") == NULL) {
+ fprintf (st, " -Y Answer Yes to prompt to overwrite last track (on disk create)\n");
+ fprintf (st, " -N Answer No to prompt to overwrite last track (on disk create)\n");
+ }
fprintf (st, "Examples:\n");
-fprintf (st, " sim> show rq\n");
-fprintf (st, " RQ, address=20001468-2000146B*, no vector, 4 units\n");
-fprintf (st, " RQ0, 159MB, not attached, write enabled, RD54, autosize, SIMH format\n");
-fprintf (st, " RQ1, 159MB, not attached, write enabled, RD54, autosize, SIMH format\n");
-fprintf (st, " RQ2, 159MB, not attached, write enabled, RD54, autosize, SIMH format\n");
-fprintf (st, " RQ3, 409KB, not attached, write enabled, RX50, autosize, SIMH format\n");
-fprintf (st, " sim> atta rq0 RA81.vhd\n");
-fprintf (st, " sim> show rq0\n");
-fprintf (st, " RQ0, 456MB, attached to RA81.vhd, write enabled, RA81, autosize, VHD format\n");
-fprintf (st, " sim> set rq2 ra92\n");
-fprintf (st, " sim> att rq2 -f vhd RA92.vhd\n");
-fprintf (st, " RQ2: creating new file\n");
-fprintf (st, " sim> sho rq2\n");
-fprintf (st, " RQ2, 1505MB, attached to RA92.vhd, write enabled, RA92, autosize, VHD format\n");
-fprintf (st, " sim> ! dir RA92.vhd\n");
-fprintf (st, " Volume in drive H is New Volume\n");
-fprintf (st, " Volume Serial Number is F8DE-510C\n\n");
+fprintf (st, " sim> show %s\n", ex->dname);
+fprintf (st, " %s, address=20001468-2000146B*, no vector, 4 units\n", ex->dname);
+fprintf (st, " %s0, %s, not attached, write enabled, %s, autosize, AUTO detect format\n", ex->dname, ex->dsize, ex->dtype);
+fprintf (st, " %s1, %s, not attached, write enabled, %s, autosize, AUTO detect format\n", ex->dname, ex->dsize, ex->dtype);
+fprintf (st, " %s2, %s, not attached, write enabled, %s, autosize, AUTO detect format\n", ex->dname, ex->dsize, ex->dtype);
+fprintf (st, " %s3, %s, not attached, write enabled, %s, autosize, AUTO detect format\n", ex->dname, ex->dsize2, ex->dtype2);
+fprintf (st, " sim> # attach an existing VHD and determine its size and type automatically\n");
+fprintf (st, " sim> attach %s0 %s.vhd\n", ex->dname, ex->dtype3);
+fprintf (st, " sim> show %s0\n", ex->dname);
+fprintf (st, " %s0, %s, attached to %s.vhd, write enabled, %s, autosize, VHD format\n", ex->dname, ex->dsize3, ex->dtype3, ex->dtype3);
+fprintf (st, " sim> # create a new %s drive type VHD\n", ex->dtype4);
+fprintf (st, " sim> set %s2 %s\n", ex->dname, ex->dtype4);
+fprintf (st, " sim> attach %s2 -f vhd %s.vhd\n", ex->dname, ex->dtype4);
+fprintf (st, " %s2: creating new file\n", ex->dname);
+fprintf (st, " sim> show %s2\n", ex->dname);
+fprintf (st, " %s2, %s, attached to %s.vhd, write enabled, %s, autosize, VHD format\n", ex->dname, ex->dsize4, ex->dtype4, ex->dtype4);
+fprintf (st, " sim> # examine the size consumed by the %s VHD file\n", ex->dsize4);
+fprintf (st, " sim> dir %s.vhd\n", ex->dtype4);
fprintf (st, " Directory of H:\\Data\n\n");
-fprintf (st, " 04/14/2011 12:57 PM 5,120 RA92.vhd\n");
+fprintf (st, " 04/14/2011 12:57 PM 5,120 %s.vhd\n", ex->dtype4);
fprintf (st, " 1 File(s) 5,120 bytes\n");
-fprintf (st, " sim> atta rq3 -d RA92-1-Diff.vhd RA92.vhd\n");
-fprintf (st, " sim> atta rq3 -c RA92-1.vhd RA92.vhd\n");
-fprintf (st, " RQ3: creating new virtual disk 'RA92-1.vhd'\n");
-fprintf (st, " RQ3: Copied 1505MB. 99%% complete.\n");
-fprintf (st, " RQ3: Copied 1505MB. Done.\n");
-fprintf (st, " sim> sh rq3\n");
-fprintf (st, " RQ3, 1505MB, attached to RA92-1.vhd, write enabled, RA92, autosize, VHD format\n");
-fprintf (st, " sim> ! dir RA92*\n");
-fprintf (st, " Volume in drive H is New Volume\n");
-fprintf (st, " Volume Serial Number is F8DE-510C\n\n");
+fprintf (st, " sim> # create a differencing vhd (%s-1-Diff.vhd) with %s.vhd as parent\n", ex->dtype4, ex->dtype4);
+fprintf (st, " sim> attach %s3 -d %s-1-Diff.vhd %s.vhd\n", ex->dname, ex->dtype4, ex->dtype4);
+fprintf (st, " sim> # create a VHD (%s-1.vhd) which is a copy of an existing disk\n", ex->dtype4);
+fprintf (st, " sim> attach %s3 -c %s-1.vhd %s.vhd\n", ex->dname, ex->dtype4, ex->dtype4);
+fprintf (st, " %s3: creating new virtual disk '%s-1.vhd'\n", ex->dname, ex->dtype4);
+fprintf (st, " %s3: Copied %s. 99%% complete.\n", ex->dname, ex->dsize4);
+fprintf (st, " %s3: Copied %s. Done.\n", ex->dname, ex->dsize4);
+fprintf (st, " sim> show %s3\n", ex->dname);
+fprintf (st, " %s3, %s, attached to %s-1.vhd, write enabled, %s, autosize, VHD format\n", ex->dname, ex->dsize4, ex->dtype4, ex->dtype4);
+fprintf (st, " sim> dir %s*\n", ex->dtype4);
fprintf (st, " Directory of H:\\Data\n\n");
-fprintf (st, " 04/14/2011 01:12 PM 5,120 RA92-1.vhd\n");
-fprintf (st, " 04/14/2011 12:58 PM 5,120 RA92.vhd\n");
+fprintf (st, " 04/14/2011 01:12 PM 5,120 %s-1.vhd\n", ex->dtype4);
+fprintf (st, " 04/14/2011 12:58 PM 5,120 %s.vhd\n", ex->dtype4);
fprintf (st, " 2 File(s) 10,240 bytes\n");
-fprintf (st, " sim> sho rq2\n");
-fprintf (st, " RQ2, 1505MB, not attached, write enabled, RA92, autosize, VHD format\n");
-fprintf (st, " sim> set rq2 ra81\n");
-fprintf (st, " sim> set rq2 noauto\n");
-fprintf (st, " sim> sho rq2\n");
-fprintf (st, " RQ2, 456MB, not attached, write enabled, RA81, noautosize, VHD format\n");
-fprintf (st, " sim> set rq2 format=simh\n");
-fprintf (st, " sim> sho rq2\n");
-fprintf (st, " RQ2, 456MB, not attached, write enabled, RA81, noautosize, SIMH format\n");
-fprintf (st, " sim> atta rq2 -c RA81-Copy.vhd VMS055.dsk\n");
-fprintf (st, " RQ2: creating new virtual disk 'RA81-Copy.vhd'\n");
-fprintf (st, " RQ2: Copied 456MB. 99%% complete.\n");
-fprintf (st, " RQ2: Copied 456MB. Done.\n");
-fprintf (st, " sim> sho rq2\n");
-fprintf (st, " RQ2, 456MB, attached to RA81-Copy.vhd, write enabled, RA81, noautosize, VHD format\n");
+fprintf (st, " sim> show %s2\n", ex->dname);
+fprintf (st, " %s2, %s, not attached, write enabled, %s, autosize, VHD format\n", ex->dname, ex->dsize4, ex->dtype4);
+fprintf (st, " sim> set %s2 %s\n", ex->dname, ex->dtype3);
+fprintf (st, " sim> set %s2 noauto\n", ex->dname);
+fprintf (st, " sim> show %s2\n", ex->dname);
+fprintf (st, " %s2, %s, not attached, write enabled, %s, noautosize, VHD format\n", ex->dname, ex->dsize3, ex->dtype3);
+fprintf (st, " sim> set %s2 format=simh\n", ex->dname);
+fprintf (st, " sim> show %s2\n", ex->dname);
+fprintf (st, " %s2, %s, not attached, write enabled, %s, noautosize, SIMH format\n", ex->dname, ex->dsize3, ex->dtype3);
+fprintf (st, " sim> # create a VHD from an existing SIMH format disk\n");
+fprintf (st, " sim> attach %s2 -c %s-Copy.vhd XYZZY.dsk\n", ex->dname, ex->dtype3);
+fprintf (st, " %s2: creating new virtual disk '%s-Copy.vhd'\n", ex->dname, ex->dtype3);
+fprintf (st, " %s2: Copied %s. 99%% complete.\n", ex->dname, ex->dsize3);
+fprintf (st, " %s2: Copied %s. Done.\n", ex->dname, ex->dsize3);
+fprintf (st, " sim> show %s2\n", ex->dname);
+fprintf (st, " %s2, %s, attached to %s-Copy.vhd, write enabled, %s, noautosize, VHD format\n", ex->dname, ex->dsize3, ex->dtype3, ex->dtype3);
return SCPE_OK;
}
t_bool sim_disk_vhd_support (void)
{
@@ -1990,11 +3030,11 @@
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
if (!(uptr->flags & UNIT_ATT)) /* attached? */
return SCPE_OK;
-sim_debug_unit (ctx->dbit, uptr, "sim_disk_reset(unit=%d)\n", (int)(uptr-ctx->dptr->units));
+sim_debug_unit (ctx->dbit, uptr, "sim_disk_reset(unit=%d)\n", (int)(uptr - ctx->dptr->units));
_sim_disk_io_flush(uptr);
AIO_VALIDATE(uptr);
AIO_UPDATE_QUEUE;
return SCPE_OK;
@@ -2115,18 +3155,17 @@
void sim_disk_data_trace(UNIT *uptr, const uint8 *data, size_t lba, size_t len, const char* txt, int detail, uint32 reason)
{
DEVICE *dptr = find_dev_from_unit (uptr);
-if (sim_deb && (dptr->dctrl & reason)) {
+if (sim_deb && ((uptr->dctrl | dptr->dctrl) & reason)) {
char pos[32];
sprintf (pos, "lbn: %08X ", (unsigned int)lba);
sim_data_trace(dptr, uptr, (detail ? data : NULL), pos, len, txt, reason);
}
}
-
/* OS Specific RAW Disk I/O support */
#if defined _WIN32
@@ -2568,21 +3607,61 @@
static t_stat sim_os_disk_rdsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectsread, t_seccnt sects)
{
OVERLAPPED pos;
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
-long long addr;
+long long addr = ((long long)lba) * ctx->sector_size;
+DWORD bytestoread = sects * ctx->sector_size;
-sim_debug_unit (ctx->dbit, uptr, "sim_os_disk_rdsect(unit=%d, lba=0x%X, sects=%d)\n", (int)(uptr-ctx->dptr->units), lba, sects);
+sim_debug_unit (ctx->dbit, uptr, "sim_os_disk_rdsect(unit=%d, lba=0x%X, sects=%d)\n", (int)(uptr - ctx->dptr->units), lba, sects);
+if (sectsread)
+ *sectsread = 0;
+memset (&pos, 0, sizeof (pos));
+while (bytestoread) {
+ DWORD bytesread;
+ DWORD sectorbytes;
-addr = ((long long)lba) * ctx->sector_size;
+ pos.Offset = (DWORD)addr;
+ pos.OffsetHigh = (DWORD)(addr >> 32);
+ if (!ReadFile ((HANDLE)(uptr->fileref), buf, bytestoread, &bytesread, &pos)) {
+ if (ERROR_HANDLE_EOF == GetLastError ()) { /* Return 0's for reads past EOF */
+ memset (buf, 0, bytestoread);
+ if (sectsread)
+ *sectsread += bytestoread / ctx->sector_size;
+ return SCPE_OK;
+ }
+ _set_errno_from_status (GetLastError ());
+ return SCPE_IOERR;
+ }
+ sectorbytes = (bytesread / ctx->sector_size) * ctx->sector_size;
+ if (sectsread)
+ *sectsread += sectorbytes / ctx->sector_size;
+ bytestoread -= sectorbytes;
+ if (bytestoread == 0)
+ break;
+ buf += sectorbytes;
+ addr += sectorbytes;
+ }
+return SCPE_OK;
+}
+
+static t_stat sim_os_disk_read (UNIT *uptr, t_offset addr, uint8 *buf, uint32 *bytesread, uint32 bytes)
+{
+OVERLAPPED pos;
+struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
+
+sim_debug_unit (ctx->dbit, uptr, "sim_os_disk_read(unit=%d, addr=0x%X, bytes=%u)\n", (int)(uptr - ctx->dptr->units), (uint32)addr, bytes);
+
memset (&pos, 0, sizeof (pos));
pos.Offset = (DWORD)addr;
pos.OffsetHigh = (DWORD)(addr >> 32);
-if (ReadFile ((HANDLE)(uptr->fileref), buf, sects * ctx->sector_size, (LPDWORD)sectsread, &pos)) {
- if (sectsread)
- *sectsread /= ctx->sector_size;
+if (ReadFile ((HANDLE)(uptr->fileref), buf, (DWORD)bytes, (LPDWORD)bytesread, &pos))
+ return SCPE_OK;
+if (ERROR_HANDLE_EOF == GetLastError ()) { /* Return 0's for reads past EOF */
+ memset (buf, 0, bytes);
+ if (bytesread)
+ *bytesread = bytes;
return SCPE_OK;
}
_set_errno_from_status (GetLastError ());
return SCPE_IOERR;
}
@@ -2590,27 +3669,57 @@
static t_stat sim_os_disk_wrsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectswritten, t_seccnt sects)
{
OVERLAPPED pos;
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
long long addr;
+DWORD byteswritten;
+DWORD bytestowrite = sects * ctx->sector_size;
-sim_debug_unit (ctx->dbit, uptr, "sim_os_disk_wrsect(unit=%d, lba=0x%X, sects=%d)\n", (int)(uptr-ctx->dptr->units), lba, sects);
+sim_debug_unit (ctx->dbit, uptr, "sim_os_disk_wrsect(unit=%d, lba=0x%X, sects=%d)\n", (int)(uptr - ctx->dptr->units), lba, sects);
+if (sectswritten)
+ *sectswritten = 0;
addr = ((long long)lba) * ctx->sector_size;
+memset (&pos, 0, sizeof (pos));
+while (bytestowrite) {
+ DWORD sectorbytes;
+
+ pos.Offset = (DWORD)addr;
+ pos.OffsetHigh = (DWORD)(addr >> 32);
+ if (!WriteFile ((HANDLE)(uptr->fileref), buf, bytestowrite, &byteswritten, &pos)) {
+ _set_errno_from_status (GetLastError ());
+ return SCPE_IOERR;
+ }
+ if (sectswritten)
+ *sectswritten += byteswritten / ctx->sector_size;
+ sectorbytes = (byteswritten / ctx->sector_size) * ctx->sector_size;
+ bytestowrite -= sectorbytes;
+ if (bytestowrite == 0)
+ break;
+ buf += sectorbytes;
+ addr += sectorbytes;
+ }
+return SCPE_OK;
+}
+
+static t_stat sim_os_disk_write (UNIT *uptr, t_offset addr, uint8 *buf, uint32 *byteswritten, uint32 bytes)
+{
+OVERLAPPED pos;
+struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
+
+sim_debug_unit (ctx->dbit, uptr, "sim_os_disk_write(unit=%d, lba=0x%X, bytes=%u)\n", (int)(uptr - ctx->dptr->units), (uint32)addr, bytes);
+
memset (&pos, 0, sizeof (pos));
pos.Offset = (DWORD)addr;
pos.OffsetHigh = (DWORD)(addr >> 32);
-if (WriteFile ((HANDLE)(uptr->fileref), buf, sects * ctx->sector_size, (LPDWORD)sectswritten, &pos)) {
- if (sectswritten)
- *sectswritten /= ctx->sector_size;
+if (WriteFile ((HANDLE)(uptr->fileref), buf, bytes, (LPDWORD)byteswritten, &pos))
return SCPE_OK;
- }
_set_errno_from_status (GetLastError ());
return SCPE_IOERR;
}
-#elif defined (__linux) || defined (__linux__) || defined (__sun) || defined (__sun__) || defined (__hpux) || defined (_AIX)
+#elif defined (__linux) || defined (__linux__) || defined (__APPLE__)|| defined (__sun) || defined (__sun__) || defined (__hpux) || defined (_AIX)
#include
#include
#include
#include
@@ -2702,44 +3811,103 @@
}
static t_stat sim_os_disk_rdsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectsread, t_seccnt sects)
{
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
-off_t addr;
+off_t addr = ((off_t)lba) * ctx->sector_size;
+ssize_t bytesread;
+size_t bytestoread = sects * ctx->sector_size;
+
+sim_debug_unit (ctx->dbit, uptr, "sim_os_disk_rdsect(unit=%d, lba=0x%X, sects=%d)\n", (int)(uptr - ctx->dptr->units), lba, sects);
+
+if (sectsread)
+ *sectsread = 0;
+while (bytestoread) {
+ size_t sectorbytes;
+
+ bytesread = pread((int)((long)uptr->fileref), buf, bytestoread, addr);
+ if (bytesread < 0) {
+ return SCPE_IOERR;
+ }
+ if (bytesread == 0) { /* read zeros at/past EOF */
+ bytesread = bytestoread;
+ memset (buf, 0, bytesread);
+ }
+ sectorbytes = (bytesread / ctx->sector_size) * ctx->sector_size;
+ if (sectsread)
+ *sectsread += sectorbytes / ctx->sector_size;
+ bytestoread -= sectorbytes;
+ if (bytestoread == 0)
+ break;
+ buf += sectorbytes;
+ addr += sectorbytes;
+ }
+return SCPE_OK;
+}
+
+static t_stat sim_os_disk_read (UNIT *uptr, t_offset addr, uint8 *buf, uint32 *rbytesread, uint32 bytes)
+{
+struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
ssize_t bytesread;
-sim_debug_unit (ctx->dbit, uptr, "sim_os_disk_rdsect(unit=%d, lba=0x%X, sects=%d)\n", (int)(uptr-ctx->dptr->units), lba, sects);
+sim_debug_unit (ctx->dbit, uptr, "sim_os_disk_read(unit=%d, addr=0x%X, bytes=%u)\n", (int)(uptr - ctx->dptr->units), (uint32)addr, bytes);
-addr = ((off_t)lba) * ctx->sector_size;
-bytesread = pread((int)((long)uptr->fileref), buf, sects * ctx->sector_size, addr);
+bytesread = pread((int)((long)uptr->fileref), buf, bytes, (off_t)addr);
if (bytesread < 0) {
- if (sectsread)
- *sectsread = 0;
+ if (rbytesread)
+ *rbytesread = 0;
return SCPE_IOERR;
}
-if (sectsread)
- *sectsread = bytesread / ctx->sector_size;
+if (rbytesread)
+ *rbytesread = bytesread;
return SCPE_OK;
}
static t_stat sim_os_disk_wrsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectswritten, t_seccnt sects)
{
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
-off_t addr;
+off_t addr = ((off_t)lba) * ctx->sector_size;
+ssize_t byteswritten;
+size_t bytestowrite = sects * ctx->sector_size;
+
+sim_debug_unit (ctx->dbit, uptr, "sim_os_disk_wrsect(unit=%d, lba=0x%X, sects=%d)\n", (int)(uptr - ctx->dptr->units), lba, sects);
+
+if (sectswritten)
+ *sectswritten = 0;
+while (bytestowrite) {
+ size_t sectorbytes;
+
+ byteswritten = pwrite((int)((long)uptr->fileref), buf, bytestowrite, addr);
+ if (byteswritten < 0) {
+ return SCPE_IOERR;
+ }
+ if (sectswritten)
+ *sectswritten += byteswritten / ctx->sector_size;
+ sectorbytes = (byteswritten / ctx->sector_size) * ctx->sector_size;
+ bytestowrite -= sectorbytes;
+ if (bytestowrite == 0)
+ break;
+ buf += sectorbytes;
+ addr += sectorbytes;
+ }
+return SCPE_OK;
+}
+
+static t_stat sim_os_disk_write (UNIT *uptr, t_offset addr, uint8 *buf, uint32 *rbyteswritten, uint32 bytes)
+{
+struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
ssize_t byteswritten;
-sim_debug_unit (ctx->dbit, uptr, "sim_os_disk_wrsect(unit=%d, lba=0x%X, sects=%d)\n", (int)(uptr-ctx->dptr->units), lba, sects);
+sim_debug_unit (ctx->dbit, uptr, "sim_os_disk_write(unit=%d, addr=0x%X, bytes=%u)\n", (int)(uptr - ctx->dptr->units), (uint32)addr, bytes);
-addr = ((off_t)lba) * ctx->sector_size;
-byteswritten = pwrite((int)((long)uptr->fileref), buf, sects * ctx->sector_size, addr);
-if (byteswritten < 0) {
- if (sectswritten)
- *sectswritten = 0;
+if (rbyteswritten)
+ *rbyteswritten = 0;
+byteswritten = pwrite((int)((long)uptr->fileref), buf, bytes, (off_t)addr);
+if (byteswritten < 0)
return SCPE_IOERR;
- }
-if (sectswritten)
- *sectswritten = byteswritten / ctx->sector_size;
+if (rbyteswritten)
+ *rbyteswritten = byteswritten;
return SCPE_OK;
}
static t_stat sim_os_disk_info_raw (FILE *f, uint32 *sector_size, uint32 *removable, uint32 *is_cdrom)
{
@@ -2812,13 +3980,23 @@
static t_stat sim_os_disk_rdsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectsread, t_seccnt sects)
{
return SCPE_NOFNC;
}
+
+static t_stat sim_os_disk_read (UNIT *uptr, t_offset addr, uint8 *buf, uint32 *bytesread, uint32 bytes)
+{
+return SCPE_NOFNC;
+}
static t_stat sim_os_disk_wrsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectswritten, t_seccnt sects)
{
+return SCPE_NOFNC;
+}
+
+static t_stat sim_os_disk_write (UNIT *uptr, t_offset addr, uint8 *buf, uint32 *byteswritten, uint32 bytes)
+{
return SCPE_NOFNC;
}
static t_stat sim_os_disk_info_raw (FILE *f, uint32 *sector_size, uint32 *removable, uint32 *is_cdrom)
{
@@ -2876,10 +4054,15 @@
static t_offset sim_vhd_disk_size (FILE *f)
{
return (t_offset)-1;
}
+
+static t_stat sim_vhd_disk_info (FILE *f, uint32 *sector_size, uint32 *removable, uint32 *is_cdrom)
+{
+return SCPE_NOFNC;
+}
static t_stat sim_vhd_disk_rdsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectsread, t_seccnt sects)
{
return SCPE_IOERR;
}
@@ -2897,11 +4080,11 @@
static t_stat sim_vhd_disk_set_dtype (FILE *f, const char *dtype)
{
return SCPE_NOFNC;
}
-static const char *sim_vhd_disk_get_dtype (FILE *f)
+static const char *sim_vhd_disk_get_dtype (FILE *f, uint32 *SectorSize, uint32 *xfer_element_size, char sim_name[64])
{
return NULL;
}
#else
@@ -3068,14 +4251,17 @@
/*
This field is an extension to the VHD spec and includes a simh drive type
name as a nul terminated string.
*/
uint8 DriveType[16];
+ uint32 DriveSectorSize;
+ uint32 DriveTransferElementSize;
+ uint8 CreatingSimulator[64];
/*
- This field contains zeroes. It is 400 bytes in size.
+ This field contains zeroes. It is 328 bytes in size.
*/
- uint8 Reserved[400];
+ uint8 Reserved[328];
} VHD_Footer;
/*
For dynamic and differencing disk images, the "Data Offset" field within
the image footer points to a secondary structure that provides additional
@@ -3207,13 +4393,11 @@
#define VHD_DT_Fixed 2 /* Fixed hard disk */
#define VHD_DT_Dynamic 3 /* Dynamic hard disk */
#define VHD_DT_Differencing 4 /* Differencing hard disk */
-static uint32 NtoHl(uint32 value);
-
-static uint64 NtoHll(uint64 value);
+#define VHD_Internal_SectorSize 512
typedef struct VHD_IOData *VHDHANDLE;
static t_stat ReadFilePosition(FILE *File, void *buf, size_t bufsize, size_t *bytesread, uint64 position)
{
@@ -3222,13 +4406,13 @@
if (bytesread)
*bytesread = 0;
if (!err) {
i = fread (buf, 1, bufsize, File);
- err = ferror (File);
- if ((!err) && bytesread)
+ if (bytesread)
*bytesread = i;
+ err = ferror (File);
}
return (err ? SCPE_IOERR : SCPE_OK);
}
static t_stat WriteFilePosition(FILE *File, void *buf, size_t bufsize, size_t *byteswritten, uint64 position)
@@ -3238,13 +4422,13 @@
if (byteswritten)
*byteswritten = 0;
if (!err) {
i = fwrite (buf, 1, bufsize, File);
- err = ferror (File);
- if ((!err) && byteswritten)
+ if (byteswritten)
*byteswritten = i;
+ err = ferror (File);
}
return (err ? SCPE_IOERR : SCPE_OK);
}
static uint32
@@ -3266,32 +4450,19 @@
#endif
#ifndef __BYTE_ORDER__
#define __BYTE_ORDER__ UNKNOWN
#endif
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
-static uint32
-NtoHl(uint32 value)
-{
-uint8 *l = (uint8 *)&value;
-return (uint32)l[3] | ((uint32)l[2]<<8) | ((uint32)l[1]<<16) | ((uint32)l[0]<<24);
-}
-
static uint64
NtoHll(uint64 value)
{
uint8 *l = (uint8 *)&value;
uint64 highresult = (uint64)l[3] | ((uint64)l[2]<<8) | ((uint64)l[1]<<16) | ((uint64)l[0]<<24);
uint32 lowresult = (uint64)l[7] | ((uint64)l[6]<<8) | ((uint64)l[5]<<16) | ((uint64)l[4]<<24);
return (highresult << 32) | lowresult;
}
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
-static uint32
-NtoHl(uint32 value)
-{
-return value;
-}
-
static uint64
NtoHll(uint64 value)
{
return value;
}
@@ -3415,14 +4586,14 @@
if ((sum != saved_sum) || (memcmp ("cxsparse", sDynamic->Cookie, sizeof (sDynamic->Cookie)))) {
Return = errno;
goto Return_Cleanup;
}
if (aBAT) {
- *aBAT = (uint32*) malloc(512*((sizeof(**aBAT)*NtoHl(sDynamic->MaxTableEntries)+511)/512));
+ *aBAT = (uint32*) calloc(1, VHD_Internal_SectorSize * ((sizeof(**aBAT) * NtoHl(sDynamic->MaxTableEntries) + VHD_Internal_SectorSize - 1) / VHD_Internal_SectorSize));
if (ReadFilePosition(File,
*aBAT,
- sizeof (**aBAT)*NtoHl(sDynamic->MaxTableEntries),
+ sizeof (**aBAT) * NtoHl(sDynamic->MaxTableEntries),
NULL,
NtoHll (sDynamic->TableOffset))) {
Return = EINVAL; /* File Corrupt */
goto Return_Cleanup;
}
@@ -3541,17 +4712,20 @@
static t_stat sim_vhd_disk_implemented (void)
{
return SCPE_OK;
}
-static t_stat sim_vhd_disk_set_dtype (FILE *f, const char *dtype)
+static t_stat sim_vhd_disk_set_dtype (FILE *f, const char *dtype, uint32 SectorSize, uint32 xfer_element_size)
{
VHDHANDLE hVHD = (VHDHANDLE)f;
int Status = 0;
memset (hVHD->Footer.DriveType, '\0', sizeof hVHD->Footer.DriveType);
memcpy (hVHD->Footer.DriveType, dtype, ((1+strlen (dtype)) < sizeof (hVHD->Footer.DriveType)) ? (1+strlen (dtype)) : sizeof (hVHD->Footer.DriveType));
+hVHD->Footer.DriveSectorSize = NtoHl (SectorSize);
+hVHD->Footer.DriveTransferElementSize = NtoHl (xfer_element_size);
+strncpy ((char *)hVHD->Footer.CreatingSimulator, sim_name, sizeof (hVHD->Footer.CreatingSimulator) - 1);
hVHD->Footer.Checksum = 0;
hVHD->Footer.Checksum = NtoHl (CalculateVhdFooterChecksum (&hVHD->Footer, sizeof(hVHD->Footer)));
if (NtoHl (hVHD->Footer.DiskType) == VHD_DT_Fixed) {
if (WriteFilePosition(hVHD->File,
@@ -3593,14 +4767,20 @@
if (Status)
return SCPE_IOERR;
return SCPE_OK;
}
-static const char *sim_vhd_disk_get_dtype (FILE *f)
+static const char *sim_vhd_disk_get_dtype (FILE *f, uint32 *SectorSize, uint32 *xfer_element_size, char sim_name[64])
{
VHDHANDLE hVHD = (VHDHANDLE)f;
+if (SectorSize)
+ *SectorSize = NtoHl (hVHD->Footer.DriveSectorSize);
+if (xfer_element_size)
+ *xfer_element_size = NtoHl (hVHD->Footer.DriveTransferElementSize);
+if (sim_name)
+ memcpy (sim_name, hVHD->Footer.CreatingSimulator, 64);
return (char *)(&hVHD->Footer.DriveType[0]);
}
static FILE *sim_vhd_disk_open (const char *szVHDPath, const char *DesiredAccess)
{
@@ -3835,10 +5015,22 @@
{
VHDHANDLE hVHD = (VHDHANDLE)f;
return (t_offset)(NtoHll (hVHD->Footer.CurrentSize));
}
+
+static t_stat sim_vhd_disk_info (FILE *f, uint32 *sector_size, uint32 *removable, uint32 *is_cdrom)
+{
+if (sector_size)
+ *sector_size = 512;
+if (removable)
+ *removable = FALSE;
+if (is_cdrom)
+ *is_cdrom = FALSE;
+return SCPE_OK;
+}
+
#include
#include
static void
_rand_uuid_gen (void *uuidaddr)
@@ -3913,16 +5105,16 @@
time_t now;
uint32 i;
FILE *File = NULL;
uint32 Status = 0;
uint32 BytesPerSector = 512;
-uint64 SizeInBytes = ((uint64)SizeInSectors)*BytesPerSector;
+uint64 SizeInBytes = ((uint64)SizeInSectors) * BytesPerSector;
uint64 TableOffset;
uint32 MaxTableEntries;
VHDHANDLE hVHD = NULL;
-if (SizeInBytes > ((uint64)(1024*1024*1024))*2040) {
+if (SizeInBytes > ((uint64)(1024 * 1024 * 1024)) * 2040) {
Status = EFBIG;
goto Cleanup_Return;
}
File = sim_fopen (szVHDPath, "rb");
if (File) {
@@ -3941,11 +5133,11 @@
memcpy (Footer.Cookie, "conectix", 8);
Footer.Features = NtoHl (0x00000002);;
Footer.FileFormatVersion = NtoHl (0x00010000);;
Footer.DataOffset = NtoHll (bFixedVHD ? ((long long)-1) : (long long)(sizeof(Footer)));
time (&now);
-Footer.TimeStamp = NtoHl ((uint32)(now-946684800));
+Footer.TimeStamp = NtoHl ((uint32)(now - 946684800));
memcpy (Footer.CreatorApplication, "simh", 4);
Footer.CreatorVersion = NtoHl (0x00040000);
memcpy (Footer.CreatorHostOS, "Wi2k", 4);
Footer.OriginalSize = NtoHll (SizeInBytes);
Footer.CurrentSize = NtoHll (SizeInBytes);
@@ -4262,10 +5454,12 @@
hVHD->Dynamic.Checksum = 0;
hVHD->Dynamic.Checksum = NtoHl (CalculateVhdFooterChecksum (&hVHD->Dynamic, sizeof(hVHD->Dynamic)));
hVHD->Footer.Checksum = 0;
hVHD->Footer.DiskType = NtoHl (VHD_DT_Differencing);
memcpy (hVHD->Footer.DriveType, ParentFooter.DriveType, sizeof (hVHD->Footer.DriveType));
+hVHD->Footer.DriveSectorSize = ParentFooter.DriveSectorSize;
+hVHD->Footer.DriveTransferElementSize = ParentFooter.DriveTransferElementSize;
hVHD->Footer.Checksum = NtoHl (CalculateVhdFooterChecksum (&hVHD->Footer, sizeof(hVHD->Footer)));
if (WriteFilePosition (hVHD->File,
&hVHD->Footer,
sizeof (hVHD->Footer),
@@ -4356,11 +5550,11 @@
t_seccnt *sectsread,
uint32 SectorSize,
t_lba lba)
{
uint64 BlockOffset = ((uint64)lba)*SectorSize;
-uint32 BlocksRead = 0;
+uint32 SectorsRead = 0;
uint32 SectorsInRead;
size_t BytesRead = 0;
if (!hVHD || (hVHD->File == NULL)) {
errno = EBADF;
@@ -4368,10 +5562,12 @@
}
if ((BlockOffset + sects*SectorSize) > (uint64)NtoHll (hVHD->Footer.CurrentSize)) {
errno = ERANGE;
return SCPE_IOERR;
}
+if (sectsread)
+ *sectsread = 0;
if (NtoHl (hVHD->Footer.DiskType) == VHD_DT_Fixed) {
if (ReadFilePosition(hVHD->File,
buf,
sects*SectorSize,
&BytesRead,
@@ -4379,19 +5575,19 @@
if (sectsread)
*sectsread = (t_seccnt)(BytesRead/SectorSize);
return SCPE_IOERR;
}
if (sectsread)
- *sectsread /= SectorSize;
+ *sectsread = (t_seccnt)(BytesRead/SectorSize);
return SCPE_OK;
}
/* We are now dealing with a Dynamically expanding or differencing disk */
while (sects) {
uint32 SectorsPerBlock = NtoHl (hVHD->Dynamic.BlockSize)/SectorSize;
uint64 BlockNumber = lba/SectorsPerBlock;
- uint32 BitMapBytes = (7+(NtoHl (hVHD->Dynamic.BlockSize)/SectorSize))/8;
- uint32 BitMapSectors = (BitMapBytes+SectorSize-1)/SectorSize;
+ uint32 BitMapBytes = (7+(NtoHl (hVHD->Dynamic.BlockSize)/VHD_Internal_SectorSize))/8;
+ uint32 BitMapSectors = (BitMapBytes+VHD_Internal_SectorSize-1)/VHD_Internal_SectorSize;
SectorsInRead = SectorsPerBlock - lba%SectorsPerBlock;
if (SectorsInRead > sects)
SectorsInRead = sects;
if (hVHD->BAT[BlockNumber] == VHD_BAT_FREE_ENTRY) {
@@ -4403,34 +5599,34 @@
SectorsInRead,
NULL,
SectorSize,
lba)) {
if (sectsread)
- *sectsread = BlocksRead;
+ *sectsread = SectorsRead;
return FALSE;
}
}
}
- else {
- BlockOffset = SectorSize*((uint64)(NtoHl (hVHD->BAT[BlockNumber]) + lba%SectorsPerBlock + BitMapSectors));
+ else {
+ BlockOffset = VHD_Internal_SectorSize * ((uint64)(NtoHl (hVHD->BAT[BlockNumber]) + BitMapSectors))+ (SectorSize * (lba % SectorsPerBlock));
if (ReadFilePosition(hVHD->File,
buf,
- SectorsInRead*SectorSize,
+ SectorsInRead * SectorSize,
NULL,
BlockOffset)) {
if (sectsread)
- *sectsread = BlocksRead;
+ *sectsread = SectorsRead;
return SCPE_IOERR;
}
}
sects -= SectorsInRead;
- buf = (uint8 *)(((char *)buf) + SectorSize*SectorsInRead);
+ buf = (uint8 *)(((char *)buf) + SectorSize * SectorsInRead);
lba += SectorsInRead;
- BlocksRead += SectorsInRead;
+ SectorsRead += SectorsInRead;
}
if (sectsread)
- *sectsread = BlocksRead;
+ *sectsread = SectorsRead;
return SCPE_OK;
}
static t_stat sim_vhd_disk_rdsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectsread, t_seccnt sects)
{
@@ -4467,11 +5663,11 @@
t_seccnt *sectswritten,
uint32 SectorSize,
t_lba lba)
{
uint64 BlockOffset = ((uint64)lba)*SectorSize;
-uint32 BlocksWritten = 0;
+uint32 SectorsWritten = 0;
uint32 SectorsInWrite;
size_t BytesWritten = 0;
if (!hVHD || !hVHD->File) {
errno = EBADF;
@@ -4479,34 +5675,36 @@
}
if ((BlockOffset + sects*SectorSize) > (uint64)NtoHll(hVHD->Footer.CurrentSize)) {
errno = ERANGE;
return SCPE_IOERR;
}
+if (sectswritten)
+ *sectswritten = 0;
if (NtoHl(hVHD->Footer.DiskType) == VHD_DT_Fixed) {
if (WriteFilePosition(hVHD->File,
buf,
- sects*SectorSize,
+ sects * SectorSize,
&BytesWritten,
BlockOffset)) {
if (sectswritten)
- *sectswritten = (t_seccnt)(BytesWritten/SectorSize);
+ *sectswritten = (t_seccnt)(BytesWritten / SectorSize);
return SCPE_IOERR;
}
if (sectswritten)
- *sectswritten /= SectorSize;
+ *sectswritten = (t_seccnt)(BytesWritten/SectorSize);
return SCPE_OK;
}
/* We are now dealing with a Dynamically expanding or differencing disk */
while (sects) {
- uint32 SectorsPerBlock = NtoHl(hVHD->Dynamic.BlockSize)/SectorSize;
- uint64 BlockNumber = lba/SectorsPerBlock;
- uint32 BitMapBytes = (7+(NtoHl(hVHD->Dynamic.BlockSize)/SectorSize))/8;
- uint32 BitMapSectors = (BitMapBytes+SectorSize-1)/SectorSize;
+ uint32 SectorsPerBlock = NtoHl(hVHD->Dynamic.BlockSize) / SectorSize;
+ uint64 BlockNumber = lba / SectorsPerBlock;
+ uint32 BitMapBytes = (7 + (NtoHl(hVHD->Dynamic.BlockSize) / VHD_Internal_SectorSize)) / 8;
+ uint32 BitMapSectors = (BitMapBytes + VHD_Internal_SectorSize - 1) / VHD_Internal_SectorSize;
if (BlockNumber >= NtoHl(hVHD->Dynamic.MaxTableEntries)) {
if (sectswritten)
- *sectswritten = BlocksWritten;
+ *sectswritten = SectorsWritten;
return SCPE_EOF;
}
SectorsInWrite = 1;
if (hVHD->BAT[BlockNumber] == VHD_BAT_FREE_ENTRY) {
uint8 *BitMap = NULL;
@@ -4515,30 +5713,30 @@
void *BlockData = NULL;
uint8 *BATUpdateBufferAddress;
uint32 BATUpdateBufferSize;
uint64 BATUpdateStorageAddress;
- if (!hVHD->Parent && BufferIsZeros(buf, SectorSize))
+ if (!hVHD->Parent && BufferIsZeros(buf, SectorsInWrite * SectorSize))
goto IO_Done;
/* Need to allocate a new Data Block. */
BlockOffset = sim_fsize_ex (hVHD->File);
if (((int64)BlockOffset) == -1)
return SCPE_IOERR;
- if (BitMapSectors*SectorSize > BitMapBufferSize)
- BitMapBufferSize = BitMapSectors*SectorSize;
- BitMapBuffer = (uint8 *)calloc(1, BitMapBufferSize + SectorSize*SectorsPerBlock);
- if (BitMapBufferSize > BitMapSectors*SectorSize)
- BitMap = BitMapBuffer + BitMapBufferSize-BitMapBytes;
+ if (BitMapSectors*VHD_Internal_SectorSize > BitMapBufferSize)
+ BitMapBufferSize = BitMapSectors * VHD_Internal_SectorSize;
+ BitMapBuffer = (uint8 *)calloc(1, BitMapBufferSize + SectorSize * SectorsPerBlock);
+ if (BitMapBufferSize > BitMapSectors * VHD_Internal_SectorSize)
+ BitMap = BitMapBuffer + BitMapBufferSize - BitMapBytes;
else
BitMap = BitMapBuffer;
memset(BitMap, 0xFF, BitMapBytes);
BlockOffset -= sizeof(hVHD->Footer);
- if (0 == (BlockOffset & ~(VHD_DATA_BLOCK_ALIGNMENT-1)))
+ if (0 == (BlockOffset & (VHD_DATA_BLOCK_ALIGNMENT-1)))
{ // Already aligned, so use padded BitMapBuffer
if (WriteFilePosition(hVHD->File,
BitMapBuffer,
- BitMapBufferSize + SectorSize*SectorsPerBlock,
+ BitMapBufferSize + SectorSize * SectorsPerBlock,
NULL,
BlockOffset)) {
free (BitMapBuffer);
return SCPE_IOERR;
}
@@ -4546,31 +5744,31 @@
}
else
{
// align the data portion of the block to the desired alignment
// compute the address of the data portion of the block
- BlockOffset += BitMapSectors*SectorSize;
+ BlockOffset += BitMapSectors * VHD_Internal_SectorSize;
// round up this address to the desired alignment
BlockOffset += VHD_DATA_BLOCK_ALIGNMENT-1;
- BlockOffset &= ~(VHD_DATA_BLOCK_ALIGNMENT-1);
- BlockOffset -= BitMapSectors*SectorSize;
+ BlockOffset &= ~(VHD_DATA_BLOCK_ALIGNMENT - 1);
+ BlockOffset -= BitMapSectors * VHD_Internal_SectorSize;
if (WriteFilePosition(hVHD->File,
BitMap,
- SectorSize * (BitMapSectors + SectorsPerBlock),
+ (BitMapSectors * VHD_Internal_SectorSize) + (SectorSize * SectorsPerBlock),
NULL,
BlockOffset)) {
free (BitMapBuffer);
return SCPE_IOERR;
}
- BlockOffset += BitMapSectors*SectorSize;
+ BlockOffset += BitMapSectors * VHD_Internal_SectorSize;
}
free(BitMapBuffer);
BitMapBuffer = BitMap = NULL;
/* the BAT block address is the beginning of the block bitmap */
- BlockOffset -= BitMapSectors*SectorSize;
- hVHD->BAT[BlockNumber] = NtoHl((uint32)(BlockOffset/SectorSize));
- BlockOffset += SectorSize * (SectorsPerBlock + BitMapSectors);
+ BlockOffset -= BitMapSectors * VHD_Internal_SectorSize;
+ hVHD->BAT[BlockNumber] = NtoHl((uint32)(BlockOffset / VHD_Internal_SectorSize));
+ BlockOffset += (BitMapSectors * VHD_Internal_SectorSize) + (SectorSize * SectorsPerBlock);
if (WriteFilePosition(hVHD->File,
&hVHD->Footer,
sizeof(hVHD->Footer),
NULL,
BlockOffset))
@@ -4590,12 +5788,12 @@
else {
BATUpdateBufferSize = VHD_DATA_BLOCK_ALIGNMENT;
BATUpdateStorageAddress = NtoHll(hVHD->Dynamic.TableOffset) + BATUpdateBufferAddress - ((uint8 *)hVHD->BAT);
}
/* If the total BAT is smaller than one VHD_DATA_BLOCK_ALIGNMENT, then be sure to only write out the BAT data */
- if ((size_t)(BATUpdateBufferAddress - (uint8 *)hVHD->BAT + BATUpdateBufferSize) > 512*((sizeof(*hVHD->BAT)*NtoHl(hVHD->Dynamic.MaxTableEntries) + 511)/512))
- BATUpdateBufferSize = (uint32)(512*((sizeof(*hVHD->BAT)*NtoHl(hVHD->Dynamic.MaxTableEntries) + 511)/512) - (BATUpdateBufferAddress - ((uint8 *)hVHD->BAT)));
+ if ((size_t)(BATUpdateBufferAddress - (uint8 *)hVHD->BAT + BATUpdateBufferSize) > VHD_Internal_SectorSize * ((sizeof(*hVHD->BAT)*NtoHl(hVHD->Dynamic.MaxTableEntries) + VHD_Internal_SectorSize - 1)/VHD_Internal_SectorSize))
+ BATUpdateBufferSize = (uint32)(VHD_Internal_SectorSize * ((sizeof(*hVHD->BAT) * NtoHl(hVHD->Dynamic.MaxTableEntries) + VHD_Internal_SectorSize - 1)/VHD_Internal_SectorSize) - (BATUpdateBufferAddress - ((uint8 *)hVHD->BAT)));
if (WriteFilePosition(hVHD->File,
BATUpdateBufferAddress,
BATUpdateBufferSize,
NULL,
BATUpdateStorageAddress))
@@ -4602,27 +5800,27 @@
goto Fatal_IO_Error;
if (hVHD->Parent)
{ /* Need to populate data block contents from parent VHD */
uint32 BlockSectors = SectorsPerBlock;
- BlockData = malloc(SectorsPerBlock*SectorSize);
+ BlockData = malloc(SectorsPerBlock * SectorSize);
- if (((lba/SectorsPerBlock)*SectorsPerBlock + BlockSectors) > ((uint64)NtoHll (hVHD->Footer.CurrentSize))/SectorSize)
- BlockSectors = (uint32)(((uint64)NtoHll (hVHD->Footer.CurrentSize))/SectorSize - (lba/SectorsPerBlock)*SectorsPerBlock);
+ if (((lba / SectorsPerBlock) * SectorsPerBlock + BlockSectors) > ((uint64)NtoHll (hVHD->Footer.CurrentSize)) / SectorSize)
+ BlockSectors = (uint32)(((uint64)NtoHll (hVHD->Footer.CurrentSize)) / SectorSize - (lba / SectorsPerBlock) * SectorsPerBlock);
if (ReadVirtualDiskSectors(hVHD->Parent,
(uint8*) BlockData,
BlockSectors,
NULL,
SectorSize,
- (lba/SectorsPerBlock)*SectorsPerBlock))
+ (lba / SectorsPerBlock) * SectorsPerBlock))
goto Fatal_IO_Error;
if (WriteVirtualDiskSectors(hVHD,
(uint8*) BlockData,
BlockSectors,
NULL,
SectorSize,
- (lba/SectorsPerBlock)*SectorsPerBlock))
+ (lba / SectorsPerBlock) * SectorsPerBlock))
goto Fatal_IO_Error;
free(BlockData);
}
continue;
Fatal_IO_Error:
@@ -4631,32 +5829,32 @@
fclose (hVHD->File);
hVHD->File = NULL;
return SCPE_IOERR;
}
else {
- BlockOffset = 512*((uint64)(NtoHl(hVHD->BAT[BlockNumber]) + lba%SectorsPerBlock + BitMapSectors));
- SectorsInWrite = SectorsPerBlock - lba%SectorsPerBlock;
+ BlockOffset = VHD_Internal_SectorSize * ((uint64)(NtoHl(hVHD->BAT[BlockNumber]) + BitMapSectors)) + (SectorSize * (lba % SectorsPerBlock));
+ SectorsInWrite = SectorsPerBlock - lba % SectorsPerBlock;
if (SectorsInWrite > sects)
SectorsInWrite = sects;
if (WriteFilePosition(hVHD->File,
buf,
- SectorsInWrite*SectorSize,
- NULL,
+ SectorsInWrite * SectorSize,
+ &BytesWritten,
BlockOffset)) {
if (sectswritten)
- *sectswritten = BlocksWritten;
+ *sectswritten = SectorsWritten + BytesWritten / SectorSize;
return SCPE_IOERR;
}
}
IO_Done:
sects -= SectorsInWrite;
- buf = (uint8 *)(((char *)buf) + SectorsInWrite*SectorSize);
+ buf = (uint8 *)(((char *)buf) + SectorsInWrite * SectorSize);
lba += SectorsInWrite;
- BlocksWritten += SectorsInWrite;
+ SectorsWritten += SectorsInWrite;
}
if (sectswritten)
- *sectswritten = BlocksWritten;
+ *sectswritten = SectorsWritten;
return SCPE_OK;
}
static t_stat sim_vhd_disk_wrsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectswritten, t_seccnt sects)
{
@@ -4664,10 +5862,172 @@
struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
return WriteVirtualDiskSectors(hVHD, buf, sects, sectswritten, ctx->sector_size, lba);
}
#endif
+
+/* disk testing */
+
+#include
+
+struct disk_test_coverage {
+ t_lba total_sectors;
+ uint32 max_xfer_size;
+ t_seccnt max_xfer_sectors;
+ uint32 wsetbits;
+ uint32 *wbitmap;
+ uint32 *data;
+ };
+
+static t_stat sim_disk_test_exercise (UNIT *uptr)
+{
+struct disk_context *ctx = (struct disk_context *)uptr->disk_ctx;
+struct disk_test_coverage *c = (struct disk_test_coverage *)calloc (1, sizeof (*c));
+t_stat r = SCPE_OK;
+uint32 uint32s_per_sector = (ctx->sector_size / sizeof (*c->data));
+DEVICE *dptr = find_dev_from_unit (uptr);
+uint32 capac_factor = ((dptr->dwidth / dptr->aincr) >= 32) ? 8 : ((dptr->dwidth / dptr->aincr) == 16) ? 2 : 1; /* capacity units (quadword: 8, word: 2, byte: 1) */
+uint32 tries = 0;
+t_bool unexpected_data = FALSE;
+
+c->max_xfer_size = 1024*1024;
+c->max_xfer_sectors = c->max_xfer_size / ctx->sector_size;
+c->total_sectors = (t_lba)((uptr->capac*capac_factor)/(ctx->sector_size/((dptr->flags & DEV_SECTORS) ? ((ctx->sector_size >= 512) ? 512 : ctx->sector_size): 1)));
+c->data = (uint32 *)malloc (c->max_xfer_size);
+c->wbitmap = (uint32 *)calloc ((c->total_sectors + 32)/32, sizeof (*c->wbitmap));
+#define BITMAP_IS_SET(n) (c->wbitmap[(n) >> 5] & (1 << ((n) & 0x1f)))
+#define SET_BITMAP(n) c->wbitmap[(n) >> 5] |= (1 << ((n) & 0x1f))
+/* Randomly populate the whole drive container with known data (sector # in each sector) */
+srand (0);
+while (c->wsetbits < c->total_sectors) {
+ t_lba start_lba = (rand () % c->total_sectors);
+ t_lba end_lba = start_lba + 1 + (rand () % (c->max_xfer_sectors - 1));
+ t_lba lba;
+ t_seccnt i, sectors_to_write, sectors_written;
+
+ if (end_lba > c->total_sectors)
+ end_lba = c->total_sectors;
+ if (BITMAP_IS_SET(start_lba)) {
+ ++tries;
+ if (tries < 30)
+ continue;
+ while (BITMAP_IS_SET(start_lba))
+ start_lba = (1 + start_lba) % c->total_sectors;
+ end_lba = start_lba + 1;
+ }
+ tries = 0;
+ for (lba = start_lba; lba < end_lba; lba++) {
+ if (BITMAP_IS_SET(lba)) {
+ end_lba = lba;
+ break;
+ }
+ SET_BITMAP(lba);
+ ++c->wsetbits;
+ }
+ sectors_to_write = end_lba - start_lba;
+ for (i=0; i < sectors_to_write * uint32s_per_sector; i++)
+ c->data[i] = start_lba + i / uint32s_per_sector;
+ r = sim_disk_wrsect (uptr, start_lba, (uint8 *)c->data, §ors_written, sectors_to_write);
+ if (r != SCPE_OK) {
+ sim_printf ("Error writing sectors %u thru %u: %s\n", start_lba, end_lba - 1, sim_error_text (r));
+ break;
+ }
+ else {
+ if (sectors_to_write != sectors_written) {
+ sim_printf ("Unexpectedly wrote %u sectors instead of %u sectors starting at lba %u\n", sectors_written, sectors_to_write, start_lba);
+ break;
+ }
+ }
+ }
+if (r == SCPE_OK) {
+ t_seccnt sectors_read, sectors_to_read, sector_to_check;
+ t_lba lba;
+
+ sim_printf("Writing OK\n");
+ for (lba = 0; (lba < c->total_sectors) && (r == SCPE_OK); lba += sectors_read) {
+ sectors_to_read = 1 + (rand () % (c->max_xfer_sectors - 1));
+ if (lba + sectors_to_read > c->total_sectors)
+ sectors_to_read = c->total_sectors - lba;
+ r = sim_disk_rdsect (uptr, lba, (uint8 *)c->data, §ors_read, sectors_to_read);
+ if (r == SCPE_OK) {
+ if (sectors_read != sectors_to_read) {
+ sim_printf ("Only returned %u sectors when reading %u sectors from lba %u\n", sectors_read, sectors_to_read, lba);
+ r = SCPE_INCOMP;
+ }
+ }
+ else
+ sim_printf ("Error reading %u sectors at lba %u, %u read - %s\n", sectors_to_read, lba, sectors_read, sim_error_text (r));
+ for (sector_to_check = 0; sector_to_check < sectors_read; ++sector_to_check) {
+ uint32 i;
+
+ for (i = 0; i < uint32s_per_sector; i++)
+ if (c->data[i + sector_to_check * uint32s_per_sector] != (lba + sector_to_check)) {
+ sim_printf ("Sector %u(0x%X) has unexpected data at offset 0x%X: 0x%08X\n",
+ lba + sector_to_check, lba + sector_to_check, i, c->data[i + sector_to_check * uint32s_per_sector]);
+ unexpected_data = TRUE;
+ break;
+ }
+ }
+ }
+ if ((r == SCPE_OK) && !unexpected_data)
+ sim_printf("Reading OK\n");
+ else {
+ sim_printf("Reading BAD\n");
+ r = SCPE_IERR;
+ }
+ }
+free (c->data);
+free (c->wbitmap);
+free (c);
+if (r == SCPE_OK) {
+ char *filename = strdup (uptr->filename);
+
+ sim_disk_detach (uptr);
+ (void)remove (filename);
+ free (filename);
+ }
+return r;
+}
t_stat sim_disk_test (DEVICE *dptr)
{
+const char *fmt[] = {"VHD", "VHD", "SIMH", "RAW", NULL};
+uint32 sect_size[] = {4096, 1024, 512, 256, 128, 64, 0};
+uint32 xfr_size[] = {1, 2, 4, 8, 0};
+int x, s, f;
+UNIT *uptr = &dptr->units[0];
+char filename[256];
+t_stat r;
+int32 saved_switches = sim_switches & ~SWMASK('T');
+SIM_TEST_INIT;
+
+for (x = 0; xfr_size[x] != 0; x++) {
+ for (f = 0; fmt[f] != 0; f++) {
+ for (s = 0; sect_size[s] != 0; s++) {
+ snprintf (filename, sizeof (filename) - 1, "Test-%u-%u.%s", sect_size[s], xfr_size[x], fmt[f]);
+ if ((f > 0) && (strcmp (fmt[f], "VHD") == 0) && (strcmp (fmt[f - 1], "VHD") == 0)) { /* Second VHD is Fixed */
+ sim_switches |= SWMASK('X');
+ snprintf (filename, sizeof (filename) - 1, "Test-%u-%u-Fixed.%s", sect_size[s], xfr_size[x], fmt[f]);
+ }
+ else
+ sim_switches = saved_switches;
+ (void)remove (filename); /* Remove any prior remnants */
+ r = sim_disk_set_fmt (uptr, 0, fmt[f], NULL);
+ if (r != SCPE_OK)
+ break;
+ sim_printf ("Testing %s (%s) using %s\n", sim_uname (uptr), sprint_capac (dptr, uptr), filename);
+ if (strcmp (fmt[f], "RAW") == 0) {
+ /* There is no innate creation of RAW containers, so create the empty container using SIMH format */
+ sim_disk_set_fmt (uptr, 0, "SIMH", NULL);
+ sim_disk_attach_ex (uptr, filename, sect_size[s], xfr_size[x], TRUE, 0, NULL, 0, 0, NULL);
+ sim_disk_detach (uptr);
+ sim_disk_set_fmt (uptr, 0, fmt[f], NULL);
+ }
+ r = sim_disk_attach_ex (uptr, filename, sect_size[s], xfr_size[x], TRUE, 0, NULL, 0, 0, NULL);
+ if (r != SCPE_OK)
+ break;
+ SIM_TEST(sim_disk_test_exercise (uptr));
+ }
+ }
+ }
return SCPE_OK;
}
Index: src/SIMH/sim_disk.h
==================================================================
--- src/SIMH/sim_disk.h
+++ src/SIMH/sim_disk.h
@@ -66,12 +66,29 @@
typedef void (*DISK_PCALLBACK)(UNIT *unit, t_stat status);
/* Prototypes */
-t_stat sim_disk_attach (UNIT *uptr, const char *cptr, size_t sector_size, size_t xfer_element_size, t_bool dontautosize,
- uint32 debugbit, const char *drivetype, uint32 pdp11_tracksize, int completion_delay);
+t_stat sim_disk_attach (UNIT *uptr,
+ const char *cptr,
+ size_t sector_size, size_t xfer_element_size,
+ t_bool dontchangecapac,
+ uint32 debugbit,
+ const char *drivetype,
+ uint32 pdp11_tracksize,
+ int completion_delay);
+t_stat sim_disk_attach_ex (UNIT *uptr,
+ const char *cptr,
+ size_t sector_size,
+ size_t xfer_element_size,
+ t_bool dontchangecapac, /* if false just change uptr->capac as needed */
+ uint32 dbit, /* debug bit */
+ const char *dtype, /* drive type */
+ uint32 pdp11tracksize, /* BAD144 track */
+ int completion_delay, /* Minimum Delay for asynch I/O completion */
+ const char **drivetypes); /* list of drive types (from smallest to largest) */
+ /* to try and fit the container/file system into */
t_stat sim_disk_detach (UNIT *uptr);
t_stat sim_disk_attach_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr);
t_stat sim_disk_rdsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectsread, t_seccnt sects);
t_stat sim_disk_rdsect_a (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectsread, t_seccnt sects, DISK_PCALLBACK callback);
t_stat sim_disk_wrsect (UNIT *uptr, t_lba lba, uint8 *buf, t_seccnt *sectswritten, t_seccnt sects);
Index: src/SIMH/sim_ether.c
==================================================================
--- src/SIMH/sim_ether.c
+++ src/SIMH/sim_ether.c
@@ -377,10 +377,11 @@
#define MAX(a,b) (((a) > (b)) ? (a) : (b))
/* Internal routines - forward declarations */
static int _eth_get_system_id (char *buf, size_t buf_size);
+static int _eth_devices (int max, ETH_LIST* dev); /* get ethernet devices on host */
/*============================================================================*/
/* OS-independant ethernet routines */
/*============================================================================*/
@@ -415,20 +416,20 @@
/* bits (if specified) must be from 16 thru 48 */
memset (&state, 0, sizeof(state));
_eth_get_system_id (state.system_id, sizeof(state.system_id));
strlcpy (state.sim, sim_name, sizeof(state.sim));
- getcwd (state.cwd, sizeof(state.cwd));
+ if (getcwd (state.cwd, sizeof(state.cwd))) {};
if (uptr)
strlcpy (state.uname, sim_uname (uptr), sizeof(state.uname));
cptr = strchr (strmac, '>');
if (cptr) {
state.file[sizeof(state.file)-1] = '\0';
strlcpy (state.file, cptr + 1, sizeof(state.file));
if ((f = fopen (state.file, "r"))) {
filebuf[sizeof(filebuf)-1] = '\0';
- fgets (filebuf, sizeof(filebuf)-1, f);
+ if (fgets (filebuf, sizeof(filebuf)-1, f)) {};
strmac = filebuf;
fclose (f);
strcpy (state.file, ""); /* avoid saving */
}
}
@@ -655,14 +656,14 @@
void eth_packet_trace_detail(ETH_DEV* dev, const uint8 *msg, int len, const char* txt)
{
eth_packet_trace_ex(dev, msg, len, txt, 1 , dev->dbit);
}
-const char* eth_getname(int number, char* name, char *desc)
+static const char* _eth_getname(int number, char* name, char *desc)
{
ETH_LIST list[ETH_MAX_DEVICE];
- int count = eth_devices(ETH_MAX_DEVICE, list);
+ int count = _eth_devices(ETH_MAX_DEVICE, list);
if ((number < 0) || (count <= number))
return NULL;
if (list[number].eth_api != ETH_API_PCAP) {
sim_printf ("Eth: Pcap capable device not found. You may need to run as root\n");
@@ -675,11 +676,11 @@
}
const char* eth_getname_bydesc(const char* desc, char* name, char *ndesc)
{
ETH_LIST list[ETH_MAX_DEVICE];
- int count = eth_devices(ETH_MAX_DEVICE, list);
+ int count = _eth_devices(ETH_MAX_DEVICE, list);
int i;
size_t j=strlen(desc);
for (i=0; i min) min = len;
for (i=0; idptr->name, eth_open_devices[i]->dptr->units[0].filename);
eth_show_dev (st, eth_open_devices[i]);
}
}
- eth_show_active = FALSE;
return SCPE_OK;
}
t_stat eth_show_devices (FILE* st, DEVICE *dptr, UNIT* uptr, int32 val, CONST char *desc)
{
@@ -960,12 +955,14 @@
ETH_BOOL all_multicast, ETH_BOOL promiscuous)
{return SCPE_NOFNC;}
t_stat eth_filter_hash (ETH_DEV* dev, int addr_count, ETH_MAC* const addresses,
ETH_BOOL all_multicast, ETH_BOOL promiscuous, ETH_MULTIHASH* const hash)
{return SCPE_NOFNC;}
-int eth_devices (int max, ETH_LIST* dev)
+int _eth_devices (int max, ETH_LIST* dev)
{return -1;}
+const char *eth_version (void)
+ {return NULL;}
void eth_show_dev (FILE* st, ETH_DEV* dev)
{}
static int _eth_get_system_id (char *buf, size_t buf_size)
{memset (buf, 0, buf_size); return 0;}
t_stat sim_ether_test (DEVICE *dptr)
@@ -1064,26 +1061,32 @@
static HINSTANCE hLib = NULL; /* handle to DLL */
#else
static void *hLib = 0; /* handle to Library */
#endif
static int lib_loaded = 0; /* 0=not loaded, 1=loaded, 2=library load failed, 3=Func load failed */
+
+#define __STR_QUOTE(tok) #tok
+#define __STR(tok) __STR_QUOTE(tok)
static const char* lib_name =
#if defined(_WIN32) || defined(__CYGWIN__)
"wpcap.dll";
#elif defined(__APPLE__)
"/usr/lib/libpcap.A.dylib";
#else
-#define __STR_QUOTE(tok) #tok
-#define __STR(tok) __STR_QUOTE(tok)
"libpcap." __STR(HAVE_DLOPEN);
#endif
-static const char* no_pcap =
+
+static char no_pcap[PCAP_ERRBUF_SIZE] =
#if defined(_WIN32) || defined(__CYGWIN__)
- "wpcap load failure";
+ "wpcap.dll failed to load, install Npcap or WinPcap 4.1.3 to use pcap networking";
+#elif defined(__APPLE__)
+ "/usr/lib/libpcap.A.dylib failed to load, install libpcap to use pcap networking";
#else
- "libpcap load failure";
+ "libpcap." __STR(HAVE_DLOPEN) " failed to load, install libpcap to use pcap networking";
#endif
+#undef __STR
+#undef __STR_QUOTE
/* define pointers to pcap functions needed */
static void (*p_pcap_close) (pcap_t *);
static int (*p_pcap_compile) (pcap_t *, struct bpf_program *, const char *, int, bpf_u_int32);
static int (*p_pcap_datalink) (pcap_t *);
@@ -1149,16 +1152,10 @@
#else
hLib = dlopen(lib_name, RTLD_NOW);
#endif
if (hLib == 0) {
/* failed to load DLL */
- sim_printf ("Eth: Failed to load %s\n", lib_name);
-#ifdef _WIN32
- sim_printf ("Eth: You must install Npcap or WinPcap 4.x to use networking\n");
-#else
- sim_printf ("Eth: You must install libpcap to use networking\n");
-#endif
lib_loaded = 2;
break;
} else {
/* library loaded OK */
lib_loaded = 1;
@@ -1186,25 +1183,20 @@
#endif
load_function("pcap_sendpacket", (_func *) &p_pcap_sendpacket);
load_function("pcap_setfilter", (_func *) &p_pcap_setfilter);
load_function("pcap_setnonblock", (_func *) &p_pcap_setnonblock);
load_function("pcap_lib_version", (_func *) &p_pcap_lib_version);
-
- if ((lib_loaded == 1) && (!eth_show_active)) {
- /* log successful load */
- sim_messagef (SCPE_OK, "%s\n", p_pcap_lib_version());
- }
break;
default: /* loaded or failed */
break;
}
return (lib_loaded == 1) ? 1 : 0;
}
/* define functions with dynamic revectoring */
void pcap_close(pcap_t* a) {
- if (a && (load_pcap() != 0)) {
+ if (load_pcap() != 0) {
p_pcap_close(a);
}
}
/* Some platforms's pcap.h have an ancient declaration of pcap_compile which doesn't have a const in the bpf string argument */
@@ -1211,57 +1203,75 @@
#if !defined (BPF_CONST_STRING)
int pcap_compile(pcap_t* a, struct bpf_program* b, char* c, int d, bpf_u_int32 e) {
#else
int pcap_compile(pcap_t* a, struct bpf_program* b, const char* c, int d, bpf_u_int32 e) {
#endif
- if (a && (load_pcap() != 0)) {
+ if (load_pcap() != 0) {
return p_pcap_compile(a, b, c, d, e);
} else {
return 0;
}
}
+
+const char *pcap_lib_version(void) {
+ static char buf[256];
+
+ if ((load_pcap() != 0) && (p_pcap_lib_version != NULL)) {
+ return p_pcap_lib_version();
+ } else {
+ sprintf (buf, "%s not installed",
+#if defined(_WIN32)
+ "npcap or winpcap"
+#else
+ "libpcap"
+#endif
+ );
+ return buf;
+ }
+}
int pcap_datalink(pcap_t* a) {
- if (a && (load_pcap() != 0)) {
+ if (load_pcap() != 0) {
return p_pcap_datalink(a);
} else {
return 0;
}
}
int pcap_dispatch(pcap_t* a, int b, pcap_handler c, u_char* d) {
- if (a && (load_pcap() != 0)) {
+ if (load_pcap() != 0) {
return p_pcap_dispatch(a, b, c, d);
} else {
return 0;
}
}
int pcap_findalldevs(pcap_if_t** a, char* b) {
- if (a && (load_pcap() != 0)) {
+ if (load_pcap() != 0) {
return p_pcap_findalldevs(a, b);
} else {
*a = 0;
strcpy(b, no_pcap);
+ no_pcap[0] = '\0';
return -1;
}
}
void pcap_freealldevs(pcap_if_t* a) {
- if (a && (load_pcap() != 0)) {
+ if (load_pcap() != 0) {
p_pcap_freealldevs(a);
}
}
void pcap_freecode(struct bpf_program* a) {
- if (a && (load_pcap() != 0)) {
+ if (load_pcap() != 0) {
p_pcap_freecode(a);
}
}
char* pcap_geterr(pcap_t* a) {
- if (a && (load_pcap() != 0)) {
+ if (load_pcap() != 0) {
return p_pcap_geterr(a);
} else {
return (char*) "";
}
}
@@ -1282,63 +1292,63 @@
}
}
#ifdef _WIN32
int pcap_setmintocopy(pcap_t* a, int b) {
- if (a && (load_pcap() != 0)) {
+ if (load_pcap() != 0) {
return p_pcap_setmintocopy(a, b);
} else {
return -1;
}
}
HANDLE pcap_getevent(pcap_t* a) {
- if (a && (load_pcap() != 0)) {
+ if (load_pcap() != 0) {
return p_pcap_getevent(a);
} else {
return (HANDLE) 0;
}
}
#else
#ifdef MUST_DO_SELECT
int pcap_get_selectable_fd(pcap_t* a) {
- if (a && (load_pcap() != 0)) {
+ if (load_pcap() != 0) {
return p_pcap_get_selectable_fd(a);
} else {
return 0;
}
}
#endif
int pcap_fileno(pcap_t * a) {
- if (a && (load_pcap() != 0)) {
+ if (load_pcap() != 0) {
return p_pcap_fileno(a);
} else {
return 0;
}
}
#endif
int pcap_sendpacket(pcap_t* a, const u_char* b, int c) {
- if (a && (load_pcap() != 0)) {
+ if (load_pcap() != 0) {
return p_pcap_sendpacket(a, b, c);
} else {
return 0;
}
}
int pcap_setfilter(pcap_t* a, struct bpf_program* b) {
- if (a && (load_pcap() != 0)) {
+ if (load_pcap() != 0) {
return p_pcap_setfilter(a, b);
} else {
return 0;
}
}
int pcap_setnonblock(pcap_t* a, int nonblock, char *errbuf) {
- if (a && (load_pcap() != 0)) {
+ if (load_pcap() != 0) {
return p_pcap_setnonblock(a, nonblock, errbuf);
} else {
return 0;
}
}
@@ -1377,11 +1387,11 @@
uint8 Data[1]; ///< variable-lenght field that contains the information passed to or received
///< from the adapter.
};
typedef struct _PACKET_OID_DATA PACKET_OID_DATA, *PPACKET_OID_DATA;
typedef void **LPADAPTER;
-#define OID_802_3_CURRENT_ADDRESS 0x01010102 /* Extracted from ntddmdis.h */
+#define OID_802_3_CURRENT_ADDRESS 0x01010102 /* Extracted from ntddndis.h */
static int pcap_mac_if_win32(const char *AdapterName, unsigned char MACAddress[6])
{
LPADAPTER lpAdapter;
PPACKET_OID_DATA OidData;
@@ -1571,15 +1581,15 @@
"egrep [0-9a-fA-F]?[0-9a-fA-F]:[0-9a-fA-F]?[0-9a-fA-F]:[0-9a-fA-F]?[0-9a-fA-F]:[0-9a-fA-F]?[0-9a-fA-F]:[0-9a-fA-F]?[0-9a-fA-F]:[0-9a-fA-F]?[0-9a-fA-F]",
NULL};
memset(command, 0, sizeof(command));
/* try to force an otherwise unused interface to be turned on */
- snprintf(command, sizeof(command)-1, "ifconfig %s up", devname);
- (void)system(command);
+ snprintf(command, sizeof(command)-1, "ifconfig %.*s up", (int)(sizeof(command) - 14), devname);
+ if (system(command)) {};
for (i=0; patterns[i] && (0 == dev->have_host_nic_phy_addr); ++i) {
- snprintf(command, sizeof(command)-1, "ifconfig %s | %s >NIC.hwaddr", devname, patterns[i]);
- (void)system(command);
+ snprintf(command, sizeof(command)-1, "ifconfig %.*s | %s >NIC.hwaddr", (int)(sizeof(command) - (26 + strlen(patterns[i]))), devname, patterns[i]);
+ if (system(command)) {};
if (NULL != (f = fopen("NIC.hwaddr", "r"))) {
while (0 == dev->have_host_nic_phy_addr) {
if (fgets(command, sizeof(command)-1, f)) {
char *p1, *p2;
@@ -1604,11 +1614,11 @@
}
else
break;
}
fclose(f);
- remove("NIC.hwaddr");
+ (void)remove("NIC.hwaddr");
}
}
}
#endif
}
@@ -1641,12 +1651,14 @@
#ifndef KEY_WOW64_64KEY
#define KEY_WOW64_64KEY (0x0100)
#endif
if ((status = RegOpenKeyExA (HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Cryptography", 0, KEY_QUERY_VALUE|KEY_WOW64_64KEY, ®hnd)) != ERROR_SUCCESS)
return -1;
- reglen = buf_size;
- if ((status = RegQueryValueExA (reghnd, "MachineGuid", NULL, ®type, buf, ®len)) != ERROR_SUCCESS) {
+ if (buf_size < 37)
+ return -1;
+ reglen = buf_size - 1;
+ if ((status = RegQueryValueExA (reghnd, "MachineGuid", NULL, ®type, (LPBYTE)buf, ®len)) != ERROR_SUCCESS) {
RegCloseKey (reghnd);
return -1;
}
RegCloseKey (reghnd );
/* make sure value is the right type, bail if not acceptable */
@@ -1660,20 +1672,21 @@
static int _eth_get_system_id (char *buf, size_t buf_size)
{
FILE *f;
memset (buf, 0, buf_size);
-if ((f = fopen ("/etc/machine-id", "r"))) {
- fread (buf, 1, buf_size, f);
+if (buf_size < 37)
+ return -1;
+if ((f = fopen ("/etc/machine-id", "r")) == NULL)
+ f = popen ("hostname", "r");
+if (f) {
+ size_t read_size;
+
+ read_size = fread (buf, 1, buf_size - 1, f);
+ buf[read_size] = '\0';
fclose (f);
}
-else {
- if ((f = popen ("hostname", "r"))) {
- fread (buf, 1, buf_size, f);
- pclose (f);
- }
- }
while ((strlen (buf) > 0) && sim_isspace(buf[strlen (buf) - 1]))
buf[strlen (buf) - 1] = '\0';
return 0;
}
#endif
@@ -1902,10 +1915,12 @@
pthread_mutex_lock (&dev->writer_lock);
while (dev->handle) {
pthread_cond_wait (&dev->writer_cond, &dev->writer_lock);
while (NULL != (request = dev->write_requests)) {
+ if (dev->handle == NULL) /* Shutting down? */
+ break;
/* Pull buffer off request list */
dev->write_requests = request->next;
pthread_mutex_unlock (&dev->writer_lock);
if (dev->throttle_delay != ETH_THROT_DISABLED_DELAY) {
@@ -1922,11 +1937,18 @@
pthread_mutex_lock (&dev->writer_lock);
/* Put buffer on free buffer list */
request->next = dev->write_buffers;
dev->write_buffers = request;
+ request = NULL;
}
+ }
+/* If we exited these loops with a request allocated, */
+/* avoid buffer leaking by putting it on free buffer list */
+if (request) {
+ request->next = dev->write_buffers;
+ dev->write_buffers = request;
}
pthread_mutex_unlock (&dev->writer_lock);
sim_debug(dev->dbit, dev->dptr, "Writer Thread Exiting\n");
return NULL;
@@ -1934,11 +1956,12 @@
#endif
t_stat eth_set_async (ETH_DEV *dev, int latency)
{
#if !defined(USE_READER_THREAD) || !defined(SIM_ASYNCH_IO)
-char *msg = "Eth: can't operate asynchronously, must poll\n";
+char *msg = "Eth: Can't operate asynchronously, must poll.\n"
+ " *** Build with USE_READER_THREAD defined and link with pthreads for asynchronous operation. ***\n";
return sim_messagef (SCPE_NOFNC, "%s", msg);
#else
int wakeup_needed;
dev->asynch_io = 1;
@@ -2006,29 +2029,34 @@
if ((tun = open("/dev/net/tun", O_RDWR)) >= 0) {
struct ifreq ifr; /* Interface Requests */
memset(&ifr, 0, sizeof(ifr));
/* Set up interface flags */
- strcpy(ifr.ifr_name, devname);
+ strlcpy(ifr.ifr_name, devname, sizeof(ifr.ifr_name));
ifr.ifr_flags = IFF_TAP|IFF_NO_PI;
/* Send interface requests to TUN/TAP driver. */
if (ioctl(tun, TUNSETIFF, &ifr) >= 0) {
if (ioctl(tun, FIONBIO, &on)) {
strlcpy(errbuf, strerror(errno), PCAP_ERRBUF_SIZE);
close(tun);
+ tun = -1;
}
else {
- *fd_handle = tun;
+ *fd_handle = (SOCKET)tun;
strcpy(savname, ifr.ifr_name);
}
}
else
strlcpy(errbuf, strerror(errno), PCAP_ERRBUF_SIZE);
}
else
strlcpy(errbuf, strerror(errno), PCAP_ERRBUF_SIZE);
+ if ((tun >= 0) && (errbuf[0] != 0)) {
+ close(tun);
+ tun = -1;
+ }
#elif defined(HAVE_BSDTUNTAP) && defined(HAVE_TAP_NETWORK)
if (1) {
char dev_name[64] = "";
snprintf(dev_name, sizeof(dev_name)-1, "/dev/%s", devname);
@@ -2036,39 +2064,46 @@
if ((tun = open(dev_name, O_RDWR)) >= 0) {
if (ioctl(tun, FIONBIO, &on)) {
strlcpy(errbuf, strerror(errno), PCAP_ERRBUF_SIZE);
close(tun);
+ tun = -1;
}
else {
- *fd_handle = tun;
- strcpy(savname, devname);
+ *fd_handle = (SOCKET)tun;
+ memmove(savname, devname, strlen(devname) + 1);
}
#if defined (__APPLE__)
- if (1) {
+ if (tun >= 0) { /* Good so far? */
struct ifreq ifr;
int s;
+ /* Now make sure the interface is up */
memset (&ifr, 0, sizeof(ifr));
ifr.ifr_addr.sa_family = AF_INET;
strlcpy(ifr.ifr_name, savname, sizeof(ifr.ifr_name));
if ((s = socket(AF_INET, SOCK_DGRAM, 0)) >= 0) {
if (ioctl(s, SIOCGIFFLAGS, (caddr_t)&ifr) >= 0) {
ifr.ifr_flags |= IFF_UP;
if (ioctl(s, SIOCSIFFLAGS, (caddr_t)&ifr)) {
strlcpy(errbuf, strerror(errno), PCAP_ERRBUF_SIZE);
close(tun);
+ tun = -1;
}
}
close(s);
}
}
#endif
}
else
strlcpy(errbuf, strerror(errno), PCAP_ERRBUF_SIZE);
- }
+ if ((tun >= 0) && (errbuf[0] != 0)) {
+ close(tun);
+ tun = -1;
+ }
+ }
#else
strlcpy(errbuf, "No support for tap: devices", PCAP_ERRBUF_SIZE);
#endif /* !defined(__linux) && !defined(HAVE_BSDTUNTAP) */
if (0 == errbuf[0]) {
*eth_api = ETH_API_TAP;
@@ -2102,11 +2137,11 @@
if (!(*handle = (void*) vde_open((char *)vdeswitch_s, (char *)"simh", &voa)))
strlcpy(errbuf, strerror(errno), PCAP_ERRBUF_SIZE);
else {
*eth_api = ETH_API_VDE;
- *fd_handle = vde_datafd((VDECONN*)(*handle));
+ *fd_handle = (SOCKET)vde_datafd((VDECONN*)(*handle));
}
#else
strlcpy(errbuf, "No support for vde: network devices", PCAP_ERRBUF_SIZE);
#endif /* defined(HAVE_VDE_NETWORK) */
}
@@ -2115,11 +2150,11 @@
#if defined(HAVE_SLIRP_NETWORK)
const char *devname = savname + 4;
while (isspace(*devname))
++devname;
- if (!(*handle = (void*) sim_slirp_open(devname, opaque, &_slirp_callback, dptr, dbit)))
+ if (!(*handle = (void*) sim_slirp_open(devname, opaque, &_slirp_callback, dptr, dbit, errbuf, PCAP_ERRBUF_SIZE)))
strlcpy(errbuf, strerror(errno), PCAP_ERRBUF_SIZE);
else {
*eth_api = ETH_API_NAT;
*fd_handle = 0;
}
@@ -2162,11 +2197,11 @@
char command[1024];
/* try to force an otherwise unused interface to be turned on */
memset(command, 0, sizeof(command));
snprintf(command, sizeof(command)-1, "ifconfig %s up", savname);
- (void)system(command);
+ if (system(command)) {};
errbuf[0] = '\0';
*handle = (void*) pcap_open_live(savname, bufsz, ETH_PROMISC, PCAP_READ_TIMEOUT, errbuf);
}
}
#endif
@@ -2275,11 +2310,11 @@
&& (tolower(name[1]) == 't')
&& (tolower(name[2]) == 'h')
&& isdigit(name[3])
) {
num = atoi(&name[3]);
- savname = eth_getname(num, temp, desc);
+ savname = _eth_getname(num, temp, desc);
if (savname == NULL) /* didn't translate */
return SCPE_OPENERR;
}
else {
/* are they trying to use device description? */
@@ -2349,11 +2384,16 @@
pthread_create (&dev->writer_thread, &attr, _eth_writer, (void *)dev);
pthread_attr_destroy(&attr);
}
#endif /* defined (USE_READER_THREAD */
_eth_add_to_open_list (dev);
-return SCPE_OK;
+/*
+ * install a total filter on a newly opened interface and let the device
+ * simulator install an appropriate filter that reflects the device's
+ * configuration.
+ */
+return eth_filter_hash (dev, 0, NULL, FALSE, FALSE, NULL);
}
static t_stat _eth_close_port(int eth_api, pcap_t *pcap, SOCKET pcap_fd)
{
switch (eth_api) {
@@ -2429,10 +2469,34 @@
free(dev->bpf_filter);
eth_zero(dev);
_eth_remove_from_open_list (dev);
return SCPE_OK;
}
+
+const char *eth_version (void)
+{
+#if defined(HAVE_PCAP_NETWORK)
+static char version[256];
+
+if (!version[0]) {
+ strlcpy(version, pcap_lib_version(), sizeof(version));
+ if (memcmp(pcap_lib_version(), "Npcap", 5) == 0) {
+ char maj_min[CBUFSIZE];
+ char *c = version;
+
+ while (*c && !isdigit (*c))
+ ++c;
+ get_glyph (c, maj_min, ',');
+ if (strcmp ("0.9990", maj_min) < 0)
+ snprintf(version, sizeof(version), "Unsupported - %s", pcap_lib_version());
+ }
+ }
+return version;
+#else
+return NULL;
+#endif
+}
t_stat eth_attach_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
{
fprintf (st, "%s attach help\n\n", dptr->name);
fprintf (st, " sim> SHOW ETHERNET\n");
@@ -3037,20 +3101,27 @@
cksum += (cksum >> 16);
/* Return the bitwise complement of the resulting mishmash */
return (uint16)(~cksum);
}
+
+/*
+ * src_addr and dest_addr are presented in network byte order
+ */
static uint16
-pseudo_checksum(uint16 len, uint16 proto, uint16 *src_addr, uint16 *dest_addr, uint8 *buff)
+pseudo_checksum(uint16 len, uint16 proto, void *nsrc_addr, void *ndest_addr, uint8 *buff)
{
uint32 sum;
+uint16 *src_addr = (uint16 *)nsrc_addr;
+uint16 *dest_addr = (uint16 *)ndest_addr;
/* Sum the data first */
sum = 0xffff&(~ip_checksum((uint16 *)buff, len));
-/* add the pseudo header which contains the IP source and destinationn addresses */
+/* add the pseudo header which contains the IP source and
+ destination addresses already in network byte order */
sum += src_addr[0];
sum += src_addr[1];
sum += dest_addr[0];
sum += dest_addr[1];
/* and the protocol number and the length of the UDP packet */
@@ -3107,11 +3178,11 @@
}
if (UDP->checksum == 0)
break; /* UDP Checksums are disabled */
orig_checksum = UDP->checksum;
UDP->checksum = 0;
- UDP->checksum = pseudo_checksum(ntohs(UDP->length), IPPROTO_UDP, (uint16 *)(&IP->source_ip), (uint16 *)(&IP->dest_ip), (uint8 *)UDP);
+ UDP->checksum = pseudo_checksum(ntohs(UDP->length), IPPROTO_UDP, &IP->source_ip, &IP->dest_ip, (uint8 *)UDP);
if (orig_checksum != UDP->checksum)
eth_packet_trace (dev, msg, len, "reading jumbo UDP header Checksum Fixed");
break;
case IPPROTO_ICMP:
ICMP = (struct ICMPHeader *)(((char *)IP)+IP_HLEN(IP));
@@ -3216,11 +3287,11 @@
IP->total_len = htons(payload_len + IP_HLEN(IP) + TCP_DATA_OFFSET(TCP));
}
IP->checksum = 0;
IP->checksum = ip_checksum((uint16 *)IP, IP_HLEN(IP));
TCP->checksum = 0;
- TCP->checksum = pseudo_checksum(ntohs(IP->total_len)-IP_HLEN(IP), IPPROTO_TCP, (uint16 *)(&IP->source_ip), (uint16 *)(&IP->dest_ip), (uint8 *)TCP);
+ TCP->checksum = pseudo_checksum(ntohs(IP->total_len)-IP_HLEN(IP), IPPROTO_TCP, &IP->source_ip, &IP->dest_ip, (uint8 *)TCP);
header.caplen = header.len = 14 + ntohs(IP->total_len);
eth_packet_trace_ex (dev, ((u_char *)IP)-14, header.len, "reading TCP segment", 1, dev->dbit);
#if ETH_MIN_JUMBO_FRAME < ETH_MAX_PACKET
if (1) {
/* Debugging is easier if we read packets directly with pcap
@@ -3288,19 +3359,19 @@
return; /* packet contained length exceeds packet size */
if (UDP->checksum == 0)
return; /* UDP Checksums are disabled */
orig_checksum = UDP->checksum;
UDP->checksum = 0;
- UDP->checksum = pseudo_checksum(ntohs(UDP->length), IPPROTO_UDP, (uint16 *)(&IP->source_ip), (uint16 *)(&IP->dest_ip), (uint8 *)UDP);
+ UDP->checksum = pseudo_checksum(ntohs(UDP->length), IPPROTO_UDP, &IP->source_ip, &IP->dest_ip, (uint8 *)UDP);
if (orig_checksum != UDP->checksum)
eth_packet_trace (dev, msg, len, "reading UDP header Checksum Fixed");
break;
case IPPROTO_TCP:
TCP = (struct TCPHeader *)(((char *)IP)+IP_HLEN(IP));
orig_checksum = TCP->checksum;
TCP->checksum = 0;
- TCP->checksum = pseudo_checksum(ntohs(IP->total_len)-IP_HLEN(IP), IPPROTO_TCP, (uint16 *)(&IP->source_ip), (uint16 *)(&IP->dest_ip), (uint8 *)TCP);
+ TCP->checksum = pseudo_checksum(ntohs(IP->total_len)-IP_HLEN(IP), IPPROTO_TCP, &IP->source_ip, &IP->dest_ip, (uint8 *)TCP);
if (orig_checksum != TCP->checksum)
eth_packet_trace (dev, msg, len, "reading TCP header Checksum Fixed");
break;
case IPPROTO_ICMP:
ICMP = (struct ICMPHeader *)(((char *)IP)+IP_HLEN(IP));
@@ -3769,16 +3840,17 @@
/* filter count OK? */
if ((addr_count < 0) || (addr_count > ETH_FILTER_MAX))
return SCPE_ARG;
else
- if (!addresses) return SCPE_ARG;
+ if (!addresses && (addr_count != 0))
+ return SCPE_ARG;
/* test reflections. This is done early in this routine since eth_reflect */
/* calls eth_filter recursively and thus changes the state of the device. */
if (dev->reflections == -1)
- eth_reflect(dev);
+ status = eth_reflect(dev);
/* set new filter addresses */
for (i = 0; i < addr_count; i++)
memcpy(dev->filter_address[i], addresses[i], sizeof(ETH_MAC));
dev->addr_count = addr_count;
@@ -3828,11 +3900,11 @@
/* setup BPF filters and other fields to minimize packet delivery */
eth_bpf_filter (dev, dev->addr_count, dev->filter_address,
dev->all_multicast, dev->promiscuous,
dev->reflections, &dev->physical_addr,
dev->have_host_nic_phy_addr ? &dev->host_nic_phy_hw_addr: NULL,
- &dev->hash, buf);
+ (dev->hash_filter ? &dev->hash : NULL), buf);
/* get netmask, which is a required argument for compiling. The value,
in our case isn't actually interesting since the filters we generate
aren't referencing IP fields, networks or values */
@@ -3916,11 +3988,11 @@
This routine exists to allow platform specific code to validate and/or
extend the set of available interfaces to include any that are not
returned by pcap_findalldevs.
*/
-int eth_host_devices(int used, int max, ETH_LIST* list)
+static int eth_host_devices(int used, int max, ETH_LIST* list)
{
pcap_t* conn = NULL;
int i, j, datalink = 0;
for (i=0; inext, ++i) {
if ((dev->flags & PCAP_IF_LOOPBACK) || (!strcmp("any", dev->name))) continue;
@@ -4223,11 +4296,11 @@
} \
}
memset (ð_tst, 0, sizeof(eth_tst));
-eth_device_count = eth_devices(ETH_MAX_DEVICE, eth_list);
+eth_device_count = _eth_devices(ETH_MAX_DEVICE, eth_list);
eth_opened = 0;
for (eth_num=0; eth_numtm_mon, tm->tm_mday, 1900 + tm->tm_year,
+ tm->tm_hour % 12, tm->tm_min, (0 == (tm->tm_hour % 12)) ? 'A' : 'P');
+ }
for (p = parts; *p; p++) {
switch (*p) {
case 'f':
tot_size += strlen (fullpath);
break;
@@ -824,10 +845,16 @@
tot_size += ext - name;
break;
case 'x':
tot_size += strlen (ext);
break;
+ case 't':
+ tot_size += strlen (filedatetimebuf);
+ break;
+ case 'z':
+ tot_size += strlen (filesizebuf);
+ break;
}
}
result = (char *)malloc (1 + tot_size);
*result = '\0';
if (*parts == '\0') /* empty part specifier means strip only quotes */
@@ -850,10 +877,16 @@
*ext = chr;
break;
case 'x':
strlcat (result, ext, 1 + tot_size);
break;
+ case 't':
+ strlcat (result, filedatetimebuf, 1 + tot_size);
+ break;
+ case 'z':
+ strlcat (result, filesizebuf, 1 + tot_size);
+ break;
}
}
free (fullpath);
free (tempfilepath);
return result;
@@ -880,11 +913,12 @@
const char *pathsep = (backslash && slash) ? MIN (backslash, slash) : (backslash ? backslash : slash);
GetFullPathNameA(cptr, sizeof(DirName), DirName, (char **)&c);
c = strrchr (DirName, '\\');
*c = '\0'; /* Truncate to just directory path */
- if (!pathsep || (!strcmp (slash, "/*"))) /* Separator wasn't mentioned? */
+ if (!pathsep || /* Separator wasn't mentioned? */
+ (slash && (0 == strcmp (slash, "/*"))))
pathsep = "\\"; /* Default to Windows backslash */
if (*pathsep == '/') { /* If slash separator? */
while ((c = strchr (DirName, '\\')))
*c = '/'; /* Convert backslash to slash */
}
@@ -920,10 +954,11 @@
#if defined (HAVE_GLOB)
glob_t paths;
#else
DIR *dir;
#endif
+int found_count = 0;
struct stat filestat;
char *c;
char DirName[PATH_MAX + 1], WholeName[PATH_MAX + 1], WildName[PATH_MAX + 1];
memset (DirName, 0, sizeof(DirName));
@@ -949,45 +984,53 @@
dir = opendir(DirName[0] ? DirName : "/.");
if (dir) {
struct dirent *ent;
#endif
t_offset FileSize;
- char FileName[PATH_MAX + 1];
+ char *FileName;
const char *MatchName = 1 + strrchr (cptr, '/');
char *p_name;
struct tm *local;
#if defined (HAVE_GLOB)
size_t i;
#endif
#if defined (HAVE_GLOB)
for (i=0; id_name, 0))
continue;
-#else
- /* only match exact name without fnmatch support */
- if (strcmp(MatchName, ent->d_name) != 0)
+#else /* !defined (HAVE_FNMATCH) */
+ /* only match all names or exact name without fnmatch support */
+ if ((strcmp(MatchName, "*") != 0) &&
+ (strcmp(MatchName, ent->d_name) != 0))
continue;
-#endif
+#endif /* defined (HAVE_FNMATCH) */
+ FileName = (char *)malloc (1 + strlen (DirName) + strlen (ent->d_name));
sprintf (FileName, "%s%s", DirName, ent->d_name);
-#endif
+#endif /* defined (HAVE_GLOB) */
p_name = FileName + strlen (DirName);
memset (&filestat, 0, sizeof (filestat));
(void)stat (FileName, &filestat);
FileSize = (t_offset)((filestat.st_mode & S_IFDIR) ? 0 : sim_fsize_name_ex (FileName));
entry (DirName, p_name, FileSize, &filestat, context);
+ free (FileName);
+ ++found_count;
}
#if defined (HAVE_GLOB)
globfree (&paths);
#else
closedir (dir);
#endif
}
else
return SCPE_ARG;
-return SCPE_OK;
+if (found_count)
+ return SCPE_OK;
+else
+ return SCPE_ARG;
}
#endif /* !defined(_WIN32) */
Index: src/SIMH/sim_rev.h
==================================================================
--- src/SIMH/sim_rev.h
+++ src/SIMH/sim_rev.h
@@ -46,10 +46,18 @@
#if defined(SIM_NEED_GIT_COMMIT_ID)
#include ".git-commit-id.h"
#endif
+/*
+ Simh's git commit id would be undefined when working with an
+ extracted archive (zip file or tar ball). To address this
+ problem and record the commit id that the archive was created
+ from, the archive creation process populates the below
+ information as a consequence of the "sim_rev.h export-subst"
+ line in the .gitattributes file.
+ */
#if !defined(SIM_GIT_COMMIT_ID)
#define SIM_GIT_COMMIT_ID $Format:%H$
#define SIM_GIT_COMMIT_TIME $Format:%aI$
#endif
Index: src/SIMH/sim_serial.c
==================================================================
--- src/SIMH/sim_serial.c
+++ src/SIMH/sim_serial.c
@@ -360,11 +360,11 @@
fprintf(st,"Open Serial Devices:\n");
for (i=0; imp->dptr->name, (int)(serial_open_devices[i].line->mp->ldsc-serial_open_devices[i].line),
- serial_open_devices[i].line->destination, d ? " {" : "", d ? d : "", d ? ")" : "", serial_open_devices[i].line->serconfig);
+ serial_open_devices[i].line->destination, ((d != NULL) && (*d != '\0')) ? " (" : "", ((d != NULL) && (*d != '\0')) ? d : "", ((d != NULL) && (*d != '\0')) ? ")" : "", serial_open_devices[i].line->serconfig);
}
}
return SCPE_OK;
}
@@ -446,12 +446,12 @@
return port;
}
void sim_close_serial (SERHANDLE port)
{
-sim_close_os_serial (port);
_serial_remove_from_open_list (port);
+sim_close_os_serial (port);
}
t_stat sim_config_serial (SERHANDLE port, CONST char *sconfig)
{
CONST char *pptr;
Index: src/SIMH/sim_sock.c
==================================================================
--- src/SIMH/sim_sock.c
+++ src/SIMH/sim_sock.c
@@ -260,10 +260,11 @@
}
if (service) {
char *c;
port = strtoul(service, &c, 10);
+ port = htons((unsigned short)port);
if ((port == 0) || (*c != '\0')) {
switch (hints->ai_socktype)
{
case SOCK_DGRAM:
se = getservbyname(service, "udp");
@@ -282,45 +283,38 @@
if (hostname) {
if ((0xffffffff != (ipaddr.s_addr = inet_addr(hostname))) ||
(0 == strcmp("255.255.255.255", hostname))) {
fixed[0] = &ipaddr;
fixed[1] = NULL;
- }
- else {
- if ((0xffffffff != (ipaddr.s_addr = inet_addr(hostname))) ||
- (0 == strcmp("255.255.255.255", hostname))) {
- fixed[0] = &ipaddr;
- fixed[1] = NULL;
- if ((hints->ai_flags & AI_CANONNAME) && !(hints->ai_flags & AI_NUMERICHOST)) {
- he = gethostbyaddr((char *)&ipaddr, 4, AF_INET);
- if (NULL != he)
- cname = he->h_name;
- else
- cname = hostname;
- }
- ips = fixed;
- }
- else {
- if (hints->ai_flags & AI_NUMERICHOST)
- return EAI_NONAME;
- he = gethostbyname(hostname);
- if (he) {
- ips = (struct in_addr **)he->h_addr_list;
- if (hints->ai_flags & AI_CANONNAME)
- cname = he->h_name;
- }
- else {
- switch (h_errno)
- {
- case HOST_NOT_FOUND:
- case NO_DATA:
- return EAI_NONAME;
- case TRY_AGAIN:
- return EAI_AGAIN;
- default:
- return EAI_FAIL;
- }
+ if ((hints->ai_flags & AI_CANONNAME) && !(hints->ai_flags & AI_NUMERICHOST)) {
+ he = gethostbyaddr((char *)&ipaddr, 4, AF_INET);
+ if (NULL != he)
+ cname = he->h_name;
+ else
+ cname = hostname;
+ }
+ ips = fixed;
+ }
+ else {
+ if (hints->ai_flags & AI_NUMERICHOST)
+ return EAI_NONAME;
+ he = gethostbyname(hostname);
+ if (he) {
+ ips = (struct in_addr **)he->h_addr_list;
+ if (hints->ai_flags & AI_CANONNAME)
+ cname = he->h_name;
+ }
+ else {
+ switch (h_errno)
+ {
+ case HOST_NOT_FOUND:
+ case NO_DATA:
+ return EAI_NONAME;
+ case TRY_AGAIN:
+ return EAI_AGAIN;
+ default:
+ return EAI_FAIL;
}
}
}
}
else {
@@ -435,10 +429,14 @@
#if defined(_WIN32) || defined(__CYGWIN__)
#if !defined(IPV6_V6ONLY) /* Older XP environments may not define IPV6_V6ONLY */
#define IPV6_V6ONLY 27 /* Treat wildcard bind as AF_INET6-only. */
+#endif
+#if defined(TEST_INFO_STUBS)
+#undef IPV6_V6ONLY
+#undef AF_INET6
#endif
/* Dynamic DLL load variables */
#ifdef _WIN32
static HINSTANCE hLib = 0; /* handle to DLL */
#else
@@ -688,11 +686,11 @@
Outputs:
host = pointer to buffer for IP address (may be NULL), 0 = none
port = pointer to buffer for IP port (may be NULL), 0 = none
localport
= pointer to buffer for local IP port (may be NULL), 0 = none
- result = status (SCPE_OK on complete success or SCPE_ARG if
+ result = status (0 on complete success or -1 if
parsing can't happen due to bad syntax, a value is
out of range, a result can't fit into a result buffer,
a service name doesn't exist, or a validation name
doesn't match the parsed host)
*/
@@ -743,10 +741,16 @@
#endif /* endif AF_INET6 */
#endif /* endif _WIN32 */
#if defined (SIGPIPE)
signal (SIGPIPE, SIG_IGN); /* no pipe signals */
#endif
+#if defined(TEST_INFO_STUBS)
+/* force use of stubs */
+p_getaddrinfo = (getaddrinfo_func)s_getaddrinfo;
+p_getnameinfo = (getnameinfo_func)s_getnameinfo;
+p_freeaddrinfo = (freeaddrinfo_func)s_freeaddrinfo;
+#endif
}
void sim_cleanup_sock (void)
{
#if defined (_WIN32)
@@ -922,11 +926,11 @@
if (sta == SOCKET_ERROR) /* bind error? */
return sim_err_sock (newsock, "bind");
if (!(opt_flags & SIM_SOCK_OPT_BLOCKING)) {
sta = sim_setnonblock (newsock); /* set nonblocking */
if (sta == SOCKET_ERROR) /* fcntl error? */
- return sim_err_sock (newsock, "fcntl");
+ return sim_err_sock (newsock, "setnonblock");
}
sta = listen (newsock, 1); /* listen on socket */
if (sta == SOCKET_ERROR) /* listen error? */
return sim_err_sock (newsock, "listen");
return newsock; /* got it! */
@@ -994,11 +998,11 @@
if (!(opt_flags & SIM_SOCK_OPT_BLOCKING)) {
sta = sim_setnonblock (newsock); /* set nonblocking */
if (sta == SOCKET_ERROR) { /* fcntl error? */
p_freeaddrinfo (result);
- return sim_err_sock (newsock, "fcntl");
+ return sim_err_sock (newsock, "setnonblock");
}
}
if ((!(opt_flags & SIM_SOCK_OPT_DATAGRAM)) && (opt_flags & SIM_SOCK_OPT_NODELAY)) {
sta = sim_setnodelay (newsock); /* set nodelay */
if (sta == SOCKET_ERROR) { /* setsock error? */
@@ -1043,11 +1047,11 @@
int keepalive = 1;
#if defined (macintosh) || defined (__linux) || defined (__linux__) || \
defined (__APPLE__) || defined (__OpenBSD__) || \
defined(__NetBSD__) || defined(__FreeBSD__) || \
(defined(__hpux) && defined(_XOPEN_SOURCE_EXTENDED)) || \
- defined (__HAIKU__)
+ defined (__HAIKU__) || defined(__CYGWIN__)
socklen_t size;
#elif defined (_WIN32) || defined (__EMX__) || \
(defined (__ALPHA) && defined (__unix__)) || \
defined (__hpux)
int size;
@@ -1068,24 +1072,20 @@
sim_err_sock(newsock, "accept");
return INVALID_SOCKET;
}
if (connectaddr != NULL) {
*connectaddr = (char *)calloc(1, NI_MAXHOST+1);
-#ifdef AF_INET6
p_getnameinfo((struct sockaddr *)&clientname, size, *connectaddr, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
if (0 == memcmp("::ffff:", *connectaddr, 7)) /* is this a IPv4-mapped IPv6 address? */
memmove(*connectaddr, 7+*connectaddr, /* prefer bare IPv4 address */
strlen(*connectaddr) - 7 + 1); /* length to include terminating \0 */
-#else
- strcpy(*connectaddr, inet_ntoa(((struct sockaddr_in *)&connectaddr)->s_addr));
-#endif
}
if (!(opt_flags & SIM_SOCK_OPT_BLOCKING)) {
sta = sim_setnonblock (newsock); /* set nonblocking */
if (sta == SOCKET_ERROR) /* fcntl error? */
- return sim_err_sock (newsock, "fcntl");
+ return sim_err_sock (newsock, "setnonblock");
}
if ((opt_flags & SIM_SOCK_OPT_NODELAY)) {
sta = sim_setnodelay (newsock); /* set nonblocking */
if (sta == SOCKET_ERROR) /* setsockopt error? */
@@ -1109,11 +1109,11 @@
struct sockaddr_storage peername;
#if defined (macintosh) || defined (__linux) || defined (__linux__) || \
defined (__APPLE__) || defined (__OpenBSD__) || \
defined(__NetBSD__) || defined(__FreeBSD__) || \
(defined(__hpux) && defined(_XOPEN_SOURCE_EXTENDED)) || \
- defined (__HAIKU__)
+ defined (__HAIKU__) || defined(__CYGWIN__)
socklen_t peernamesize = (socklen_t)sizeof(peername);
#elif defined (_WIN32) || defined (__EMX__) || \
(defined (__ALPHA) && defined (__unix__)) || \
defined (__hpux)
int peernamesize = (int)sizeof(peername);
@@ -1145,11 +1145,11 @@
{
#if defined (macintosh) || defined (__linux) || defined (__linux__) || \
defined (__APPLE__) || defined (__OpenBSD__) || \
defined(__NetBSD__) || defined(__FreeBSD__) || \
(defined(__hpux) && defined(_XOPEN_SOURCE_EXTENDED)) || \
- defined (__HAIKU__)
+ defined (__HAIKU__) || defined(__CYGWIN__)
socklen_t size = (socklen_t)addrsize;
#elif defined (_WIN32) || defined (__EMX__) || \
(defined (__ALPHA) && defined (__unix__)) || \
defined (__hpux)
int size = (int)addrsize;
@@ -1156,23 +1156,18 @@
#else
size_t size = addrsize;
#endif
int ret = 0;
-#ifdef AF_INET6
*hostnamebuf = '\0';
*portnamebuf = '\0';
ret = p_getnameinfo(addr, size, hostnamebuf, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
if (0 == memcmp("::ffff:", hostnamebuf, 7)) /* is this a IPv4-mapped IPv6 address? */
memmove(hostnamebuf, 7+hostnamebuf, /* prefer bare IPv4 address */
strlen(hostnamebuf) + 7 - 1); /* length to include terminating \0 */
if (!ret)
ret = p_getnameinfo(addr, size, NULL, 0, portnamebuf, NI_MAXSERV, NI_NUMERICSERV);
-#else
-strcpy(hostnamebuf, inet_ntoa(((struct sockaddr_in *)addr)->s_addr));
-sprintf(portnamebuf, "%d", (int)ntohs(((struct sockaddr_in *)addr)->s_port)));
-#endif
return ret;
}
int sim_getnames_sock (SOCKET sock, char **socknamebuf, char **peernamebuf)
{
@@ -1179,11 +1174,11 @@
struct sockaddr_storage sockname, peername;
#if defined (macintosh) || defined (__linux) || defined (__linux__) || \
defined (__APPLE__) || defined (__OpenBSD__) || \
defined(__NetBSD__) || defined(__FreeBSD__) || \
(defined(__hpux) && defined(_XOPEN_SOURCE_EXTENDED)) || \
- defined (__HAIKU__)
+ defined (__HAIKU__) || defined(__CYGWIN__)
socklen_t socknamesize = (socklen_t)sizeof(sockname);
socklen_t peernamesize = (socklen_t)sizeof(peername);
#elif defined (_WIN32) || defined (__EMX__) || \
(defined (__ALPHA) && defined (__unix__)) || \
defined (__hpux)
Index: src/SIMH/sim_sock.h
==================================================================
--- src/SIMH/sim_sock.h
+++ src/SIMH/sim_sock.h
@@ -118,11 +118,11 @@
#define SIM_SOCK_OPT_NODELAY 0x0004
#define SIM_SOCK_OPT_BLOCKING 0x0008
SOCKET sim_master_sock_ex (const char *hostport, int *parse_status, int opt_flags);
#define sim_master_sock(hostport, parse_status) sim_master_sock_ex(hostport, parse_status, ((sim_switches & SWMASK ('U')) ? SIM_SOCK_OPT_REUSEADDR : 0))
SOCKET sim_connect_sock_ex (const char *sourcehostport, const char *hostport, const char *default_host, const char *default_port, int opt_flags);
-#define sim_connect_sock(hostport, default_host, default_port) sim_connect_sock_ex(NULL, hostport, default_host, default_port, 0)
+#define sim_connect_sock(hostport, default_host, default_port) sim_connect_sock_ex(NULL, hostport, default_host, default_port, SIM_SOCK_OPT_BLOCKING)
SOCKET sim_accept_conn_ex (SOCKET master, char **connectaddr, int opt_flags);
#define sim_accept_conn(master, connectaddr) sim_accept_conn_ex(master, connectaddr, 0)
int sim_check_conn (SOCKET sock, int rd);
int sim_read_sock (SOCKET sock, char *buf, int nbytes);
int sim_write_sock (SOCKET sock, const char *msg, int nbytes);
Index: src/SIMH/sim_tape.c
==================================================================
--- src/SIMH/sim_tape.c
+++ src/SIMH/sim_tape.c
@@ -98,25 +98,26 @@
#if defined SIM_ASYNCH_IO
#include
#endif
-struct sim_tape_fmt {
+static struct sim_tape_fmt {
const char *name; /* name */
- int32 uflags; /* unit flags */
+ int32 uflags; /* unit flags */
t_addr bot; /* bot test */
t_addr eom_remnant; /* potentially unprocessed data */
- };
-
-static struct sim_tape_fmt fmts[MTUF_N_FMT] = {
- { "SIMH", 0, sizeof (t_mtrlnt) - 1, sizeof (t_mtrlnt)},
- { "E11", 0, sizeof (t_mtrlnt) - 1, sizeof (t_mtrlnt) },
- { "TPC", UNIT_RO, sizeof (t_tpclnt) - 1, sizeof (t_tpclnt) },
- { "P7B", 0, 0, 0 },
- { "AWS", 0, 0, 0 },
- { "TAR", 0, 0, 0 },
- { NULL, 0, 0 }
+ } fmts[] = {
+ { "SIMH", 0, sizeof (t_mtrlnt) - 1, sizeof (t_mtrlnt) },
+ { "E11", 0, sizeof (t_mtrlnt) - 1, sizeof (t_mtrlnt) },
+ { "TPC", UNIT_RO, sizeof (t_tpclnt) - 1, sizeof (t_tpclnt) },
+ { "P7B", 0, 0, 0 },
+ { "AWS", 0, 0, 0 },
+ { "TAR", UNIT_RO, 0, 0 },
+ { "ANSI", UNIT_RO, 0, 0 },
+ { "FIXED", UNIT_RO, 0, 0 },
+ { "DOS11", UNIT_RO, 0, 0 },
+ { NULL, 0, 0, 0 }
};
static const uint32 bpi [] = { /* tape density table, indexed by MT_DENS constants */
0, /* 0 = MT_DENS_NONE -- density not set */
200, /* 1 = MT_DENS_200 -- 200 bpi NRZI */
@@ -373,10 +374,175 @@
#define AIO_CALL(op, _buf, _fc, _bc, _max, _vbc, _gaplen, _bpi, _obj, _callback) \
if (_callback) \
(_callback) (uptr, r);
#endif
+typedef struct VOL1 {
+ char type[3]; /* VOL */
+ char num; /* 1 */
+ char ident[6]; /* characters blank padded > */
+ char accessibity; /* blank */
+ char reserved1[13]; /* */
+ char implement[13]; /* */
+ char owner[14]; /* */
+ char reserved2[28]; /* */
+ char standard; /* 1,3 or 4 */
+ } VOL1;
+
+typedef struct HDR1 { /* Also EOF1, EOV1 */
+ char type[3]; /* HDR|EOF|EOV */
+ char num; /* 1 */
+ char file_ident[17]; /* filename */
+ char file_set[6]; /* label ident */
+ char file_section[4]; /* 0001 */
+ char file_sequence[4]; /* 0001 */
+ char generation_number[4]; /* 0001 */
+ char version_number[2]; /* 00 */
+ char creation_date[6]; /* cyyddd */
+ char expiration_date[6];
+ char accessibility; /* space */
+ char block_count[6]; /* 000000 */
+ char system_code[13]; /* */
+ char reserved[7]; /* blank */
+ } HDR1;
+
+typedef struct HDR2 { /* Also EOF2, EOV2 */
+ char type[3]; /* HDR */
+ char num; /* 2 */
+ char record_format; /* F(fixed)|D(variable)|S(spanned) */
+ char block_length[5]; /* label ident */
+ char record_length[5]; /* */
+ char reserved_os1[21]; /* */
+ char carriage_control; /* A - Fortran CC, M - Record contained CC, space - CR/LF to be added */
+ char reserved_os2[13]; /* */
+ char buffer_offset[2]; /* */
+ char reserved_std[28]; /* */
+ } HDR2;
+
+typedef struct HDR3 { /* Also EOF3, EOV3 */
+ char type[3]; /* HDR */
+ char num; /* 2 */
+ char record_format; /* F(fixed)|D(variable)|S(spanned) */
+ char block_length[5]; /* label ident */
+ char record_length[5]; /* */
+ char reserved_os[35]; /* */
+ char buffer_offset[2]; /* */
+ char reserved_std[28]; /* */
+ } HDR3;
+
+typedef struct HDR4 { /* Also EOF4, EOV4 */
+ char type[3]; /* HDR */
+ char num; /* 4 */
+ char blank; /* blank */
+ char extra_name[62]; /* */
+ char extra_name_used[2]; /* 99 */
+ char unused[11];
+ } HDR4;
+
+typedef struct TAPE_RECORD {
+ uint32 size;
+ uint8 data[1];
+ } TAPE_RECORD;
+
+typedef struct MEMORY_TAPE {
+ uint32 ansi_type; /* ANSI-VMS, ANSI-RT11, ANSI-RSTS, ANSI-RSX11, etc. */
+ uint32 file_count; /* number of labeled files */
+ uint32 record_count; /* number of entries in the record array */
+ uint32 array_size; /* allocated size of records array */
+ uint32 block_size; /* tape block size */
+ TAPE_RECORD **records;
+ VOL1 vol1;
+ } MEMORY_TAPE;
+
+const char HDR3_RMS_STREAM[] = "HDR3020002040000"
+ "0000000100000000"
+ "0000000002000000"
+ "0000000000000000"
+ "0000 ";
+const char HDR3_RMS_STMLF[] = "HDR3020002050000"
+ "0000000100000000"
+ "0000000002000000"
+ "0000000000000000"
+ "0000 ";
+const char HDR3_RMS_VAR[] = "HDR3005C02020000"
+ "0000000100000000"
+ "0000000000000000"
+ "0000000000000000"
+ "0000 ";
+const char HDR3_RMS_FIXED[] = "HDR3020000010000"
+ "0000000100000000"
+ "0000000002000000"
+ "0000000000000000"
+ "0000 ";
+const char HDR3_RMS_VARRSX[] = "HDR300000A020000"
+ "0000000100000000"
+ "0000000000000000"
+ "0000000000000000"
+ "0000 ";
+const char HDR3_RMS_FIXRSX[] = "HDR3020008010000"
+ "0000000100000000"
+ "0000000000000000"
+ "0000000000000000"
+ "0000 ";
+
+static struct ansi_tape_parameters {
+ const char *name; /* operating system */
+ const char *system_code; /* */
+ t_bool nohdr2; /* no HDR2 records */
+ t_bool nohdr3; /* no HDR2 records */
+ t_bool fixed_text; /* */
+ char vol1_standard; /* 3 or 4 */
+ const char *hdr3_fixed; /* HDR3 template for Fixed format files */
+ const char *hdr3_lf_line_endings; /* HDR3 template for text with LF line ending files */
+ const char *hdr3_crlf_line_endings;/* HDR3 template for text with CRLF line ending files */
+ int skip_lf_line_endings;
+ int skip_crlf_line_endings;
+ t_bool y2k_date_bug;
+ t_bool zero_record_length;
+ char record_format;
+ char carriage_control;
+ } ansi_args[] = { /* code nohdr2 nohdr3 fixed_text lvl hdr3 fir fuxed hdr3 fir lf hdr3 for crlf skLF CRLF Y2KDT 0RecLnt RFM CC*/
+ {"ANSI-VMS" , "DECFILE11A", FALSE, FALSE, FALSE, '3', HDR3_RMS_FIXED, HDR3_RMS_STMLF, HDR3_RMS_STREAM, 0, 0, FALSE, FALSE, 0, 0},
+ {"ANSI-RSX11" , "DECFILE11A", FALSE, FALSE, FALSE, '4', HDR3_RMS_FIXRSX, HDR3_RMS_VARRSX, HDR3_RMS_VARRSX, 1, 2, FALSE, FALSE, 0, 0},
+ {"ANSI-RT11" , "DECRT11A", TRUE, TRUE, TRUE, '3', NULL, NULL, NULL, 0, 0, FALSE, FALSE, 0, 0},
+ {"ANSI-RSTS" , "DECRSTS/E", FALSE, TRUE, TRUE, '3', NULL, NULL, NULL, 0, 0, TRUE, TRUE, 'U', 'M'},
+ {"ANSI-VAR" , "DECRSTS/E", FALSE, TRUE, FALSE, '3', NULL, NULL, NULL, 1, 2, TRUE, FALSE, 'D', ' '},
+ {NULL}
+ };
+
+
+static MEMORY_TAPE *ansi_create_tape (const char *label, uint32 block_size, uint32 ansi_type);
+static MEMORY_TAPE *memory_create_tape (void);
+static void memory_free_tape (void *vtape);
+static void sim_tape_add_ansi_entry (const char *directory,
+ const char *filename,
+ t_offset FileSize,
+ const struct stat *filestat,
+ void *context);
+static t_bool memory_tape_add_block (MEMORY_TAPE *tape, uint8 *block, uint32 size);
+
+typedef struct DOS11_HDR {
+ uint16 fname[2]; /* File name (RAD50 - 6 characters) */
+ uint16 ext; /* Extension (RAD50 - 3 characters) */
+ uint8 prog; /* Programmer # */
+ uint8 proj; /* Project # */
+ uint16 prot; /* Protection */
+ uint16 date; /* (year - 1970) * 1000 + day of year */
+ uint16 fname3; /* File name (RAD50 - 3 characters) */
+ } DOS11_HDR;
+#define DOS11_PROT 0233
+
+static void sim_tape_add_dos11_entry (const char *directory,
+ const char *filename,
+ t_offset FileSize,
+ const struct stat *filestat,
+ void *context);
+
+static t_stat sim_export_tape (UNIT *uptr, const char *export_file);
+static FILE *tape_open_and_check_file(const char *filename);
+static int tape_classify_file_contents (FILE *f, size_t *max_record_size, t_bool *lf_line_endings, t_bool *crlf_line_endings);
+
/* Enable asynchronous operation */
t_stat sim_tape_set_async (UNIT *uptr, int latency)
{
@@ -446,11 +612,22 @@
sim_tape_clr_async (uptr);
if (sim_asynch_enabled)
sim_tape_set_async (uptr, ctx->asynch_io_latency);
#endif
-fflush (uptr->fileref);
+if (MT_GET_FMT (uptr) < MTUF_F_ANSI)
+ fflush (uptr->fileref);
+}
+
+static const char *_sim_tape_format_name (UNIT *uptr)
+{
+int32 f = MT_GET_FMT (uptr);
+
+if (f == MTUF_F_ANSI)
+ return ansi_args[MT_GET_ANSI_TYP (uptr)].name;
+else
+ return fmts[f].name;
}
/* Attach tape unit */
t_stat sim_tape_attach (UNIT *uptr, CONST char *cptr)
@@ -466,49 +643,258 @@
{
struct tape_context *ctx;
uint32 objc;
DEVICE *dptr;
char gbuf[CBUFSIZE];
+char export_file[CBUFSIZE] = "";
+uint32 recsize = 0;
t_stat r;
t_bool auto_format = FALSE;
t_bool had_debug = (sim_deb != NULL);
uint32 starting_dctrl = uptr->dctrl;
int32 saved_switches = sim_switches;
+MEMORY_TAPE *tape = NULL;
if ((dptr = find_dev_from_unit (uptr)) == NULL)
return SCPE_NOATT;
if (sim_switches & SWMASK ('F')) { /* format spec? */
cptr = get_glyph (cptr, gbuf, 0); /* get spec */
if (*cptr == 0) /* must be more */
- return sim_messagef (SCPE_2FARG, "Missing Format specifier and filename to attach\n");
+ return sim_messagef (SCPE_2FARG, "Missing Format specifier and/or filename to attach\n");
if (sim_tape_set_fmt (uptr, 0, gbuf, NULL) != SCPE_OK)
return sim_messagef (SCPE_ARG, "Invalid Tape Format: %s\n", gbuf);
sim_switches = sim_switches & ~(SWMASK ('F')); /* Record Format specifier already processed */
auto_format = TRUE;
}
-if (MT_GET_FMT (uptr) == MTUF_F_TAR) {
- if (sim_switches & SWMASK ('B')) { /* Record Size (blocking factor)? */
- uint32 recsize;
-
- cptr = get_glyph (cptr, gbuf, 0); /* get spec */
- if (*cptr == 0) /* must be more */
- return sim_messagef (SCPE_2FARG, "Missing Record Size and filename to attach\n");
- recsize = (uint32) get_uint (gbuf, 10, 65536, &r);
- if ((r != SCPE_OK) || (recsize == 0))
- return sim_messagef (SCPE_ARG, "Invalid Tape Record Size: %s\n", gbuf);
- uptr->recsize = recsize;
- sim_switches = sim_switches & ~(SWMASK ('B')); /* Record Blocking Factor */
- }
- if (uptr->recsize == 0)
+if (sim_switches & SWMASK ('B')) { /* Record Size (blocking factor)? */
+ cptr = get_glyph (cptr, gbuf, 0); /* get spec */
+ if (*cptr == 0) /* must be more */
+ return sim_messagef (SCPE_2FARG, "Missing Record Size and filename to attach\n");
+ recsize = (uint32) get_uint (gbuf, 10, 65536, &r);
+ if ((r != SCPE_OK) || (recsize == 0))
+ return sim_messagef (SCPE_ARG, "Invalid Tape Record Size: %s\n", gbuf);
+ uptr->recsize = recsize;
+ sim_switches = sim_switches & ~(SWMASK ('B')); /* Record Blocking Factor */
+ }
+else {
+ if ((MT_GET_FMT (uptr) == MTUF_F_TAR) && (uptr->recsize == 0))
uptr->recsize = TAR_DFLT_RECSIZE;
}
if ((MT_GET_FMT (uptr) == MTUF_F_TPC) ||
- (MT_GET_FMT (uptr) == MTUF_F_TAR))
- sim_switches |= SWMASK ('R'); /* Force ReadOnly attach for TPC and TAR tapes */
-r = attach_unit (uptr, (CONST char *)cptr); /* attach unit */
-if (r != SCPE_OK) /* error? */
- return sim_messagef (r, "Can't open tape image: %s\n", cptr);
+ (MT_GET_FMT (uptr) == MTUF_F_TAR) ||
+ (MT_GET_FMT (uptr) >= MTUF_F_ANSI))
+ sim_switches |= SWMASK ('R'); /* Force ReadOnly attach for TPC, TAR and ANSI tapes */
+if (sim_switches & SWMASK ('X'))
+ cptr = get_glyph_nc (cptr, export_file, 0); /* get export file spec */
+switch (MT_GET_FMT (uptr)) {
+ case MTUF_F_ANSI:
+ if (1) {
+ const char *ocptr = cptr;
+ char label[CBUFSIZE] = "simh";
+ int file_errors = 0;
+
+ if ((MT_GET_ANSI_TYP (uptr) == MTAT_F_RT11) ||
+ (MT_GET_ANSI_TYP (uptr) == MTAT_F_RSX11) ||
+ (MT_GET_ANSI_TYP (uptr) == MTAT_F_RSTS))
+ uptr->recsize = 512;
+ if (uptr->recsize == 0)
+ uptr->recsize = 2048;
+ else {
+ if ((uptr->recsize < 512) || (uptr->recsize % 512))
+ return sim_messagef (SCPE_ARG, "Block size of %u is below or not a multiple of the required minimum ANSI size of 512.\n", uptr->recsize);
+ }
+ tape = ansi_create_tape (label, uptr->recsize, MT_GET_ANSI_TYP (uptr));
+ uptr->fileref = (FILE *)tape;
+ if (!uptr->fileref)
+ return SCPE_MEM;
+ while (*cptr != 0) { /* do all mods */
+ uint32 initial_file_count = tape->file_count;
+
+ cptr = get_glyph_nc (cptr, gbuf, ','); /* get filename */
+ r = sim_dir_scan (gbuf, sim_tape_add_ansi_entry, tape);
+ if (r != SCPE_OK)
+ sim_messagef (SCPE_ARG, "file not found: %s\n", gbuf);
+ if (tape->file_count == initial_file_count)
+ ++file_errors;
+ }
+ if ((tape->file_count > 0) && (file_errors == 0)) {
+ r = SCPE_OK;
+ memory_tape_add_block (tape, NULL, 0); /* Tape Mark */
+ uptr->flags |= UNIT_ATT;
+ uptr->filename = (char *)malloc (strlen (ocptr) + 1);
+ strcpy (uptr->filename, ocptr);
+ uptr->tape_eom = tape->record_count;
+ }
+ else {
+ r = SCPE_ARG;
+ memory_free_tape (uptr->fileref);
+ uptr->fileref = NULL;
+ cptr = ocptr;
+ }
+ }
+ break;
+
+ case MTUF_F_FIXED:
+ if (1) {
+ FILE *f;
+ size_t max_record_size;
+ t_bool lf_line_endings;
+ t_bool crlf_line_endings;
+ uint8 *block = NULL;
+ int error = FALSE;
+ static const uint8 ascii2ebcdic[128] = {
+ 0000,0001,0002,0003,0067,0055,0056,0057,
+ 0026,0005,0045,0013,0014,0015,0016,0017,
+ 0020,0021,0022,0023,0074,0075,0062,0046,
+ 0030,0031,0077,0047,0034,0035,0036,0037,
+ 0100,0117,0177,0173,0133,0154,0120,0175,
+ 0115,0135,0134,0116,0153,0140,0113,0141,
+ 0360,0361,0362,0363,0364,0365,0366,0367,
+ 0370,0371,0172,0136,0114,0176,0156,0157,
+ 0174,0301,0302,0303,0304,0305,0306,0307,
+ 0310,0311,0321,0322,0323,0324,0325,0326,
+ 0327,0330,0331,0342,0343,0344,0345,0346,
+ 0347,0350,0351,0112,0340,0132,0137,0155,
+ 0171,0201,0202,0203,0204,0205,0206,0207,
+ 0210,0211,0221,0222,0223,0224,0225,0226,
+ 0227,0230,0231,0242,0243,0244,0245,0246,
+ 0247,0250,0251,0300,0152,0320,0241,0007};
+
+ tape = memory_create_tape ();
+ uptr->fileref = (FILE *)tape;
+ if (!uptr->fileref)
+ return SCPE_MEM;
+ f = fopen (cptr, "rb");
+ if (f == NULL) {
+ r = sim_messagef (SCPE_OPENERR, "Can't open: %s - %s\n", cptr, strerror (errno));
+ break;
+ }
+ tape_classify_file_contents (f, &max_record_size, &lf_line_endings, &crlf_line_endings);
+ if ((!lf_line_endings) && (!crlf_line_endings)) { /* binary file? */
+ if (uptr->recsize == 0) {
+ r = sim_messagef (SCPE_ARG, "Binary file %s must specify a record size with -B\n", cptr);
+ fclose (f);
+ break;
+ }
+ block = (uint8 *)malloc (uptr->recsize);
+ tape->block_size = uptr->recsize;
+ while (!feof(f) && !error) {
+ size_t data_read = fread (block, 1, tape->block_size, f);
+ if (data_read > 0)
+ error = memory_tape_add_block (tape, block, data_read);
+ }
+ }
+ else { /* text file */
+ if (uptr->recsize == 0)
+ uptr->recsize = max_record_size;
+ if (uptr->recsize < max_record_size) {
+ r = sim_messagef (SCPE_ARG, "Text file: %s has lines longer than %d\n", cptr, (int)uptr->recsize);
+ fclose (f);
+ break;
+ }
+ tape->block_size = uptr->recsize;
+ block = (uint8 *)calloc (1, uptr->recsize + 3);
+ while (!feof(f) && !error) {
+ if (fgets ((char *)block, uptr->recsize + 3, f)) {
+ size_t len = strlen ((char *)block);
+
+ while ((len > 0) &&
+ ((block[len - 1] == '\r') || (block[len - 1] == '\n')))
+ --len;
+ memset (block + len, ' ', uptr->recsize - len);
+ if (sim_switches & SWMASK ('C')) {
+ uint32 i;
+
+ for (i=0; irecsize; i++)
+ block[i] = ascii2ebcdic[block[i]];
+ }
+ error = memory_tape_add_block (tape, block, uptr->recsize);
+ }
+ else
+ error = ferror (f);
+ }
+ }
+ free (block);
+ fclose (f);
+ r = SCPE_OK;
+ memory_tape_add_block (tape, NULL, 0); /* Tape Mark */
+ memory_tape_add_block (tape, NULL, 0); /* Tape Mark */
+ uptr->flags |= UNIT_ATT;
+ uptr->filename = (char *)malloc (strlen (cptr) + 1);
+ strcpy (uptr->filename, cptr);
+ uptr->tape_eom = tape->record_count;
+ }
+ break;
+
+ case MTUF_F_DOS11:
+ if (1) {
+ const char *ocptr = cptr;
+ int file_errors = 0;
+
+ uptr->recsize = 512;
+
+ tape = memory_create_tape();
+ tape->block_size = uptr->recsize;
+ uptr->fileref = (FILE *)tape;
+ if (!uptr->fileref)
+ return SCPE_MEM;
+
+ while (*cptr != 0) {
+ uint32 initial_file_count = tape->file_count;
+
+ cptr = get_glyph_nc (cptr, gbuf, ','); /* Get filename */
+ r = sim_dir_scan (gbuf, sim_tape_add_dos11_entry, tape);
+ if (r != SCPE_OK)
+ sim_messagef (SCPE_ARG, "file not found: %s\n", gbuf);
+ if (tape->file_count == initial_file_count)
+ ++file_errors;
+ }
+
+ if ((tape->file_count > 0) && (file_errors == 0)) {
+ r = SCPE_OK;
+ memory_tape_add_block (tape, NULL, 0); /* Tape Mark */
+ memory_tape_add_block (tape, NULL, 0); /* Tape Mark */
+ uptr->flags |= UNIT_ATT;
+ uptr->filename = (char *)malloc (strlen (ocptr) + 1);
+ strcpy (uptr->filename, ocptr);
+ uptr->tape_eom = tape->record_count;
+ }
+ else {
+ r = SCPE_ARG;
+ memory_free_tape (uptr->fileref);
+ uptr->fileref = NULL;
+ cptr = ocptr;
+ }
+ }
+ break;
+
+ case MTUF_F_TAR:
+ if (uptr->recsize == 0)
+ uptr->recsize = TAR_DFLT_RECSIZE; /* Apply default block size */
+ /* fall through */
+ default:
+ r = attach_unit (uptr, (CONST char *)cptr); /* attach unit */
+ break;
+ }
+if (r != SCPE_OK) { /* error? */
+ switch (MT_GET_FMT (uptr)) {
+ case MTUF_F_ANSI:
+ case MTUF_F_TAR:
+ case MTUF_F_FIXED:
+ case MTUF_F_DOS11:
+ r = sim_messagef (r, "Error opening %s format internal tape image generated from: %s\n", _sim_tape_format_name (uptr), cptr);
+ break;
+ default:
+ r = sim_messagef (r, "Error opening %s format tape image: %s - %s\n", _sim_tape_format_name (uptr), cptr, strerror(errno));
+ break;
+ }
+ if (auto_format) /* format was specified at attach time? */
+ sim_tape_set_fmt (uptr, 0, "SIMH", NULL); /* restore default format */
+ uptr->recsize = 0;
+ uptr->tape_eom = 0;
+ return r;
+ }
if ((sim_switches & SWMASK ('D')) && !had_debug) {
sim_switches |= SWMASK ('E');
sim_switches &= ~(SWMASK ('D') | SWMASK ('R') | SWMASK ('F'));
sim_set_debon (0, "STDOUT");
@@ -561,10 +947,12 @@
if ((sim_switches & SWMASK ('D')) && !had_debug)
sim_set_deboff (0, "");
if (sim_switches & SWMASK ('D'))
uptr->dctrl = starting_dctrl;
+if ((r == SCPE_OK) && (sim_switches & SWMASK ('X')))
+ r = sim_export_tape (uptr, export_file);
return r;
}
/* Detach tape unit */
@@ -588,12 +976,19 @@
if (ctx)
auto_format = ctx->auto_format;
sim_tape_clr_async (uptr);
-MT_CLR_INMRK (uptr); /* Not within an AWS or TAR tapemark */
-r = detach_unit (uptr); /* detach unit */
+MT_CLR_INMRK (uptr); /* Not within a TAR tapemark */
+if (MT_GET_FMT (uptr) >= MTUF_F_ANSI) {
+ memory_free_tape ((void *)uptr->fileref);
+ uptr->fileref = NULL;
+ uptr->flags &= ~UNIT_ATT;
+ r = SCPE_OK;
+ }
+else
+ r = detach_unit (uptr); /* detach unit */
if (r != SCPE_OK)
return r;
switch (f) { /* case on format */
case MTUF_F_TPC: /* TPC */
@@ -606,12 +1001,13 @@
break;
}
uptr->hwmark = 0;
uptr->recsize = 0;
uptr->tape_eom = 0;
-
-sim_tape_rewind (uptr);
+uptr->pos = 0;
+MT_CLR_PNU (uptr);
+MT_CLR_INMRK (uptr); /* Not within a TAR tapemark */
free (uptr->tape_ctx);
uptr->tape_ctx = NULL;
uptr->io_flush = NULL;
if (auto_format) /* format was determined or specified at attach time? */
sim_tape_set_fmt (uptr, 0, "SIMH", NULL); /* restore default format */
@@ -634,25 +1030,57 @@
}
else
fprintf (st, " sim> ATTACH {switches} %s tapefile\n\n", dptr->name);
fprintf (st, "Attach command switches\n");
fprintf (st, " -R Attach Read Only.\n");
-fprintf (st, " -E Must Exist (if not specified an attempt to create the indicated\n");
-fprintf (st, " virtual tape will be attempted).\n");
-fprintf (st, " -F Open the indicated tape container in a specific format (default\n");
-fprintf (st, " is SIMH, alternatives are E11, TPC, P7B, AWS and TAR)\n");
+fprintf (st, " -E Must Exist (if not specified, the default behavior is to\n");
+fprintf (st, " attempt to create the indicated virtual tape file).\n");
+fprintf (st, " -F Open the indicated tape container in a specific format\n");
+fprintf (st, " (default is SIMH, alternatives are E11, TPC, P7B, AWS, TAR,\n");
+fprintf (st, " ANSI-VMS, ANSI-RT11, ANSI-RSX11, ANSI-RSTS, ANSI-VAR, FIXED,\n");
+fprintf (st, " DOS11)\n");
fprintf (st, " -B For TAR format tapes, the record size for data read from the\n");
-fprintf (st, " specified file. This record size will be used for all but\n");
+fprintf (st, " specified file. This record size will be used for all but \n");
fprintf (st, " possibly the last record which will be what remains unread.\n");
-fprintf (st, " The default TAR record size is 10240.\n");
+fprintf (st, " The default TAR record size is 10240. For FIXED format tapes\n");
+fprintf (st, " -B specifies the record size for binary data or the maximum \n");
+fprintf (st, " record size for text data\n");
fprintf (st, " -V Display some summary information about the record structure\n");
-fprintf (st, " contained in the tape image scan performed when it is attached.\n");
+fprintf (st, " observed in the tape image observed during the attach\n");
+fprintf (st, " validation pass\n");
fprintf (st, " -L Display detailed record size counts observed during attach\n");
fprintf (st, " validation pass\n");
-fprintf (st, " contained in the tape image scan performed when it is attached.\n");
fprintf (st, " -D Causes the internal tape structure information to be displayed\n");
fprintf (st, " while the tape image is scanned.\n");
+fprintf (st, " -C Causes FIXED format tape data sets derived from text files to\n");
+fprintf (st, " be converted from ASCII to EBCDIC.\n");
+fprintf (st, " -X Extract a copy of the attached tape and convert it to a SIMH\n");
+fprintf (st, " format tape image.\n\n");
+fprintf (st, "Notes: ANSI-VMS, ANSI-RT11, ANSI-RSTS, ANSI-RSX11, ANSI-VAR formats allows\n");
+fprintf (st, " one or several files to be presented to as a read only ANSI Level 3\n");
+fprintf (st, " labeled tape with file labels that make each individual file\n");
+fprintf (st, " accessible directly as files on the tape.\n\n");
+fprintf (st, " FIXED format will present the contents of a file (text or binary) as\n");
+fprintf (st, " fixed sized records/blocks with ascii text data optionally converted\n");
+fprintf (st, " to EBCDIC.\n\n");
+fprintf (st, " DOS11 format will present the contents of a file preceeded by a DOS11\n");
+fprintf (st, " 14-byte header. All files will be owned by [1,1], have a default\n");
+fprintf (st, " protection of <233> and a date in the range 1972 - 1999 with the\n");
+fprintf (st, " month/day layout as the current year. The file name on the tape\n");
+fprintf (st, " will be sanitized to contain only alphanumeric characters from the\n");
+fprintf (st, " original source file name. Characters 7 - 9 of the file name will be\n");
+fprintf (st, " placed in an otherwise unused word in the header which some DEC\n");
+fprintf (st, " operating systems will be able to process. If the resulting\n");
+fprintf (st, " filename is NULL, a filename in the range 000000 - 999999 will be\n");
+fprintf (st, " generated based of the file position on the tape.\n\n");
+fprintf (st, "Examples:\n\n");
+fprintf (st, " sim> ATTACH %s -F ANSI-VMS Hobbyist-USE-ONLY-VA.TXT\n", dptr->name);
+fprintf (st, " sim> ATTACH %s -F ANSI-RSX11 *.TXT,*.ini,*.exe\n", dptr->name);
+fprintf (st, " sim> ATTACH %s -FX ANSI-RSTS RSTS.tap *.TXT,*.SAV\n", dptr->name);
+fprintf (st, " sim> ATTACH %s -F ANSI-RT11 *.TXT,*.TSK\n", dptr->name);
+fprintf (st, " sim> ATTACH %s -FB FIXED 80 SOMEFILE.TXT\n", dptr->name);
+fprintf (st, " sim> ATTACH %s -F DOS11 *.LDA,*.TXT\n\n", dptr->name);
return SCPE_OK;
}
static void sim_tape_data_trace(UNIT *uptr, const uint8 *data, size_t len, const char* txt, int detail, uint32 reason)
{
@@ -661,10 +1089,24 @@
if (ctx == NULL)
return;
if (sim_deb && ((uptr->dctrl | ctx->dptr->dctrl) & reason))
sim_data_trace(ctx->dptr, uptr, (detail ? data : NULL), "", len, txt, reason);
}
+
+static int sim_tape_seek (UNIT *uptr, t_addr pos)
+{
+if (MT_GET_FMT (uptr) < MTUF_F_ANSI)
+ return sim_fseek (uptr->fileref, pos, SEEK_SET);
+return 0;
+}
+
+static t_offset sim_tape_size (UNIT *uptr)
+{
+if (MT_GET_FMT (uptr) < MTUF_F_ANSI)
+ return sim_fsize_ex (uptr->fileref);
+return uptr->tape_eom;
+}
/* Read record length forward (internal routine).
Inputs:
uptr = pointer to tape unit
@@ -780,11 +1222,11 @@
(uptr->pos >= uptr->tape_eom)) {
MT_SET_PNU (uptr); /* then set position not updated */
return MTSE_EOM; /* and quit with I/O error status */
}
-if (sim_fseek (uptr->fileref, uptr->pos, SEEK_SET)) { /* set the initial tape position; if it fails */
+if (sim_tape_seek (uptr, uptr->pos)) { /* set the initial tape position; if it fails */
MT_SET_PNU (uptr); /* then set position not updated */
return sim_tape_ioerr (uptr); /* and quit with I/O error status */
}
switch (f) { /* otherwise the read method depends on the tape format */
@@ -876,12 +1318,12 @@
runaway_counter -= sizeof_gap; /* then decrement the gap counter */
else if (*bc == MTR_FHGAP) { /* otherwise if the value if a half gap */
uptr->pos = uptr->pos - sizeof (t_mtrlnt) / 2; /* then back up and resync */
- if (sim_fseek (uptr->fileref, uptr->pos, SEEK_SET)) { /* set the tape position; if it fails */
- status = sim_tape_ioerr (uptr); /* then quit with I/O error status */
+ if (sim_tape_seek (uptr, uptr->pos)) { /* set the tape position; if it fails */
+ status = sim_tape_ioerr (uptr); /* then quit with I/O error status */
break;
}
bufcntr = bufcap; /* mark the buffer as invalid to force a read */
@@ -902,11 +1344,11 @@
status = MTSE_RUNAWAY; /* then report it */
if (status == MTSE_OK) { /* Validate the reverse record size for data records */
t_mtrlnt rev_lnt;
- if (sim_fseek (uptr->fileref, uptr->pos - sizeof (t_mtrlnt), SEEK_SET)){ /* then seek to the end of record size; if it fails */
+ if (sim_tape_seek (uptr, uptr->pos - sizeof (t_mtrlnt))) { /* then seek to the end of record size; if it fails */
status = sim_tape_ioerr (uptr); /* then quit with I/O error status */
break;
}
(void)sim_fread (&rev_lnt, /* get the reverse length */
@@ -922,11 +1364,11 @@
status = MTSE_INVRL;
uptr->pos -= (sizeof (t_mtrlnt) + *bc + sizeof (t_mtrlnt));
MT_SET_PNU (uptr); /* pos not upd */
break;
}
- if (sim_fseek (uptr->fileref, saved_pos, SEEK_SET)) /* then seek back to the beginning of the data; if it fails */
+ if (sim_tape_seek (uptr, saved_pos)) /* then seek back to the beginning of the data; if it fails */
status = sim_tape_ioerr (uptr); /* then quit with I/O error status */
}
break; /* otherwise the operation succeeded */
case MTUF_F_TPC:
@@ -974,73 +1416,72 @@
all_eof = 0;
}
if (status == MTSE_OK) {
*bc = sbc; /* save rec lnt */
- (void)sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* for read */
+ (void)sim_tape_seek (uptr, uptr->pos); /* for read */
uptr->pos = uptr->pos + sbc; /* spc over record */
if (all_eof) { /* tape mark? */
status = MTSE_TMK;
*bc = 0;
}
}
break;
case MTUF_F_AWS:
- MT_CLR_INMRK (uptr); /* Not within an AWS tapemark */
+ saved_pos = (t_addr)sim_ftell (uptr->fileref);
memset (&awshdr, 0, sizeof (awshdr));
rdcnt = sim_fread (&awshdr, sizeof (t_awslnt), 3, uptr->fileref);
-
- if (ferror (uptr->fileref)) { /* error? */
- MT_SET_PNU (uptr); /* pos not upd */
+ if (ferror (uptr->fileref)) { /* error? */
+ MT_SET_PNU (uptr); /* pos not upd */
status = sim_tape_ioerr (uptr);
break;
}
- if ((feof (uptr->fileref)) || /* eof? */
+ if ((feof (uptr->fileref)) || /* eof? */
(rdcnt < 3)) {
- MT_SET_PNU (uptr); /* pos not upd */
+ uptr->tape_eom = uptr->pos;
+ MT_SET_PNU (uptr); /* pos not upd */
status = MTSE_EOM;
break;
}
- uptr->pos += sizeof (t_awshdr); /* spc over AWS header */
- if (awshdr.rectyp == AWS_TMK) { /* tape mark? */
- status = MTSE_TMK;
- MT_SET_INMRK (uptr); /* within an AWS tapemark */
- }
- else
- if (awshdr.rectyp != AWS_REC) { /* Unknown record type */
- MT_SET_PNU (uptr); /* pos not upd */
- status = MTSE_INVRL;
- }
- else { /* tape data record */
- t_addr saved_pos;
-
- *bc = (t_mtrlnt)awshdr.nxtlen; /* save rec lnt */
- uptr->pos += awshdr.nxtlen; /* spc over record */
- memset (&awshdr, 0, sizeof (t_awslnt));
- saved_pos = (t_addr)sim_ftell (uptr->fileref);
- (void)sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* for read */
- (void)sim_fread (&awshdr, sizeof (t_awslnt), 3, uptr->fileref);
- if (awshdr.rectyp == AWS_TMK)
- MT_SET_INMRK (uptr); /* within an AWS tapemark */
- if (awshdr.prelen != *bc) {
- status = MTSE_INVRL;
- uptr->pos = saved_pos - sizeof (t_awslnt);
- MT_CLR_INMRK (uptr); /* not within an AWS tapemark */
- }
- else
- (void)sim_fseek (uptr->fileref, saved_pos, SEEK_SET); /* Move back to the data */
- }
+ uptr->pos += sizeof (t_awshdr); /* spc over AWS header */
+ if (awshdr.rectyp == AWS_TMK) /* tape mark? */
+ status = MTSE_TMK;
+ else {
+ if (awshdr.rectyp != AWS_REC) { /* Unknown record type */
+ MT_SET_PNU (uptr); /* pos not upd */
+ uptr->tape_eom = uptr->pos;
+ status = MTSE_INVRL;
+ break;
+ }
+ else
+ status = MTSE_OK;
+ }
+ /* tape data record (or tapemark) */
+ *bc = (t_mtrlnt)awshdr.nxtlen; /* save rec lnt */
+ uptr->pos += awshdr.nxtlen; /* spc over record */
+ memset (&awshdr, 0, sizeof (t_awslnt));
+ saved_pos = (t_addr)sim_ftell (uptr->fileref);/* save record data address */
+ (void)sim_tape_seek (uptr, uptr->pos); /* for read */
+ rdcnt = sim_fread (&awshdr, sizeof (t_awslnt), 3, uptr->fileref);
+ if ((rdcnt == 3) &&
+ ((awshdr.prelen != *bc) || ((awshdr.rectyp != AWS_REC) && (awshdr.rectyp != AWS_TMK)))) {
+ status = MTSE_INVRL;
+ uptr->tape_eom = uptr->pos;
+ uptr->pos = saved_pos - sizeof (t_awslnt);
+ }
+ else
+ (void)sim_tape_seek (uptr, saved_pos); /* Move back to the data */
break;
case MTUF_F_TAR:
if (uptr->pos < uptr->hwmark) {
if ((uptr->hwmark - uptr->pos) >= uptr->recsize)
*bc = (t_mtrlnt)uptr->recsize; /* TAR record size */
else
*bc = (t_mtrlnt)(uptr->hwmark - uptr->pos); /* TAR remnant last record */
- (void)sim_fseek (uptr->fileref, uptr->pos, SEEK_SET);
+ (void)sim_tape_seek (uptr, uptr->pos);
uptr->pos += *bc;
MT_CLR_INMRK (uptr);
}
else {
if (MT_TST_INMRK (uptr))
@@ -1049,10 +1490,28 @@
status = MTSE_TMK;
MT_SET_INMRK (uptr);
}
}
break;
+
+ case MTUF_F_ANSI:
+ case MTUF_F_FIXED:
+ case MTUF_F_DOS11:
+ if (1) {
+ MEMORY_TAPE *tape = (MEMORY_TAPE *)uptr->fileref;
+
+ if (uptr->pos >= tape->record_count)
+ status = MTSE_EOM;
+ else {
+ if (tape->records[uptr->pos]->size == 0)
+ status = MTSE_TMK;
+ else
+ *bc = tape->records[uptr->pos]->size;
+ ++uptr->pos;
+ }
+ }
+ break;
default:
status = MTSE_FMT;
}
@@ -1132,13 +1591,13 @@
if ((uptr->flags & UNIT_ATT) == 0) /* if the unit is not attached */
return MTSE_UNATT; /* then quit with an error */
if (sim_tape_bot (uptr)) /* if the unit is positioned at the BOT */
- status = MTSE_BOT; /* then reading backward is not possible */
+ return MTSE_BOT; /* then reading backward is not possible */
-else switch (f) { /* otherwise the read method depends on the tape format */
+switch (f) { /* otherwise the read method depends on the tape format */
case MTUF_F_STD:
case MTUF_F_E11:
runaway_counter = 25 * 12 * bpi [MT_DENS (uptr->dynflags)]; /* set the largest legal gap size in bytes */
@@ -1169,14 +1628,14 @@
else /* otherwise reset the capacity */
bufcap = sizeof (buffer) /* to the full size of the buffer */
/ sizeof (buffer [0]);
- if (sim_fseek (uptr->fileref, /* seek back to the location */
- uptr->pos - bufcap * sizeof (t_mtrlnt), /* corresponding to the start */
- SEEK_SET)) { /* of the buffer; if it fails */
- status = sim_tape_ioerr (uptr); /* and fail with I/O error status */
+ if (sim_tape_seek (uptr, /* seek back to the location */
+ uptr->pos - bufcap * sizeof (t_mtrlnt))) { /* corresponding to the start */
+ /* of the buffer; if it fails */
+ status = sim_tape_ioerr (uptr); /* and fail with I/O error status */
break;
}
bufcntr = sim_fread (buffer, sizeof (t_mtrlnt), /* fill the buffer */
bufcap, uptr->fileref); /* with tape metadata */
@@ -1197,27 +1656,26 @@
}
else if (*bc == MTR_GAP) /* otherwise if the marker is a full gap */
runaway_counter -= sizeof_gap; /* then decrement the gap counter */
- else if ((*bc & MTR_M_RHGAP) == MTR_RHGAP /* otherwise if the marker */
- || *bc == MTR_RRGAP) { /* is a half gap */
- uptr->pos = uptr->pos + sizeof (t_mtrlnt) / 2; /* then position forward to resync */
- bufcntr = 0; /* mark the buffer as invalid to force a read */
-
- *bc = (t_mtrlnt)MTR_GAP; /* reset the marker */
- runaway_counter -= sizeof_gap / 2; /* and decrement the gap counter */
- }
-
- else { /* otherwise it's a record marker */
- sbc = MTR_L (*bc); /* extract the record length */
- uptr->pos = uptr->pos - sizeof (t_mtrlnt) /* position to the start */
- - (f == MTUF_F_STD ? (sbc + 1) & ~1 : sbc); /* of the record */
-
- if (sim_fseek (uptr->fileref, /* seek to the start of the data area; if it fails */
- uptr->pos + sizeof (t_mtrlnt), /* then return with I/O error status */
- SEEK_SET)) {
+ else if ((*bc & MTR_M_RHGAP) == MTR_RHGAP /* otherwise if the marker */
+ || *bc == MTR_RRGAP) { /* is a half gap */
+ uptr->pos = uptr->pos + sizeof (t_mtrlnt) / 2;/* then position forward to resync */
+ bufcntr = 0; /* mark the buffer as invalid to force a read */
+
+ *bc = (t_mtrlnt)MTR_GAP; /* reset the marker */
+ runaway_counter -= sizeof_gap / 2; /* and decrement the gap counter */
+ }
+
+ else { /* otherwise it's a record marker */
+ sbc = MTR_L (*bc); /* extract the record length */
+ uptr->pos = uptr->pos - sizeof (t_mtrlnt)/* position to the start */
+ - (f == MTUF_F_STD ? (sbc + 1) & ~1 : sbc);/* of the record */
+
+ if (sim_tape_seek (uptr, /* seek to the start of the data area; if it fails */
+ uptr->pos + sizeof (t_mtrlnt))) {/* then return with I/O error status */
status = sim_tape_ioerr (uptr);
break;
}
}
}
@@ -1228,11 +1686,11 @@
break; /* otherwise the operation succeeded */
case MTUF_F_TPC:
ppos = sim_tape_tpc_fnd (uptr, (t_addr *) uptr->filebuf); /* find prev rec */
- (void)sim_fseek (uptr->fileref, ppos, SEEK_SET);/* position */
+ (void)sim_tape_seek (uptr, ppos); /* position */
(void)sim_fread (&tpcbc, sizeof (t_tpclnt), 1, uptr->fileref);
*bc = (t_mtrlnt)tpcbc; /* save rec lnt */
if (ferror (uptr->fileref)) /* error? */
status = sim_tape_ioerr (uptr);
@@ -1241,39 +1699,39 @@
else {
uptr->pos = ppos; /* spc over record */
if (*bc == MTR_TMK) /* tape mark? */
status = MTSE_TMK;
else
- (void)sim_fseek (uptr->fileref, uptr->pos + sizeof (t_tpclnt), SEEK_SET);
+ (void)sim_tape_seek (uptr, uptr->pos + sizeof (t_tpclnt));
}
break;
case MTUF_F_P7B:
if (1) {
#define BUF_SZ 512
uint8 buf[BUF_SZ];
- t_addr buf_offset;
+ t_addr buf_offset = uptr->pos;
size_t bytes_in_buf = 0;
size_t read_size;
for (sbc = 1, all_eof = 1; (t_addr) sbc <= uptr->pos ; sbc++) {
- if (bytes_in_buf == 0) { /* Need to Fill Buffer */
- if (uptr->pos < BUF_SZ) {
+ if (bytes_in_buf == 0) { /* Need to Fill Buffer */
+ if (buf_offset < BUF_SZ) {
+ read_size = (size_t)buf_offset;
buf_offset = 0;
- read_size = (size_t)uptr->pos;
}
else {
- buf_offset = uptr->pos - (sbc - 1 + BUF_SZ);
read_size = BUF_SZ;
+ buf_offset -= BUF_SZ;
}
- (void)sim_fseek (uptr->fileref, buf_offset, SEEK_SET);
+ (void)sim_tape_seek (uptr, buf_offset);
bytes_in_buf = sim_fread (buf, sizeof (uint8), read_size, uptr->fileref);
- if (ferror (uptr->fileref)) { /* error? */
+ if (ferror (uptr->fileref)) { /* error? */
status = sim_tape_ioerr (uptr);
break;
}
- if (feof (uptr->fileref)) { /* eof? */
+ if (feof (uptr->fileref)) { /* eof? */
status = MTSE_EOM;
break;
}
}
c = buf[--bytes_in_buf];
@@ -1282,74 +1740,73 @@
if (c & P7B_SOR) /* start of record? */
break;
}
if (status == MTSE_OK) {
- uptr->pos = uptr->pos - sbc; /* update position */
- *bc = sbc; /* save rec lnt */
- (void)sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* for next read */
- if (all_eof) /* tape mark? */
+ uptr->pos = uptr->pos - sbc; /* update position */
+ *bc = sbc; /* save rec lnt */
+ (void)sim_tape_seek (uptr, uptr->pos); /* for next read */
+ if (all_eof) /* tape mark? */
status = MTSE_TMK;
}
break;
}
case MTUF_F_AWS:
- *bc = sbc = 0;
- while ((sbc == 0) && (status == MTSE_OK)) {
- if (sim_tape_bot (uptr)) { /* if we start at BOT */
- status = MTSE_BOT; /* then we're done */
+ *bc = 0;
+ status = MTSE_OK;
+ (void)sim_tape_seek (uptr, uptr->pos); /* position */
+ while (1) {
+ if (sim_tape_bot (uptr)) { /* if we start at BOT */
+ status = MTSE_BOT; /* then we're done */
break;
}
- (void)sim_fseek (uptr->fileref, uptr->pos, SEEK_SET);/* position */
memset (&awshdr, 0, sizeof (awshdr));
rdcnt = sim_fread (&awshdr, sizeof (t_awslnt), 3, uptr->fileref);
- if (ferror (uptr->fileref)) { /* error? */
+ if (ferror (uptr->fileref)) { /* error? */
status = sim_tape_ioerr (uptr);
break;
}
if (feof (uptr->fileref)) { /* eof? */
if ((uptr->pos > sizeof (t_awshdr)) &&
(uptr->pos >= sim_fsize (uptr->fileref))) {
- if (MT_TST_INMRK (uptr)) {
- status = MTSE_TMK;
- MT_CLR_INMRK (uptr);
- uptr->pos -= sizeof (awshdr);
- }
- else
- status = MTSE_EOM; /* then we're done */
- break;
- }
+ uptr->tape_eom = uptr->pos;
+ (void)sim_tape_seek (uptr, uptr->pos - sizeof (t_awshdr));/* position */
+ continue;
+ }
+ status = MTSE_EOM;
+ break;
}
if ((rdcnt != 3) ||
((awshdr.rectyp != AWS_REC) &&
(awshdr.rectyp != AWS_TMK))) {
status = MTSE_INVRL;
- break;
- }
- if (MT_TST_INMRK (uptr)) /* already in a tapemark? */
- awshdr.rectyp = AWS_REC;
- MT_CLR_INMRK (uptr); /* No longer in a tapemark */
- *bc = (t_mtrlnt)((awshdr.rectyp == AWS_REC) ? awshdr.prelen : 0);/* save rec lnt */
- sbc = *bc; /* extract the record length */
- if ((awshdr.rectyp != AWS_TMK) ||
- (awshdr.prelen == 0)) {
- uptr->pos -= sizeof (t_awshdr); /* position to the start of the record */
- uptr->pos -= awshdr.prelen; /* Including the data length */
-
- if (sim_fseek (uptr->fileref, /* seek to the start of the data area; if it fails */
- uptr->pos + sizeof (t_awshdr),
- SEEK_SET)) {
- status = sim_tape_ioerr (uptr); /* then return with I/O error status */
- break;
- }
- }
- if (awshdr.rectyp == AWS_TMK) { /* tape mark? */
+ }
+ break;
+ }
+ if (status != MTSE_OK)
+ break;
+ if (awshdr.prelen == 0)
+ status = MTSE_TMK;
+ else {
+ if ((uptr->tape_eom > 0) &&
+ (uptr->pos >= uptr->tape_eom) &&
+ (awshdr.rectyp == AWS_TMK)) {
status = MTSE_TMK;
- if (awshdr.prelen != 0)
- MT_SET_INMRK (uptr); /* Flag processing a tapemark header */
+ *bc = 0; /* save rec lnt */
+ }
+ else {
+ status = MTSE_OK;
+ *bc = (t_mtrlnt)awshdr.prelen; /* save rec lnt */
}
+ }
+ uptr->pos -= sizeof (t_awshdr); /* position to the start of the record */
+ uptr->pos -= *bc; /* Including the data length */
+ if (sim_tape_seek (uptr, /* seek to the start of the data area; if it fails */
+ uptr->pos + sizeof (t_awshdr))) {
+ status = sim_tape_ioerr (uptr); /* then return with I/O error status */
+ break;
}
break;
case MTUF_F_TAR:
if (uptr->pos == uptr->hwmark) {
@@ -1366,13 +1823,27 @@
}
else
*bc = (t_mtrlnt)uptr->recsize;
if (*bc) {
uptr->pos -= *bc;
- (void)sim_fseek (uptr->fileref, uptr->pos, SEEK_SET);
+ (void)sim_tape_seek (uptr, uptr->pos);
}
break;
+
+ case MTUF_F_ANSI:
+ case MTUF_F_FIXED:
+ case MTUF_F_DOS11:
+ if (1) {
+ MEMORY_TAPE *tape = (MEMORY_TAPE *)uptr->fileref;
+
+ --uptr->pos;
+ if (tape->records[uptr->pos]->size == 0)
+ status = MTSE_TMK;
+ else
+ *bc = tape->records[uptr->pos]->size;
+ }
+ break;
default:
status = MTSE_FMT;
}
@@ -1437,15 +1908,23 @@
if (rbc > max) { /* rec out of range? */
MT_SET_PNU (uptr);
uptr->pos = opos;
return MTSE_INVRL;
}
-i = (t_mtrlnt) sim_fread (buf, sizeof (uint8), rbc, uptr->fileref); /* read record */
-if (ferror (uptr->fileref)) { /* error? */
- MT_SET_PNU (uptr);
- uptr->pos = opos;
- return sim_tape_ioerr (uptr);
+if (f < MTUF_F_ANSI) {
+ i = (t_mtrlnt) sim_fread (buf, sizeof (uint8), rbc, uptr->fileref); /* read record */
+ if (ferror (uptr->fileref)) { /* error? */
+ MT_SET_PNU (uptr);
+ uptr->pos = opos;
+ return sim_tape_ioerr (uptr);
+ }
+ }
+else {
+ MEMORY_TAPE *tape = (MEMORY_TAPE *)uptr->fileref;
+
+ memcpy (buf, tape->records[uptr->pos - 1]->data, rbc);
+ i = rbc;
}
for ( ; i < rbc; i++) /* fill with 0's */
buf[i] = 0;
if (f == MTUF_F_P7B) /* p7b? strip SOR */
buf[0] = buf[0] & P7B_DPAR;
@@ -1502,13 +1981,21 @@
return st;
}
*bc = rbc = MTR_L (tbc); /* strip error flag */
if (rbc > max) /* rec out of range? */
return MTSE_INVRL;
-i = (t_mtrlnt) sim_fread (buf, sizeof (uint8), rbc, uptr->fileref); /* read record */
-if (ferror (uptr->fileref)) /* error? */
- return sim_tape_ioerr (uptr);
+if (f < MTUF_F_ANSI) {
+ i = (t_mtrlnt) sim_fread (buf, sizeof (uint8), rbc, uptr->fileref); /* read record */
+ if (ferror (uptr->fileref)) /* error? */
+ return sim_tape_ioerr (uptr);
+ }
+else {
+ MEMORY_TAPE *tape = (MEMORY_TAPE *)uptr->fileref;
+
+ memcpy (buf, tape->records[uptr->pos]->data, rbc);
+ i = rbc;
+ }
for ( ; i < rbc; i++) /* fill with 0's */
buf[i] = 0;
if (f == MTUF_F_P7B) /* p7b? strip SOR */
buf[0] = buf[0] & P7B_DPAR;
sim_tape_data_trace(uptr, buf, rbc, "Record Read Reverse", (uptr->dctrl | ctx->dptr->dctrl) & MTSE_DBG_DAT, MTSE_DBG_STR);
@@ -1559,11 +2046,11 @@
return MTSE_UNATT;
if (sim_tape_wrp (uptr)) /* write prot? */
return MTSE_WRP;
if (sbc == 0) /* nothing to do? */
return MTSE_OK;
-if (sim_fseek (uptr->fileref, uptr->pos, SEEK_SET)) /* set pos */
+if (sim_tape_seek (uptr, uptr->pos)) /* set pos */
return MTSE_IOERR;
switch (f) { /* case on format */
case MTUF_F_STD: /* standard */
sbc = MTR_L ((bc + 1) & ~1); /* pad odd length */
@@ -1617,11 +2104,11 @@
t_awshdr awshdr;
size_t rdcnt;
t_bool replacing_record;
memset (&awshdr, 0, sizeof (t_awshdr));
-if (sim_fseek (uptr->fileref, uptr->pos, SEEK_SET)) /* set pos */
+if (sim_tape_seek (uptr, uptr->pos)) /* set pos */
return MTSE_IOERR;
rdcnt = sim_fread (&awshdr, sizeof (t_awslnt), 3, uptr->fileref);
if (ferror (uptr->fileref)) { /* error? */
MT_SET_PNU (uptr); /* pos not upd */
return sim_tape_ioerr (uptr);
@@ -1630,11 +2117,11 @@
(((feof (uptr->fileref)) && (rdcnt < 3)) || /* eof? */
((awshdr.rectyp != AWS_REC) && (awshdr.rectyp != AWS_TMK)))) {
MT_SET_PNU (uptr); /* pos not upd */
return MTSE_INVRL;
}
-if (sim_fseek (uptr->fileref, uptr->pos, SEEK_SET)) /* set pos */
+if (sim_tape_seek (uptr, uptr->pos)) /* set pos */
return MTSE_IOERR;
replacing_record = (awshdr.nxtlen == (t_awslnt)bc) && (awshdr.rectyp == (bc ? AWS_REC : AWS_TMK));
awshdr.nxtlen = (t_awslnt)bc;
awshdr.rectyp = (bc) ? AWS_REC : AWS_TMK;
(void)sim_fwrite (&awshdr, sizeof (t_awslnt), 3, uptr->fileref);
@@ -1665,11 +2152,11 @@
return MTSE_UNATT;
if (ctx == NULL) /* if not properly attached? */
return sim_messagef (SCPE_IERR, "Bad Attach\n"); /* that's a problem */
if (sim_tape_wrp (uptr)) /* write prot? */
return MTSE_WRP;
-(void)sim_fseek (uptr->fileref, uptr->pos, SEEK_SET); /* set pos */
+(void)sim_tape_seek (uptr, uptr->pos); /* set pos */
(void)sim_fwrite (&dat, sizeof (t_mtrlnt), 1, uptr->fileref);
if (ferror (uptr->fileref)) { /* error? */
MT_SET_PNU (uptr);
return sim_tape_ioerr (uptr);
}
@@ -1887,11 +2374,11 @@
else if (gap_size == 0 || format != MTUF_F_STD) /* otherwise if zero length or gaps aren't supported */
return MTSE_OK; /* then take no action */
file_size = sim_fsize (uptr->fileref); /* get the file size */
-if (sim_fseek (uptr->fileref, uptr->pos, SEEK_SET)) { /* position the tape; if it fails */
+if (sim_tape_seek (uptr, uptr->pos)) { /* position the tape; if it fails */
MT_SET_PNU (uptr); /* then set position not updated */
return sim_tape_ioerr (uptr); /* and quit with I/O error status */
}
/* Read tape records and allocate them to the gap until the amount required is
@@ -1941,12 +2428,12 @@
}
else if (meta == MTR_FHGAP) { /* half gap? */
uptr->pos = uptr->pos - meta_size / 2; /* backup to resync */
- if (sim_fseek (uptr->fileref, uptr->pos, SEEK_SET)) /* position the tape; if it fails */
- return sim_tape_ioerr (uptr); /* then quit with I/O error status */
+ if (sim_tape_seek (uptr, uptr->pos)) /* position the tape; if it fails */
+ return sim_tape_ioerr (uptr); /* then quit with I/O error status */
gap_alloc = gap_alloc + meta_size / 2; /* allocate marker space */
gap_needed = gap_needed - meta_size / 2; /* reduce requirement */
}
@@ -1967,12 +2454,12 @@
rec_size = ((sbc + 1) & ~1) + meta_size * 2; /* overall size in bytes */
if (rec_size < gap_needed + min_rec_size) { /* rec too small? */
uptr->pos = uptr->pos - meta_size + rec_size; /* position past record */
- if (sim_fseek (uptr->fileref, uptr->pos, SEEK_SET)) /* position the tape; if it fails */
- return sim_tape_ioerr (uptr); /* then quit with I/O error status */
+ if (sim_tape_seek (uptr, uptr->pos)) /* position the tape; if it fails */
+ return sim_tape_ioerr (uptr); /* then quit with I/O error status */
gap_alloc = gap_alloc + rec_size; /* allocate record */
gap_needed = gap_needed - rec_size; /* reduce requirement */
}
@@ -2082,11 +2569,11 @@
return MTSE_UNATT; /* then we cannot proceed */
else if (sim_tape_wrp (uptr)) /* otherwise if the unit is write protected */
return MTSE_WRP; /* then we cannot write */
-else if (gap_size == 0 || format != MTUF_F_STD) /* otherwise if the gap length is zero or unsupported */
+else if ((gap_size == 0) || (format != MTUF_F_STD)) /* otherwise if the gap length is zero or unsupported */
return MTSE_OK; /* then take no action */
gap_pos = uptr->pos; /* save the starting position */
if (gap_size == meta_size) { /* if the request is for a single metadatum */
@@ -2093,29 +2580,29 @@
if (sim_tape_bot (uptr)) /* then if the unit is positioned at the BOT */
return MTSE_BOT; /* then erasing backward is not possible */
else /* otherwise */
uptr->pos -= meta_size; /* back up the file pointer */
- if (sim_fseek (uptr->fileref, uptr->pos, SEEK_SET)) /* position the tape; if it fails */
+ if (sim_tape_seek (uptr, uptr->pos)) /* position the tape; if it fails */
return sim_tape_ioerr (uptr); /* then quit with I/O error status */
(void)sim_fread (&metadatum, meta_size, 1, uptr->fileref);/* read a metadatum */
if (ferror (uptr->fileref)) /* if a file I/O error occurred */
return sim_tape_ioerr (uptr); /* then report the error and quit */
else if (metadatum == MTR_TMK) /* otherwise if a tape mark is present */
- if (sim_fseek (uptr->fileref, uptr->pos, SEEK_SET)) /* then reposition the tape; if it fails */
+ if (sim_tape_seek (uptr, uptr->pos)) /* then reposition the tape; if it fails */
return sim_tape_ioerr (uptr); /* then quit with I/O error status */
else { /* otherwise */
metadatum = MTR_GAP; /* replace it with an erase gap marker */
xfer = sim_fwrite (&metadatum, meta_size, /* write the gap marker */
1, uptr->fileref);
- if (ferror (uptr->fileref) || xfer == 0) /* if a file I/O error occurred */
+ if (ferror (uptr->fileref) || (xfer == 0)) /* if a file I/O error occurred */
return sim_tape_ioerr (uptr); /* report the error and quit */
else /* otherwise the write succeeded */
status = MTSE_OK; /* so return success */
}
@@ -2129,12 +2616,12 @@
}
else { /* otherwise it's an erase record request */
status = sim_tape_rdlntr (uptr, &rec_size); /* so get the length of the preceding record */
- if (status == MTSE_OK /* if the read succeeded */
- && gap_size == rec_size + 2 * meta_size) { /* and the gap will exactly overlay the record */
+ if ((status == MTSE_OK) && /* if the read succeeded */
+ (gap_size == rec_size + 2 * meta_size)) { /* and the gap will exactly overlay the record */
gap_pos = uptr->pos; /* then save the gap start position */
status = tape_erase_fwd (uptr, gap_size); /* erase the record */
if (status == MTSE_OK) /* if the gap write succeeded */
@@ -2663,14 +3150,15 @@
if (ctx == NULL) /* if not properly attached? */
return sim_messagef (SCPE_IERR, "Bad Attach\n");/* that's a problem */
sim_debug_unit (ctx->dbit, uptr, "sim_tape_rewind(unit=%d)\n", (int)(uptr-ctx->dptr->units));
}
uptr->pos = 0;
-if (uptr->flags & UNIT_ATT)
- (void)sim_fseek (uptr->fileref, uptr->pos, SEEK_SET);
+if (uptr->flags & UNIT_ATT) {
+ (void)sim_tape_seek (uptr, uptr->pos);
+ }
MT_CLR_PNU (uptr);
-MT_CLR_INMRK (uptr); /* Not within an AWS or TAR tapemark */
+MT_CLR_INMRK (uptr); /* Not within a TAR tapemark */
return MTSE_OK;
}
t_stat sim_tape_rewind_a (UNIT *uptr, TAPE_PCALLBACK callback)
{
@@ -2768,11 +3256,11 @@
t_bool sim_tape_bot (UNIT *uptr)
{
uint32 f = MT_GET_FMT (uptr);
-return (uptr->pos <= fmts[f].bot)? TRUE: FALSE;
+return ((uptr->pos <= fmts[f].bot) && (!MT_TST_INMRK (uptr))) ? TRUE: FALSE;
}
/* Test for end of tape */
t_bool sim_tape_eot (UNIT *uptr)
@@ -2806,29 +3294,38 @@
return SCPE_IERR;
if (uptr->flags & UNIT_ATT)
return SCPE_ALATT;
if (cptr == NULL)
return SCPE_ARG;
-for (f = 0; f < MTUF_N_FMT; f++) {
- if (fmts[f].name && (strcmp (cptr, fmts[f].name) == 0)) {
- uptr->flags = (uptr->flags & ~MTUF_FMT) |
- (f << MTUF_V_FMT) | fmts[f].uflags;
+for (f = 0; fmts[f].name; f++) {
+ if (MATCH_CMD(fmts[f].name, cptr) == 0) {
+ uint32 a = 0;
+
+ if (f == MTUF_F_ANSI) {
+ for (a = 0; ansi_args[a].name; a++)
+ if (MATCH_CMD(ansi_args[a].name, cptr) == 0)
+ break;
+ if (ansi_args[a].name == NULL)
+ return sim_messagef (SCPE_ARG, "Unknown ANSI tape format: %s\n", cptr);
+ }
+ uptr->flags &= ~UNIT_RO;
+ uptr->flags |= fmts[f].uflags;
+ uptr->dynflags &= ~UNIT_M_TAPE_FMT;
+ uptr->dynflags |= (f << UNIT_V_TAPE_FMT);
+ uptr->dynflags &= ~UNIT_M_TAPE_ANSI;
+ uptr->dynflags |= (a << UNIT_V_TAPE_ANSI);
return SCPE_OK;
}
}
-return SCPE_ARG;
+return sim_messagef (SCPE_ARG, "Unknown tape format: %s\n", cptr);
}
/* Show tape format */
t_stat sim_tape_show_fmt (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
{
-int32 f = MT_GET_FMT (uptr);
-
-if (fmts[f].name)
- fprintf (st, "%s format", fmts[f].name);
-else fprintf (st, "invalid format");
+fprintf (st, "%s format", _sim_tape_format_name (uptr));
return SCPE_OK;
}
/* Map a TPC format tape image */
@@ -2849,13 +3346,15 @@
countmap = (uint32 *)calloc (65536, sizeof(*countmap));
recbuf = (uint8 *)malloc (65536);
tape_size = (t_addr)sim_fsize (uptr->fileref);
sim_debug_unit (MTSE_DBG_STR, uptr, "tpc_map: tape_size: %" T_ADDR_FMT "u\n", tape_size);
for (objc = 0, sizec = 0, tpos = 0;; ) {
- (void)sim_fseek (uptr->fileref, tpos, SEEK_SET);
- i = sim_fread (&bc, sizeof (t_tpclnt), 1, uptr->fileref);
+ (void)sim_tape_seek (uptr, tpos);
+ i = sim_fread (&bc, sizeof (bc), 1, uptr->fileref);
if (i == 0) /* past or at eof? */
+ break;
+ if (bc > 65535) /* Range check length value to satisfy Coverity */
break;
if (countmap[bc] == 0)
sizec++;
++countmap[bc];
if (map && (objc < mapsize))
@@ -2938,10 +3437,14 @@
if (stat <= MTSE_MAX_ERR)
return mtse_errors[stat];
sprintf(msgbuf, "Error %d", stat);
return msgbuf;
}
+
+#ifndef MAX
+#define MAX(a,b) (((a) > (b)) ? (a) : (b))
+#endif
static t_stat sim_tape_validate_tape (UNIT *uptr)
{
t_addr saved_pos = uptr->pos;
uint32 record_in_file = 0;
@@ -2948,10 +3451,12 @@
uint32 data_total = 0;
uint32 tapemark_total = 0;
uint32 record_total = 0;
uint32 unique_record_sizes = 0;
uint32 remaining_data = 0;
+uint32 gaps = 0;
+uint32 gap_bytes = 0;
uint32 *rec_sizes = NULL;
t_stat r = SCPE_OK;
t_stat r_f;
t_stat r_r;
t_stat r_s;
@@ -2961,10 +3466,12 @@
t_mtrlnt bc_r;
t_mtrlnt bc_s;
t_mtrlnt bc;
t_addr pos_f;
t_addr pos_r;
+t_addr pos_fa;
+t_addr pos_sa;
t_mtrlnt max = MTR_MAXLEN;
if (!(uptr->flags & UNIT_ATT))
return SCPE_UNATT;
buf_f = (uint8 *)calloc (1, max);
@@ -2979,16 +3486,20 @@
if (rec_sizes == NULL) {
free (buf_f);
free (buf_r);
return SCPE_MEM;
}
+
r = sim_tape_rewind (uptr);
while (r == SCPE_OK) {
- if (stop_cpu) /* SIGINT? */
+ if (stop_cpu) { /* SIGINT? */
+ stop_cpu = FALSE;
break;
+ }
pos_f = uptr->pos;
r_f = sim_tape_rdrecf (uptr, buf_f, &bc_f, max);
+ pos_fa = uptr->pos;
switch (r_f) {
case MTSE_OK: /* no error */
case MTSE_TMK: /* tape mark */
if (r_f == MTSE_OK)
++record_total;
@@ -3002,33 +3513,50 @@
}
r_r = sim_tape_rdrecr (uptr, buf_r, &bc_r, max);
pos_r = uptr->pos;
if (r_r != r_f) {
sim_printf ("Forward Record Read returned: %s, Reverse read returned: %s\n", sim_tape_error_text (r_f), sim_tape_error_text (r_r));
+ r = MAX(r_f, r_r);
break;
}
if (bc_f != bc_r) {
sim_printf ("Forward Record Read record length: %d, Reverse read record length: %d\n", bc_f, bc_r);
+ r = MTSE_RECE;
break;
}
if (0 != memcmp (buf_f, buf_r, bc_f)) {
sim_printf ("%d byte record contents differ when read forward amd backwards start from position %" T_ADDR_FMT "u\n", bc_f, pos_f);
+ r = MTSE_RECE;
break;
}
memset (buf_f, 0, bc_f);
- memset (buf_r, 0, bc_f);
+ memset (buf_r, 0, bc_r);
if (pos_f != pos_r) {
- sim_printf ("Unexpected tape file position between forward and reverse record read: (%" T_ADDR_FMT "u, %" T_ADDR_FMT "u\n", pos_f, pos_r);
- break;
+ if (MT_GET_FMT (uptr) == MTUF_F_STD) {
+ ++gaps;
+ gap_bytes += (uint32)(pos_r - pos_f);
+ }
+ else {
+ sim_printf ("Unexpected tape file position between forward and reverse record read: (%" T_ADDR_FMT "u, %" T_ADDR_FMT "u)\n", pos_f, pos_r);
+ r = MTSE_RECE;
+ break;
+ }
}
r_s = sim_tape_sprecf (uptr, &bc_s);
+ pos_sa = uptr->pos;
if (r_s != r_f) {
sim_printf ("Unexpected Space Record Status: %s vs %s\n", sim_tape_error_text (r_s), sim_tape_error_text (r_f));
+ r = MAX(r_s, r_f);
break;
}
if (bc_s != bc_f) {
sim_printf ("Unexpected Space Record Length: %d vs %d\n", bc_s, bc_f);
+ r = MTSE_RECE;
+ break;
+ }
+ if (pos_fa != pos_sa) {
+ sim_printf ("Unexpected tape file position after forward and skip record: (%" T_ADDR_FMT "u, %" T_ADDR_FMT "u)\n", pos_fa, pos_sa);
break;
}
r = SCPE_OK;
break;
case MTSE_INVRL: /* invalid rec lnt */
@@ -3047,43 +3575,41 @@
}
}
uptr->tape_eom = uptr->pos;
if ((!stop_cpu) &&
((r != MTSE_EOM) || (sim_switches & SWMASK ('V')) || (sim_switches & SWMASK ('L')) ||
- ((uint32)(sim_fsize_ex (uptr->fileref) - (t_offset)uptr->pos) > fmts[MT_GET_FMT (uptr)].eom_remnant) ||
+ ((uint32)(sim_tape_size (uptr) - (t_offset)uptr->pos) > fmts[MT_GET_FMT (uptr)].eom_remnant) ||
(unique_record_sizes > 2 * tapemark_total))) {
- remaining_data = (uint32)(sim_fsize_ex (uptr->fileref) - (t_offset)uptr->tape_eom);
- sim_printf ("Tape Image '%s' scanned as %s format.\n", uptr->filename, fmts[MT_GET_FMT (uptr)].name);
- if (r != MTSE_EOM)
- sim_printf ("After processing ");
- else
- sim_printf ("contains ");
- sim_printf ("%u bytes of tape data (%u records, %u tapemarks)\n",
- data_total, record_total, tapemark_total);
+ remaining_data = (uint32)(sim_tape_size (uptr) - (t_offset)uptr->tape_eom);
+ sim_messagef (SCPE_OK, "Tape Image %s'%s' scanned as %s format.\n", ((MT_GET_FMT (uptr) == MTUF_F_ANSI) ? "made from " : ""), uptr->filename, (MT_GET_FMT (uptr) == MTUF_F_ANSI) ? ansi_args[MT_GET_ANSI_TYP (uptr)].name : fmts[MT_GET_FMT (uptr)].name);
+ sim_messagef (SCPE_OK, "%s %u bytes of tape data (%u records, %u tapemarks)\n",
+ (r != MTSE_EOM) ? "After processing" : "contains", data_total, record_total, tapemark_total);
if ((record_total > 0) && (sim_switches & SWMASK ('L'))) {
- sim_printf ("Comprising %d different sized records (in record size order):\n", unique_record_sizes);
+ sim_messagef (SCPE_OK, "Comprising %d different sized records (in record size order):\n", unique_record_sizes);
for (bc = 0; bc <= max; bc++) {
if (rec_sizes[bc])
- sim_printf ("%8u %u byte records\n", rec_sizes[bc], (uint32)bc);
+ sim_messagef (SCPE_OK, "%8u %u byte record%s\n", rec_sizes[bc], (uint32)bc, (rec_sizes[bc] != 1) ? "s" : "");
}
+ if (gaps)
+ sim_messagef (SCPE_OK, "%8u gap%s totalling %u bytes %s seen\n", gaps, (gaps != 1) ? "s" : "", gap_bytes, (gaps != 1) ? "were" : "was");
}
if (r != MTSE_EOM)
- sim_printf ("Read Tape Record Returned Unexpected Status: %s\n", sim_tape_error_text (r));
+ sim_messagef (SCPE_OK, "Read Tape Record Returned Unexpected Status: %s\n", sim_tape_error_text (r));
if (remaining_data > fmts[MT_GET_FMT (uptr)].eom_remnant)
- sim_printf ("%u bytes of unexamined data remain in the tape image file\n", remaining_data);
+ sim_messagef (SCPE_OK, "%u bytes of unexamined data remain in the tape image file\n", remaining_data);
}
if ((!stop_cpu) &&
(unique_record_sizes > 2 * tapemark_total)) {
- sim_printf ("An unreasonable number of record sizes(%u) vs tape marks (%u) have been found\n", unique_record_sizes, tapemark_total);
- sim_printf ("The tape format (%s) might not be correct for the '%s' tape image\n", fmts[MT_GET_FMT (uptr)].name, uptr->filename);
+ sim_messagef (SCPE_OK, "A potentially unreasonable number of record sizes(%u) vs tape marks (%u) have been found\n", unique_record_sizes, tapemark_total);
+ sim_messagef (SCPE_OK, "The tape format (%s) might not be correct for the '%s' tape image\n", fmts[MT_GET_FMT (uptr)].name, uptr->filename);
}
free (buf_f);
free (buf_r);
free (rec_sizes);
uptr->pos = saved_pos;
-(void)sim_fseek (uptr->fileref, uptr->pos, SEEK_SET);
+(void)sim_tape_seek (uptr, uptr->pos);
return SCPE_OK;
}
/* Find the preceding record in a TPC file */
@@ -3098,13 +3624,16 @@
hi = uptr->hwmark - 1;
do {
p = (lo + hi) >> 1;
if (uptr->pos == map[p])
return ((p == 0)? map[p]: map[p - 1]);
- else if (uptr->pos < map[p])
- hi = p - 1;
- else lo = p + 1;
+ else {
+ if (uptr->pos < map[p])
+ hi = p - 1;
+ else
+ lo = p + 1;
+ }
}
while (lo <= hi);
return ((p == 0)? map[p]: map[p - 1]);
}
@@ -3286,10 +3815,12 @@
FILE *fSIMH = NULL;
FILE *fE11 = NULL;
FILE *fTPC = NULL;
FILE *fP7B = NULL;
FILE *fAWS = NULL;
+FILE *fAWS2 = NULL;
+FILE *fAWS3 = NULL;
FILE *fTAR = NULL;
FILE *fTAR2 = NULL;
int i, j, k;
t_tpclnt tpclnt;
t_mtrlnt mtrlnt;
@@ -3346,10 +3877,18 @@
if (fTAR2 == NULL)
goto Done_Files;
sprintf (name, "%s.aws", filename);
fAWS = fopen (name, "wb");
if (fAWS == NULL)
+ goto Done_Files;
+sprintf (name, "%s.2.aws", filename);
+fAWS2 = fopen (name, "wb");
+if (fAWS2 == NULL)
+ goto Done_Files;
+sprintf (name, "%s.3.aws", filename);
+fAWS3 = fopen (name, "wb");
+if (fAWS3 == NULL)
goto Done_Files;
sprintf (name, "aws %s.aws.tape", filename);
(void)remove (name);
sim_switches = SWMASK ('F') | (sim_switches & SWMASK ('D')) | SWMASK ('N');
if (sim_switches & SWMASK ('D'))
@@ -3371,15 +3910,22 @@
(void)sim_fwrite (&mtrlnt, sizeof (mtrlnt), 1, fE11);
(void)sim_fwrite (&tpclnt, sizeof (tpclnt), 1, fTPC);
(void)sim_fwrite (&awslnt, sizeof (awslnt), 1, fAWS);
(void)sim_fwrite (&awslnt_last, sizeof (awslnt_last), 1, fAWS);
(void)sim_fwrite (&awsrec_typ, sizeof (awsrec_typ), 1, fAWS);
+ if (i == 0) {
+ (void)sim_fwrite (&awslnt, sizeof (awslnt), 1, fAWS3);
+ (void)sim_fwrite (&awslnt_last, sizeof (awslnt_last), 1, fAWS3);
+ (void)sim_fwrite (&awsrec_typ, sizeof (awsrec_typ), 1, fAWS3);
+ }
awslnt_last = awslnt;
(void)sim_fwrite (buf, 1, rec_size, fSIMH);
(void)sim_fwrite (buf, 1, rec_size, fE11);
(void)sim_fwrite (buf, 1, rec_size, fTPC);
(void)sim_fwrite (buf, 1, rec_size, fAWS);
+ if (i == 0)
+ (void)sim_fwrite (buf, 1, rec_size, fAWS3);
stat = sim_tape_wrrecf (uptr, buf, rec_size);
if (MTSE_OK != stat)
goto Done_Files;
if (rec_size & 1) {
(void)sim_fwrite (&tpclnt, 1, 1, fSIMH);
@@ -3401,14 +3947,25 @@
buf[0] = P7B_SOR | P7B_EOF;
(void)sim_fwrite (buf, 1, 1, fP7B);
(void)sim_fwrite (&awslnt, sizeof (awslnt), 1, fAWS);
(void)sim_fwrite (&awslnt_last, sizeof (awslnt_last), 1, fAWS);
(void)sim_fwrite (&awsrec_typ, sizeof (awsrec_typ), 1, fAWS);
+ if (i == 0) {
+ (void)sim_fwrite (&awslnt, sizeof (awslnt), 1, fAWS3);
+ (void)sim_fwrite (&awslnt_last, sizeof (awslnt_last), 1, fAWS3);
+ (void)sim_fwrite (&awsrec_typ, sizeof (awsrec_typ), 1, fAWS3);
+ }
awslnt_last = 0;
stat = sim_tape_wrtmk (uptr);
if (MTSE_OK != stat)
goto Done_Files;
+ if (i == 0) {
+ mtrlnt = MTR_GAP;
+ for (j=0; junits, "TapeTestFile1"));
SIM_TEST(sim_tape_test_create_tape_files (dptr->units, "TapeTestFile1", 2, 5, 4096));
sim_switches = saved_switches;
-SIM_TEST(sim_tape_test_process_tape_file (dptr->units, "TapeTestFile1", "tar"));
-
-sim_switches = saved_switches;
-SIM_TEST(sim_tape_test_process_tape_file (dptr->units, "TapeTestFile1.2", "tar"));
-
-sim_switches = saved_switches;
-SIM_TEST(sim_tape_test_process_tape_file (dptr->units, "TapeTestFile1", "aws"));
-
-sim_switches = saved_switches;
-SIM_TEST(sim_tape_test_process_tape_file (dptr->units, "TapeTestFile1", "p7b"));
-
-sim_switches = saved_switches;
-SIM_TEST(sim_tape_test_process_tape_file (dptr->units, "TapeTestFile1", "tpc"));
-
-sim_switches = saved_switches;
-SIM_TEST(sim_tape_test_process_tape_file (dptr->units, "TapeTestFile1", "e11"));
-
-sim_switches = saved_switches;
-SIM_TEST(sim_tape_test_process_tape_file (dptr->units, "TapeTestFile1", "simh"));
+SIM_TEST(sim_tape_test_process_tape_file (dptr->units, "TapeTestFile1.*", "ansi-vms", 0));
+
+sim_switches = saved_switches;
+SIM_TEST(sim_tape_test_process_tape_file (dptr->units, "TapeTestFile1.*", "ansi-rsx11", 0));
+
+sim_switches = saved_switches;
+SIM_TEST(sim_tape_test_process_tape_file (dptr->units, "TapeTestFile1.*", "ansi-rt11", 0));
+
+sim_switches = saved_switches;
+SIM_TEST(sim_tape_test_process_tape_file (dptr->units, "TapeTestFile1.*", "ansi-rsts", 0));
+
+sim_switches = saved_switches;
+SIM_TEST(sim_tape_test_process_tape_file (dptr->units, "TapeTestFile1.*", "ansi-var", 0));
+
+sim_switches = saved_switches;
+SIM_TEST(sim_tape_test_process_tape_file (dptr->units, "TapeTestFile1", "tar", 0));
+
+sim_switches = saved_switches;
+SIM_TEST(sim_tape_test_process_tape_file (dptr->units, "TapeTestFile1", "aws", 0));
+
+sim_switches = saved_switches;
+SIM_TEST(sim_tape_test_process_tape_file (dptr->units, "TapeTestFile1.3", "aws", 0));
+
+sim_switches = saved_switches;
+SIM_TEST(sim_tape_test_process_tape_file (dptr->units, "TapeTestFile1.2", "aws", 0));
+
+sim_switches = saved_switches;
+SIM_TEST(sim_tape_test_process_tape_file (dptr->units, "TapeTestFile1.2", "tar", 0));
+
+sim_switches = saved_switches;
+SIM_TEST(sim_tape_test_process_tape_file (dptr->units, "TapeTestFile1", "aws", 0));
+
+sim_switches = saved_switches;
+SIM_TEST(sim_tape_test_process_tape_file (dptr->units, "TapeTestFile1", "p7b", 0));
+
+sim_switches = saved_switches;
+SIM_TEST(sim_tape_test_process_tape_file (dptr->units, "TapeTestFile1", "tpc", 0));
+
+sim_switches = saved_switches;
+SIM_TEST(sim_tape_test_process_tape_file (dptr->units, "TapeTestFile1", "e11", 0));
+
+sim_switches = saved_switches;
+SIM_TEST(sim_tape_test_process_tape_file (dptr->units, "TapeTestFile1", "simh", 0));
SIM_TEST(sim_tape_test_remove_tape_files (dptr->units, "TapeTestFile1"));
return SCPE_OK;
}
+
+static void ansi_date (time_t datetime, char date[6], t_bool y2k_date_bug)
+ {
+ struct tm *lt;
+ char buf[20];
+
+ lt = localtime (&datetime);
+ if (y2k_date_bug)
+ sprintf (buf, " %c%c%03d", '0' + (lt->tm_year / 10),
+ '0' + (lt->tm_year % 10),
+ lt->tm_yday + 1);
+ else
+ sprintf (buf, "%c%02d%03d", (lt->tm_year < 100) ? ' ' : '0' + (lt->tm_year/100 - 1),
+ lt->tm_year % 100,
+ lt->tm_yday + 1);
+ memcpy (date, buf, 6);
+ }
+
+/*
+ * This isn't quite ANSI 'a' since several ANSI allowed characters
+ * are either illegal file names on many DEC systems or are confusing
+ * to OS file name parsers.
+ */
+static void to_ansi_a (char *out, const char *in, size_t size)
+ {
+ memset (out, ' ', size);
+ while (size--) {
+ if (isupper (*in) || isdigit (*in))
+ *(out++) = *in++;
+ else {
+ if (*in == '\0')
+ break;
+ if (islower (*in)) {
+ *(out++) = toupper (*in);
+ ++in;
+ }
+ else {
+ if (strchr ("-.$_/", *in))
+ *(out++) = *in++;
+ else
+ ++in;
+ }
+ }
+ }
+ }
+
+static void ansi_make_VOL1 (VOL1 *vol, const char *ident, uint32 ansi_type)
+ {
+ memset (vol, ' ', sizeof (*vol));
+ memcpy (vol->type, "VOL", 3);
+ vol->num = '1';
+ to_ansi_a (vol->ident, ident, sizeof (vol->ident));
+ vol->standard = ansi_args[ansi_type].vol1_standard;
+ }
+
+static void ansi_make_HDR1 (HDR1 *hdr1, VOL1 *vol, HDR4 *hdr4, const char *filename, uint32 ansi_type)
+ {
+ const char *fn;
+ struct stat statb;
+ char extra_name_used[3] = "00";
+ char *fn_cpy, *c, *ext;
+
+ memset (&statb, 0, sizeof (statb));
+ (void)stat (filename, &statb);
+ if (!(fn = strrchr (filename, '/')) && !(fn = strrchr (filename, '\\')))
+ fn = filename;
+ else
+ ++fn; /* skip over slash or backslash */
+ fn_cpy = (char *)malloc (strlen (fn) + 1);
+ strcpy (fn_cpy, fn);
+ fn = fn_cpy;
+ ext = strrchr (fn_cpy, '.');
+ if (ext) {
+ while (((c = strchr (fn_cpy, '.')) != NULL) &&
+ (c != ext))
+ *c = '_'; /* translate extra .'s to _ */
+ }
+ memset (hdr1, ' ', sizeof (*hdr1));
+ memcpy (hdr1->type, "HDR", 3);
+ hdr1->num = '1';
+ memset (hdr4, ' ', sizeof (*hdr1));
+ memcpy (hdr4->type, "HDR", 3);
+ hdr4->num = '4';
+ to_ansi_a (hdr1->file_ident, fn, sizeof (hdr1->file_ident));
+ if (strlen (fn) > 17) {
+ to_ansi_a (hdr4->extra_name, fn + 17, sizeof (hdr4->extra_name));
+ sprintf (extra_name_used, "%02d", (int)(strlen (fn) - 17));
+ }
+ memcpy (hdr4->extra_name_used, extra_name_used, 2);
+ memcpy (hdr1->file_set, vol->ident, sizeof (hdr1->file_set));
+ memcpy (hdr1->file_section, "0001", 4);
+ memcpy (hdr1->file_sequence, "0001", 4);
+ memcpy (hdr1->generation_number, "0001", 4); /* generation_number and version_number */
+ memcpy (hdr1->version_number, "00", 2); /* combine to produce VMS version # ;1 here */
+ ansi_date (statb.st_mtime, hdr1->creation_date, ansi_args[ansi_type].y2k_date_bug);
+ memcpy (hdr1->expiration_date, " 00000", 6);
+ memcpy (hdr1->block_count, "000000", 6);
+ to_ansi_a (hdr1->system_code, ansi_args[ansi_type].system_code, sizeof (hdr1->system_code));
+ free (fn_cpy);
+ }
+
+static void ansi_make_HDR2 (HDR2 *hdr, t_bool fixed_record, size_t block_size, size_t record_size, uint32 ansi_type)
+ {
+ char size[12];
+ struct ansi_tape_parameters *ansi = &ansi_args[ansi_type];
+
+ memset (hdr, ' ', sizeof (*hdr));
+ memcpy (hdr->type, "HDR", 3);
+ hdr->num = '2';
+ hdr->record_format = ansi->record_format ? ansi->record_format : (fixed_record ? 'F' : 'D');
+ sprintf (size, "%05d", (int)block_size);
+ memcpy (hdr->block_length, size, sizeof (hdr->block_length));
+ sprintf (size, "%05d", (ansi->zero_record_length)? 0 : (int)record_size);
+ memcpy (hdr->record_length, size, sizeof (hdr->record_length));
+ hdr->carriage_control = ansi->carriage_control ? ansi->carriage_control : (fixed_record ? 'M' : ' ');
+ memcpy (hdr->buffer_offset, "00", 2);
+ }
+
+static void ansi_fill_text_buffer (FILE *f, char *buf, size_t buf_size, size_t record_skip_ending, t_bool fixed_text)
+ {
+ long start;
+ char *tmp = (char *)calloc (2 + buf_size, sizeof (*buf));
+ size_t offset = 0;
+
+ while (1) {
+ size_t rec_size;
+ char rec_size_str[16];
+
+ start = ftell (f);
+ if (start < 0)
+ break;
+ if (!fgets (tmp, buf_size, f))
+ break;
+ rec_size = strlen (tmp);
+ if (!fixed_text) {
+ if (rec_size >= record_skip_ending)
+ rec_size -= record_skip_ending;
+ if ((rec_size + 4) > (int)(buf_size - offset)) { /* room for record? */
+ (void)fseek (f, start, SEEK_SET);
+ break;
+ }
+ sprintf (rec_size_str, "%04u", (int)(rec_size + 4));
+ memcpy (buf + offset, rec_size_str, 4);
+ memcpy (buf + offset + 4, tmp, rec_size);
+ offset += 4 + rec_size;
+ }
+ else {
+ size_t move_size;
+
+ if ((tmp[rec_size - 2] != '\r') &&
+ (tmp[rec_size - 1] == '\n')) {
+ memcpy (&tmp[rec_size - 1], "\r\n", 3);
+ rec_size += 1;
+ }
+ if (offset + rec_size < buf_size)
+ move_size = rec_size;
+ else
+ move_size = buf_size - offset;
+ /* We've got a line that stradles a block boundary */
+ memcpy (buf + offset, tmp, move_size);
+ offset += move_size;
+ if (offset == buf_size) {
+ (void)fseek (f, start + move_size, SEEK_SET);
+ break;
+ }
+ }
+ }
+ if (buf_size > offset) {
+ if (fixed_text)
+ memset (buf + offset, 0, buf_size - offset);
+ else
+ memset (buf + offset, '^', buf_size - offset);
+ }
+ free (tmp);
+ }
+
+static t_bool memory_tape_add_block (MEMORY_TAPE *tape, uint8 *block, uint32 size)
+{
+TAPE_RECORD *rec;
+
+if (tape->array_size <= tape->record_count) {
+ TAPE_RECORD **new_records;
+ new_records = (TAPE_RECORD **)realloc (tape->records, (tape->array_size + 1000) * sizeof (*tape->records));
+ if (new_records == NULL)
+ return TRUE; /* no memory error */
+ tape->records = new_records;
+ memset (tape->records + tape->array_size, 0, 1000 * sizeof (*tape->records));
+ tape->array_size += 1000;
+ }
+rec = (TAPE_RECORD *)malloc (sizeof (*rec) + size);
+if (rec == NULL)
+ return TRUE; /* no memory error */
+rec->size = size;
+memcpy (rec->data, block, size);
+tape->records[tape->record_count++] = rec;
+return FALSE;
+}
+
+static void memory_free_tape (void *vtape)
+{
+uint32 i;
+MEMORY_TAPE *tape = (MEMORY_TAPE *)vtape;
+
+for (i=0; irecord_count; i++) {
+ free (tape->records[i]);
+ tape->records[i] = NULL;
+ }
+free (tape->records);
+free (tape);
+}
+
+MEMORY_TAPE *memory_create_tape (void)
+{
+MEMORY_TAPE *tape = (MEMORY_TAPE *)calloc (1, sizeof (*tape));
+
+if (NULL == tape)
+ return tape;
+tape->ansi_type = -1;
+return tape;
+}
+
+static const char rad50[] = " ABCDEFGHIJKLMNOPQRSTUVWXYZ%.%0123456789";
+
+static uint16 dos11_ascR50(char *inbuf)
+{
+uint16 value;
+
+value = (strchr (rad50, *inbuf++) - rad50) * 03100;
+value += (strchr (rad50, *inbuf++) - rad50) * 050;
+value += (strchr (rad50, *inbuf++) - rad50);
+
+return value;
+}
+
+/*
+ * Sanitize a filename to generate a DOS-11 compatible version. Ignore
+ * non-alphanumerics, upper case lower case characters and terminate on '.'
+ */
+static void dos11_sanitize(char *buf, int len, const char *inbuf)
+{
+ while ((len != 0) && (*inbuf != '\0') && (*inbuf !='.')) {
+ char ch = toupper (*inbuf++);
+
+ if (isalnum(ch)) {
+ *buf++ = ch;
+ len--;
+ }
+ }
+}
+
+static int dos11_copy_ascii_file(FILE *f, MEMORY_TAPE *tape, char *buf, size_t bufSize)
+{
+char ch, tmp[512];
+t_bool crlast = FALSE;
+int error = 0;
+size_t i, data_read, offset = 0;
+
+memset (buf, 0, bufSize);
+
+while (!feof (f) && !error) {
+ data_read = fread (tmp, 1, sizeof (tmp), f);
+ if (data_read > 0)
+ for (i = 0; i < data_read; i++) {
+ ch = tmp[i];
+ if (ch == '\n') {
+ if (!crlast) {
+ buf[offset++] = '\r';
+ if (offset == bufSize) {
+ error = memory_tape_add_block (tape, (uint8 *)buf, bufSize);
+ offset = 0;
+ memset (buf, 0, bufSize);
+ }
+ }
+ buf[offset++] = ch;
+ if (offset == bufSize) {
+ error = memory_tape_add_block (tape, (uint8 *)buf, bufSize);
+ offset = 0;
+ memset (buf, 0, bufSize);
+ }
+ crlast = FALSE;
+ }
+ else {
+ crlast = ch == '\r';
+
+ buf[offset++] = ch;
+ if (offset == bufSize) {
+ error = memory_tape_add_block (tape, (uint8 *)buf, bufSize);
+ offset = 0;
+ memset (buf, 0, bufSize);
+ }
+ }
+ }
+ }
+ if (offset != 0)
+ error = memory_tape_add_block (tape, (uint8 *)buf, bufSize);
+
+return error;
+}
+
+static void sim_tape_add_dos11_entry (const char *directory,
+ const char *filename,
+ t_offset FileSize,
+ const struct stat *filestat,
+ void *context)
+{
+MEMORY_TAPE *tape = (MEMORY_TAPE *)context;
+char FullPath[PATH_MAX + 1];
+FILE *f;
+size_t max_record_size;
+t_bool lf_line_endings;
+t_bool crlf_line_endings;
+uint8 *block = NULL;
+int error = 0;
+DOS11_HDR hdr;
+char fname[9], ext[3];
+const char *ptr;
+struct tm *tm;
+time_t now = time (NULL);
+uint16 today;
+int year;
+
+/*
+ * Compute a suitable year for file creation date. This year will have the
+ * same calendar as the current year but will be in the 20th century so that
+ * DOS/BATCH-11 will be able to interpret it correctly.
+ */
+tm = localtime (&now);
+year = tm->tm_year + 1900;
+while (year >= 2000)
+ year -= 28;
+
+today = ((year - 70) * 1000) + tm->tm_yday + 1;
+
+sprintf (FullPath, "%s%s", directory, filename);
+f = tape_open_and_check_file(FullPath);
+ if (f == NULL)
+ return;
+
+tape_classify_file_contents (f, &max_record_size, &lf_line_endings, &crlf_line_endings);
+
+memset (&hdr, 0, sizeof (hdr));
+memset (fname, ' ', sizeof (fname));
+memset (ext, ' ', sizeof (ext));
+
+dos11_sanitize (fname, sizeof (fname), filename);
+ptr = strchr (filename, '.');
+if (ptr != NULL)
+ dos11_sanitize (ext, sizeof (ext), ++ptr);
+
+/*
+ * If we were unable to generate a valid DOS11 filename, generate one based
+ * on the file number on the tape (000000 - 999999).
+ */
+if (fname[0] == ' ') {
+ char temp[10];
+
+ sprintf(temp, "%06u ", tape->file_count % 100000);
+ memcpy(fname, temp, sizeof(fname));
+}
+
+hdr.fname[0] = dos11_ascR50 (&fname[0]);
+hdr.fname[1] = dos11_ascR50 (&fname[3]);
+hdr.ext = dos11_ascR50 (&ext[0]);
+hdr.prog = 1;
+hdr.proj = 1;
+hdr.prot = DOS11_PROT;
+hdr.date = today;
+hdr.fname3 = dos11_ascR50 (&fname[6]);
+
+memory_tape_add_block (tape, (uint8 *)&hdr, sizeof (hdr));
+
+rewind (f);
+block = (uint8 *)calloc (tape->block_size, 1);
+
+if (lf_line_endings || crlf_line_endings)
+ error = dos11_copy_ascii_file (f, tape, (char *)block, tape->block_size);
+else {
+ size_t data_read;
+
+ while (!feof (f) && !error) {
+ data_read = fread (block, 1, tape->block_size, f);
+ if (data_read > 0)
+ error = memory_tape_add_block (tape, block, data_read);
+ }
+ }
+
+fclose (f);
+free (block);
+memory_tape_add_block (tape, NULL, 0);
+++tape->file_count;
+}
+
+static FILE *tape_open_and_check_file(const char *filename)
+{
+FILE *file = fopen(filename, "rb");
+
+if (file != NULL) {
+ struct stat statb;
+
+ memset (&statb, 0, sizeof (statb));
+ if (fstat (fileno (file), &statb) == 0) {
+ if (((S_IFDIR | S_IFREG) & statb.st_mode) == S_IFREG)
+ return file;
+
+ sim_printf ("Can't put a %s on tape: %s\n",
+ statb.st_mode & S_IFREG ? "directory" : "non regular file",
+ filename);
+ fclose (file);
+ return NULL;
+ }
+ sim_printf ("Can't stat: %s\n", filename);
+ fclose (file);
+ return NULL;
+ }
+sim_printf ("Can't open: %s - %s\n", filename, strerror (errno));
+return NULL;
+}
+
+static int tape_classify_file_contents (FILE *f, size_t *max_record_size, t_bool *lf_line_endings, t_bool *crlf_line_endings)
+{
+long pos = -1;
+long last_cr = -1;
+long last_lf = -1;
+long line_start = 0;
+int chr;
+long non_print_chars = 0;
+long lf_lines = 0;
+long crlf_lines = 0;
+
+*max_record_size = 0;
+*lf_line_endings = FALSE;
+*crlf_line_endings = FALSE;
+rewind (f);
+while (EOF != (chr = fgetc (f))) {
+ ++pos;
+ if (!isprint (chr) && (chr != '\r') && (chr != '\n') && (chr != '\t') && (chr != '\f'))
+ ++non_print_chars;
+ if (chr == '\r')
+ last_cr = pos;
+ if (chr == '\n') {
+ long line_size;
+
+ if (last_cr == (pos - 1)) {
+ ++crlf_lines;
+ line_size = (pos - (line_start - 2));
+ }
+ else {
+ ++lf_lines;
+ line_size = (pos - (line_start - 1));
+ }
+ if ((line_size + 4) > (long)(*max_record_size + 4))
+ *max_record_size = line_size + 4;
+ line_start = pos + 1;
+ last_lf = pos;
+ }
+ }
+rewind (f);
+if (non_print_chars)
+ *max_record_size = 512;
+else {
+ if ((crlf_lines > 0) && (lf_lines == 0)) {
+ *lf_line_endings = FALSE;
+ *crlf_line_endings = TRUE;
+ }
+ else {
+ if ((lf_lines > 0) && (crlf_lines == 0)) {
+ *lf_line_endings = TRUE;
+ *crlf_line_endings = FALSE;
+ }
+ }
+ }
+return 0;
+}
+
+MEMORY_TAPE *ansi_create_tape (const char *label, uint32 block_size, uint32 ansi_type)
+{
+MEMORY_TAPE *tape = memory_create_tape ();
+
+if (NULL == tape)
+ return tape;
+tape->block_size = block_size;
+tape->ansi_type = ansi_type;
+ansi_make_VOL1 (&tape->vol1, label, ansi_type);
+memory_tape_add_block (tape, (uint8 *)&tape->vol1, sizeof (tape->vol1));
+return tape;
+}
+
+static int ansi_add_file_to_tape (MEMORY_TAPE *tape, const char *filename)
+{
+FILE *f;
+struct ansi_tape_parameters *ansi = &ansi_args[tape->ansi_type];
+uint8 *block = NULL;
+size_t max_record_size;
+t_bool lf_line_endings;
+t_bool crlf_line_endings;
+char file_sequence[5];
+int block_count = 0;
+char block_count_string[17];
+int error = FALSE;
+HDR1 hdr1;
+HDR2 hdr2;
+HDR3 hdr3;
+HDR4 hdr4;
+
+f = tape_open_and_check_file(filename);
+if (f == NULL)
+ return TRUE;
+
+tape_classify_file_contents (f, &max_record_size, &lf_line_endings, &crlf_line_endings);
+ansi_make_HDR1 (&hdr1, &tape->vol1, &hdr4, filename, tape->ansi_type);
+sprintf (file_sequence, "%04d", 1 + tape->file_count);
+memcpy (hdr1.file_sequence, file_sequence, sizeof (hdr1.file_sequence));
+if (ansi->fixed_text)
+ max_record_size = 512;
+ansi_make_HDR2 (&hdr2, !lf_line_endings && !crlf_line_endings, tape->block_size, (tape->ansi_type > MTUF_F_ANSI) ? 512 : max_record_size, tape->ansi_type);
+
+if (!ansi->nohdr3) { /* Need HDR3? */
+ if (!lf_line_endings && !crlf_line_endings) /* Binary File? */
+ memcpy (&hdr3, ansi->hdr3_fixed, sizeof (hdr3));
+ else { /* Text file */
+ if ((lf_line_endings) && !(ansi->fixed_text))
+ memcpy (&hdr3, ansi->hdr3_lf_line_endings, sizeof (hdr3));
+ else
+ memcpy (&hdr3, ansi->hdr3_crlf_line_endings, sizeof (hdr3));
+ }
+ }
+memory_tape_add_block (tape, (uint8 *)&hdr1, sizeof (hdr1));
+if (!ansi->nohdr2)
+ memory_tape_add_block (tape, (uint8 *)&hdr2, sizeof (hdr2));
+if (!ansi->nohdr3)
+ memory_tape_add_block (tape, (uint8 *)&hdr3, sizeof (hdr3));
+if ((0 != memcmp (hdr4.extra_name_used, "00", 2)) && !ansi->nohdr3 && !ansi->nohdr2)
+ memory_tape_add_block (tape, (uint8 *)&hdr4, sizeof (hdr4));
+memory_tape_add_block (tape, NULL, 0); /* Tape Mark */
+rewind (f);
+block = (uint8 *)calloc (tape->block_size, 1);
+while (!feof(f) && !error) {
+ size_t data_read = tape->block_size;
+
+ if (lf_line_endings || crlf_line_endings) /* text file? */
+ ansi_fill_text_buffer (f, (char *)block, tape->block_size,
+ crlf_line_endings ? ansi->skip_crlf_line_endings : ansi->skip_lf_line_endings,
+ ansi->fixed_text);
+ else
+ data_read = fread (block, 1, tape->block_size, f);
+ if (data_read > 0)
+ error = memory_tape_add_block (tape, block, data_read);
+ if (!error)
+ ++block_count;
+ }
+fclose (f);
+free (block);
+memory_tape_add_block (tape, NULL, 0); /* Tape Mark */
+memcpy (hdr1.type, "EOF", sizeof (hdr1.type));
+memcpy (hdr2.type, "EOF", sizeof (hdr2.type));
+memcpy (hdr3.type, "EOF", sizeof (hdr3.type));
+memcpy (hdr4.type, "EOF", sizeof (hdr4.type));
+sprintf (block_count_string, "%06d", block_count);
+memcpy (hdr1.block_count, block_count_string, sizeof (hdr1.block_count));
+memory_tape_add_block (tape, (uint8 *)&hdr1, sizeof (hdr1));
+if (!ansi->nohdr2)
+ memory_tape_add_block (tape, (uint8 *)&hdr2, sizeof (hdr2));
+if (!ansi->nohdr3)
+ memory_tape_add_block (tape, (uint8 *)&hdr3, sizeof (hdr3));
+if ((0 != memcmp (hdr4.extra_name_used, "00", 2)) && !ansi->nohdr3 && !ansi->nohdr2)
+ memory_tape_add_block (tape, (uint8 *)&hdr4, sizeof (hdr4));
+memory_tape_add_block (tape, NULL, 0); /* Tape Mark */
+if (sim_switches & SWMASK ('V'))
+ sim_messagef (SCPE_OK, "%17.17s%62.62s\n\t%d blocks of data\n", hdr1.file_ident, hdr4.extra_name, block_count);
+++tape->file_count;
+return error;
+}
+
+static void sim_tape_add_ansi_entry (const char *directory,
+ const char *filename,
+ t_offset FileSize,
+ const struct stat *filestat,
+ void *context)
+{
+MEMORY_TAPE *tape = (MEMORY_TAPE *)context;
+char FullPath[PATH_MAX + 1];
+
+sprintf (FullPath, "%s%s", directory, filename);
+
+(void)ansi_add_file_to_tape (tape, FullPath);
+}
+
+/* export an existing tape to a SIMH tape image */
+static t_stat sim_export_tape (UNIT *uptr, const char *export_file)
+{
+t_stat r;
+FILE *f;
+t_addr saved_pos = uptr->pos;
+uint8 *buf = NULL;
+t_mtrlnt bc, sbc;
+t_mtrlnt max = MTR_MAXLEN;
+
+if ((export_file == NULL) || (*export_file == '\0'))
+ return sim_messagef (SCPE_ARG, "Missing tape export file specification\n");
+f = fopen (export_file, "wb");
+if (f == NULL)
+ return sim_messagef (SCPE_OPENERR, "Can't open SIMH tape image file: %s - %s\n", export_file, strerror (errno));
+
+buf = (uint8 *)calloc (max, 1);
+if (buf == NULL) {
+ fclose (f);
+ return SCPE_MEM;
+ }
+r = sim_tape_rewind (uptr);
+while (r == SCPE_OK) {
+ r = sim_tape_rdrecf (uptr, buf, &bc, max);
+ switch (r) {
+ case MTSE_OK:
+ sbc = ((bc + 1) & ~1); /* word alignment for SIMH format data */
+ if ((1 != sim_fwrite (&bc, sizeof (bc), 1, f)) ||
+ (sbc != sim_fwrite (buf, 1, sbc, f)) ||
+ (1 != sim_fwrite (&bc, sizeof (bc), 1, f)))
+ r = sim_messagef (SCPE_IOERR, "Error writing file: %s - %s\n", export_file, strerror (errno));
+ else
+ r = SCPE_OK;
+ break;
+
+ case MTSE_TMK:
+ bc = 0;
+ if (1 != sim_fwrite (&bc, sizeof (bc), 1, f))
+ r = sim_messagef (SCPE_IOERR, "Error writing file: %s - %s\n", export_file, strerror (errno));
+ else
+ r = SCPE_OK;
+ break;
+
+ default:
+ break;
+ }
+ }
+if (r == MTSE_EOM)
+ r = SCPE_OK;
+free (buf);
+fclose (f);
+uptr->pos = saved_pos;
+return r;
+}
Index: src/SIMH/sim_tape.h
==================================================================
--- src/SIMH/sim_tape.h
+++ src/SIMH/sim_tape.h
@@ -88,42 +88,39 @@
/* TAR tape format */
#define TAR_DFLT_RECSIZE 10240 /* Default Fixed record size */
/* Unit flags */
-#define MTUF_V_PNU (UNIT_V_UF + 0) /* position not upd */
-#define MTUF_V_WLK (UNIT_V_UF + 1) /* write locked */
+#define MTUF_V_WLK (UNIT_V_UF + 0) /* write locked */
#define MTUF_V_FMT (UNIT_V_UF + 2) /* tape file format */
-#define MTUF_W_FMT 3 /* 3b of formats */
-#define MTUF_N_FMT (1u << MTUF_W_FMT) /* number of formats */
-#define MTUF_M_FMT ((1u << MTUF_W_FMT) - 1)
-#define MTUF_F_STD 0 /* SIMH format */
-#define MTUF_F_E11 1 /* E11 format */
-#define MTUF_F_TPC 2 /* TPC format */
-#define MTUF_F_P7B 3 /* P7B format */
-#define MTUF_F_AWS 4 /* AWS format */
-#define MTUF_F_TAR 5 /* TAR format */
-#define MTUF_V_UF (MTUF_V_FMT + MTUF_W_FMT)
-#define MTUF_PNU (1u << MTUF_V_PNU)
+#define MTUF_F_STD 0 /* SIMH format */
+#define MTUF_F_E11 1 /* E11 format */
+#define MTUF_F_TPC 2 /* TPC format */
+#define MTUF_F_P7B 3 /* P7B format */
+#define MTUF_F_AWS 4 /* AWS format */
+#define MTUF_F_TAR 5 /* TAR format */
+#define MTUF_F_ANSI 6 /* ANSI format */
+#define MTUF_F_FIXED 7 /* FIXED format */
+#define MTUF_F_DOS11 8 /* DOS11 format */
+
+#define MTAT_F_VMS 0 /* VMS ANSI type */
+#define MTAT_F_RSX11 1 /* RSX-11 ANSI type */
+#define MTAT_F_RSTS 2 /* RSTS ANSI type */
+#define MTAT_F_RT11 3 /* RT-11 ANSI type */
+
+#define MTUF_V_UF (MTUF_V_WLK + 1)
#define MTUF_WLK (1u << MTUF_V_WLK)
-#define MTUF_FMT (MTUF_M_FMT << MTUF_V_FMT)
#define MTUF_WRP (MTUF_WLK | UNIT_RO)
-#define MT_F_STD (MTUF_F_STD << MTUF_V_FMT)
-#define MT_F_E11 (MTUF_F_E11 << MTUF_V_FMT)
-#define MT_F_TPC (MTUF_F_TPC << MTUF_V_FMT)
-#define MT_F_P7B (MTUF_F_P7B << MTUF_V_FMT)
-#define MT_F_AWS (MTUF_F_AWS << MTUF_V_FMT)
-#define MT_F_TAR (MTUF_F_TAR << MTUF_V_FMT)
-
-#define MT_SET_PNU(u) (u)->flags = (u)->flags | MTUF_PNU
-#define MT_CLR_PNU(u) (u)->flags = (u)->flags & ~MTUF_PNU
-#define MT_TST_PNU(u) ((u)->flags & MTUF_PNU)
+#define MT_SET_PNU(u) (u)->dynflags |= UNIT_TAPE_PNU
+#define MT_CLR_PNU(u) (u)->dynflags &= ~UNIT_TAPE_PNU
+#define MT_TST_PNU(u) ((u)->dynflags & UNIT_TAPE_PNU)
#define MT_SET_INMRK(u) (u)->dynflags = (u)->dynflags | UNIT_TAPE_MRK
#define MT_CLR_INMRK(u) (u)->dynflags = (u)->dynflags & ~UNIT_TAPE_MRK
#define MT_TST_INMRK(u) ((u)->dynflags & UNIT_TAPE_MRK)
-#define MT_GET_FMT(u) (((u)->flags >> MTUF_V_FMT) & MTUF_M_FMT)
+#define MT_GET_FMT(u) (((u)->dynflags >> UNIT_V_TAPE_FMT) & ((1 << UNIT_S_TAPE_FMT) - 1))
+#define MT_GET_ANSI_TYP(u) (((u)->dynflags >> UNIT_V_TAPE_ANSI) & ((1 << UNIT_S_TAPE_ANSI) - 1))
/* sim_tape_position Position Flags */
#define MTPOS_V_REW 3
#define MTPOS_M_REW (1u << MTPOS_V_REW) /* Rewind First */
#define MTPOS_V_REV 2
Index: src/SIMH/sim_timer.c
==================================================================
--- src/SIMH/sim_timer.c
+++ src/SIMH/sim_timer.c
@@ -148,22 +148,26 @@
#endif /* defined(MS_MIN_GRANULARITY) && (MS_MIN_GRANULARITY != 1) */
t_bool sim_idle_enab = FALSE; /* global flag */
volatile t_bool sim_idle_wait = FALSE; /* global flag */
+int32 sim_vm_initial_ips = SIM_INITIAL_IPS;
+
+static int32 sim_precalibrate_ips = SIM_INITIAL_IPS;
static int32 sim_calb_tmr = -1; /* the system calibrated timer */
static int32 sim_calb_tmr_last = -1; /* shadow value when at sim> prompt */
static double sim_inst_per_sec_last = 0; /* shadow value when at sim> prompt */
-static double sim_stop_time = 0; /* time when sim_stop_timer_services was called */
+static uint32 sim_stop_time = 0; /* time when sim_stop_timer_services was called */
+double sim_time_at_sim_prompt = 0; /* time spent processing commands from sim> prompt */
-static uint32 sim_idle_rate_ms = 0;
+static uint32 sim_idle_rate_ms = 0; /* Minimum Sleep time */
static uint32 sim_os_sleep_min_ms = 0;
static uint32 sim_os_sleep_inc_ms = 0;
static uint32 sim_os_clock_resoluton_ms = 0;
static uint32 sim_os_tick_hz = 0;
static uint32 sim_idle_stable = SIM_IDLE_STDFLT;
-static uint32 sim_idle_calib_pct = 0;
+static uint32 sim_idle_calib_pct = 100;
static double sim_timer_stop_time = 0;
static uint32 sim_rom_delay = 0;
static uint32 sim_throt_ms_start = 0;
static uint32 sim_throt_ms_stop = 0;
static uint32 sim_throt_type = 0;
@@ -174,16 +178,61 @@
static double sim_throt_peak_cps;
static double sim_throt_inst_start;
static uint32 sim_throt_sleep_time = 0;
static int32 sim_throt_wait = 0;
static uint32 sim_throt_delay = 3;
-#define CLK_TPS 10
-#define CLK_INIT (SIM_INITIAL_IPS/CLK_TPS)
+#define CLK_TPS 100
+#define CLK_INIT (sim_precalibrate_ips/CLK_TPS)
static int32 sim_int_clk_tps;
-static UNIT *sim_clock_unit[SIM_NTIMERS+1] = {NULL};
-UNIT * volatile sim_clock_cosched_queue[SIM_NTIMERS+1] = {NULL};
-static int32 sim_cosched_interval[SIM_NTIMERS+1];
+
+typedef struct RTC {
+ UNIT *clock_unit; /* registered ticking clock unit */
+ UNIT *timer_unit; /* points to related clock assist unit (sim_timer_units) */
+ UNIT *clock_cosched_queue;
+ int32 cosched_interval;
+ uint32 ticks; /* ticks */
+ uint32 hz; /* tick rate */
+ uint32 last_hz; /* prior tick rate */
+ uint32 rtime; /* real time (usecs) */
+ uint32 vtime; /* virtual time (usecs) */
+ double gtime; /* instruction time */
+ uint32 nxintv; /* next interval */
+ int32 based; /* base delay */
+ int32 currd; /* current delay */
+ int32 initd; /* initial delay */
+ uint32 elapsed; /* seconds since init */
+ uint32 calibrations; /* calibration count */
+ double clock_skew_max; /* asynchronous max skew */
+ double clock_tick_size; /* 1/hz */
+ uint32 calib_initializations; /* Initialization Count */
+ double calib_tick_time; /* ticks time */
+ double calib_tick_time_tot; /* ticks time - total*/
+ uint32 calib_ticks_acked; /* ticks Acked */
+ uint32 calib_ticks_acked_tot; /* ticks Acked - total */
+ uint32 clock_ticks; /* ticks delivered since catchup base */
+ uint32 clock_ticks_tot; /* ticks delivered since catchup base - total */
+ double clock_init_base_time; /* reference time for clock initialization */
+ double clock_tick_start_time; /* reference time when ticking started */
+ double clock_catchup_base_time; /* reference time for catchup ticks */
+ uint32 clock_catchup_ticks; /* Record of catchups */
+ uint32 clock_catchup_ticks_tot; /* Record of catchups - total */
+ uint32 clock_catchup_ticks_curr;/* Record of catchups in this second */
+ t_bool clock_catchup_pending; /* clock tick catchup pending */
+ t_bool clock_catchup_eligible; /* clock tick catchup eligible */
+ uint32 clock_time_idled; /* total time idled */
+ uint32 clock_time_idled_last; /* total time idled as of the previous second */
+ uint32 clock_calib_skip_idle; /* Calibrations skipped due to idling */
+ uint32 clock_calib_gap2big; /* Calibrations skipped Gap Too Big */
+ uint32 clock_calib_backwards; /* Calibrations skipped Clock Running Backwards */
+ } RTC;
+
+RTC rtcs[SIM_NTIMERS+1];
+UNIT sim_timer_units[SIM_NTIMERS+1];/* Clock assist units */
+ /* one for each timer and one for an internal */
+ /* clock if no clocks are registered. */
+
+
static t_bool sim_catchup_ticks = TRUE;
#if defined (SIM_ASYNCH_CLOCKS) && !defined (SIM_ASYNCH_IO)
#undef SIM_ASYNCH_CLOCKS
#endif
t_bool sim_asynch_timer = FALSE;
@@ -702,11 +751,11 @@
/* Forward declarations */
static double _timespec_to_double (struct timespec *time);
static void _double_to_timespec (struct timespec *time, double dtime);
-static t_bool _rtcn_tick_catchup_check (int32 tmr, int32 time);
+static t_bool _rtcn_tick_catchup_check (RTC *rtc, int32 time);
static void _rtcn_configure_calibrated_clock (int32 newtmr);
static t_bool _sim_coschedule_cancel (UNIT *uptr);
static t_bool _sim_wallclock_cancel (UNIT *uptr);
static t_bool _sim_wallclock_is_active (UNIT *uptr);
t_stat sim_timer_show_idle_mode (FILE* st, UNIT* uptr, int32 val, CONST void * desc);
@@ -736,58 +785,23 @@
}
#endif /* defined(SIM_ASYNCH_CLOCKS) */
/* OS independent clock calibration package */
-static int32 rtc_ticks[SIM_NTIMERS+1] = { 0 }; /* ticks */
-static uint32 rtc_hz[SIM_NTIMERS+1] = { 0 }; /* tick rate */
-static uint32 rtc_last_hz[SIM_NTIMERS+1] = { 0 }; /* prior tick rate */
-static uint32 rtc_rtime[SIM_NTIMERS+1] = { 0 }; /* real time */
-static uint32 rtc_vtime[SIM_NTIMERS+1] = { 0 }; /* virtual time */
-static double rtc_gtime[SIM_NTIMERS+1] = { 0 }; /* instruction time */
-static uint32 rtc_nxintv[SIM_NTIMERS+1] = { 0 }; /* next interval */
-static int32 rtc_based[SIM_NTIMERS+1] = { 0 }; /* base delay */
-static int32 rtc_currd[SIM_NTIMERS+1] = { 0 }; /* current delay */
-static int32 rtc_initd[SIM_NTIMERS+1] = { 0 }; /* initial delay */
-static uint32 rtc_elapsed[SIM_NTIMERS+1] = { 0 }; /* sec since init */
-static uint32 rtc_calibrations[SIM_NTIMERS+1] = { 0 }; /* calibration count */
-static double rtc_clock_skew_max[SIM_NTIMERS+1] = { 0 }; /* asynchronous max skew */
-static double rtc_clock_start_gtime[SIM_NTIMERS+1] = { 0 };/* reference instruction time for clock */
-static double rtc_clock_tick_size[SIM_NTIMERS+1] = { 0 }; /* 1/hz */
-static uint32 rtc_calib_initializations[SIM_NTIMERS+1] = { 0 };/* Initialization Count */
-static double rtc_calib_tick_time[SIM_NTIMERS+1] = { 0 }; /* ticks time */
-static double rtc_calib_tick_time_tot[SIM_NTIMERS+1] = { 0 };/* ticks time - total*/
-static uint32 rtc_calib_ticks_acked[SIM_NTIMERS+1] = { 0 };/* ticks Acked */
-static uint32 rtc_calib_ticks_acked_tot[SIM_NTIMERS+1] = { 0 };/* ticks Acked - total */
-static uint32 rtc_clock_ticks[SIM_NTIMERS+1] = { 0 };/* ticks delivered since catchup base */
-static uint32 rtc_clock_ticks_tot[SIM_NTIMERS+1] = { 0 };/* ticks delivered since catchup base - total */
-static double rtc_clock_init_base_time[SIM_NTIMERS+1] = { 0 };/* reference time for clock initialization */
-static double rtc_clock_tick_start_time[SIM_NTIMERS+1] = { 0 };/* reference time when ticking started */
-static double rtc_clock_catchup_base_time[SIM_NTIMERS+1] = { 0 };/* reference time for catchup ticks */
-static uint32 rtc_clock_catchup_ticks[SIM_NTIMERS+1] = { 0 };/* Record of catchups */
-static uint32 rtc_clock_catchup_ticks_tot[SIM_NTIMERS+1] = { 0 };/* Record of catchups - total */
-static t_bool rtc_clock_catchup_pending[SIM_NTIMERS+1] = { 0 };/* clock tick catchup pending */
-static t_bool rtc_clock_catchup_eligible[SIM_NTIMERS+1] = { 0 };/* clock tick catchup eligible */
-static uint32 rtc_clock_time_idled[SIM_NTIMERS+1] = { 0 };/* total time idled */
-static uint32 rtc_clock_time_idled_last[SIM_NTIMERS+1] = { 0 };/* total time idled */
-static uint32 rtc_clock_calib_skip_idle[SIM_NTIMERS+1] = { 0 };/* Calibrations skipped due to idling */
-static uint32 rtc_clock_calib_gap2big[SIM_NTIMERS+1] = { 0 };/* Calibrations skipped Gap Too Big */
-static uint32 rtc_clock_calib_backwards[SIM_NTIMERS+1] = { 0 };/* Calibrations skipped Clock Running Backwards */
-static uint32 sim_idle_cyc_ms = 0; /* Cycles per millisecond while not idling */
-static uint32 sim_idle_cyc_sleep = 0; /* Cycles per minimum sleep interval */
-static double sim_idle_end_time = 0.0; /* Time when last idle completed */
-
-UNIT sim_timer_units[SIM_NTIMERS+1]; /* Clock assist units */
- /* one for each timer and one for an internal */
- /* clock if no clocks are registered. */
+static uint32 sim_idle_cyc_ms = 0; /* Cycles per millisecond while not idling */
+static uint32 sim_idle_cyc_sleep = 0; /* Cycles per minimum sleep interval */
+static double sim_idle_end_time = 0.0; /* Time when last idle completed */
+
UNIT sim_stop_unit; /* Stop unit */
UNIT sim_internal_timer_unit; /* Internal calibration timer */
+int32 sim_internal_timer_time; /* Pending internal timer delay */
UNIT sim_throttle_unit; /* one for throttle */
t_stat sim_throt_svc (UNIT *uptr);
t_stat sim_timer_tick_svc (UNIT *uptr);
t_stat sim_timer_stop_svc (UNIT *uptr);
+
#define DBG_IDL TIMER_DBG_IDLE /* idling */
#define DBG_QUE TIMER_DBG_QUEUE /* queue activities */
#define DBG_MUX TIMER_DBG_MUX /* tmxr queue activities */
#define DBG_TRC 0x008 /* tracing */
@@ -796,16 +810,18 @@
#define DBG_THR 0x040 /* throttle activities */
#define DBG_ACK 0x080 /* interrupt acknowledgement activities */
#define DBG_CHK 0x100 /* check scheduled activation time*/
#define DBG_INT 0x200 /* internal timer activities */
#define DBG_GET 0x400 /* get_time activities */
+#define DBG_TIK 0x800 /* tick activities */
DEBTAB sim_timer_debug[] = {
{"TRACE", DBG_TRC, "Trace routine calls"},
{"IDLE", DBG_IDL, "Idling activities"},
{"QUEUE", DBG_QUE, "Event queuing activities"},
{"IACK", DBG_ACK, "interrupt acknowledgement activities"},
{"CALIB", DBG_CAL, "Calibration activities"},
+ {"TICK", DBG_TIK, "Calibration tick activities"},
{"TIME", DBG_TIM, "Activation and scheduling activities"},
{"GETTIME", DBG_GET, "get_time activities"},
{"INTER", DBG_INT, "Internal timer activities"},
{"THROT", DBG_THR, "Throttling activities"},
{"MUX", DBG_MUX, "Tmxr scheduling activities"},
@@ -820,227 +836,286 @@
void sim_rtcn_init_all (void)
{
int32 tmr;
+RTC *rtc;
-for (tmr = 0; tmr <= SIM_NTIMERS; tmr++)
- if (rtc_initd[tmr] != 0)
- sim_rtcn_init (rtc_initd[tmr], tmr);
+for (tmr = 0; tmr <= SIM_NTIMERS; tmr++) {
+ rtc = &rtcs[tmr];
+ if (rtc->initd != 0)
+ sim_rtcn_init (rtc->initd, tmr);
+ }
}
int32 sim_rtcn_init (int32 time, int32 tmr)
{
return sim_rtcn_init_unit (NULL, time, tmr);
}
int32 sim_rtcn_init_unit (UNIT *uptr, int32 time, int32 tmr)
{
+return sim_rtcn_init_unit_ticks (uptr, time, tmr, 0);
+}
+
+int32 sim_rtcn_init_unit_ticks (UNIT *uptr, int32 time, int32 tmr, int32 ticksper)
+{
+RTC *rtc;
+
if (time == 0)
time = 1;
if (tmr == SIM_INTERNAL_CLK)
tmr = SIM_NTIMERS;
else {
if ((tmr < 0) || (tmr >= SIM_NTIMERS))
return time;
}
+rtc = &rtcs[tmr];
/*
* If we'd previously succeeded in calibrating a tick value, then use that
* delay as a better default to setup when we're re-initialized.
- * Re-initializing happens on any boot or after any breakpoint/continue.
+ * Re-initializing happens on any boot.
*/
-if (rtc_currd[tmr])
- time = rtc_currd[tmr];
+if (rtc->currd)
+ time = rtc->currd;
if (!uptr)
- uptr = sim_clock_unit[tmr];
+ uptr = rtc->clock_unit;
sim_debug (DBG_CAL, &sim_timer_dev, "sim_rtcn_init_unit(unit=%s, time=%d, tmr=%d)\n", uptr ? sim_uname(uptr) : "", time, tmr);
if (uptr) {
- if (!sim_clock_unit[tmr])
+ if (!rtc->clock_unit)
sim_register_clock_unit_tmr (uptr, tmr);
}
-rtc_clock_start_gtime[tmr] = sim_gtime();
-rtc_rtime[tmr] = sim_os_msec ();
-rtc_vtime[tmr] = rtc_rtime[tmr];
-rtc_nxintv[tmr] = 1000;
-rtc_ticks[tmr] = 0;
-rtc_last_hz[tmr] = rtc_hz[tmr];
-rtc_hz[tmr] = 0;
-rtc_based[tmr] = time;
-rtc_currd[tmr] = time;
-rtc_initd[tmr] = time;
-rtc_elapsed[tmr] = 0;
-rtc_calibrations[tmr] = 0;
-rtc_clock_ticks_tot[tmr] += rtc_clock_ticks[tmr];
-rtc_clock_ticks[tmr] = 0;
-rtc_calib_tick_time_tot[tmr] += rtc_calib_tick_time[tmr];
-rtc_calib_tick_time[tmr] = 0;
-rtc_clock_catchup_pending[tmr] = FALSE;
-rtc_clock_catchup_eligible[tmr] = FALSE;
-rtc_clock_catchup_ticks_tot[tmr] += rtc_clock_catchup_ticks[tmr];
-rtc_clock_catchup_ticks[tmr] = 0;
-rtc_calib_ticks_acked_tot[tmr] += rtc_calib_ticks_acked[tmr];
-rtc_calib_ticks_acked[tmr] = 0;
-++rtc_calib_initializations[tmr];
-rtc_clock_init_base_time[tmr] = sim_timenow_double ();
+rtc->gtime = sim_gtime();
+rtc->rtime = sim_is_running ? sim_os_msec () : sim_stop_time;
+rtc->vtime = rtc->rtime;
+rtc->nxintv = 1000;
+rtc->ticks = 0;
+rtc->last_hz = rtc->hz;
+rtc->hz = ticksper;
+rtc->based = time;
+rtc->currd = time;
+rtc->initd = time;
+rtc->elapsed = 0;
+rtc->calibrations = 0;
+rtc->clock_ticks_tot += rtc->clock_ticks;
+rtc->clock_ticks = 0;
+rtc->calib_tick_time_tot += rtc->calib_tick_time;
+rtc->calib_tick_time = 0;
+rtc->clock_catchup_pending = FALSE;
+rtc->clock_catchup_eligible = FALSE;
+rtc->clock_catchup_ticks_tot += rtc->clock_catchup_ticks;
+rtc->clock_catchup_ticks = 0;
+rtc->clock_catchup_ticks_curr = 0;
+rtc->calib_ticks_acked_tot += rtc->calib_ticks_acked;
+rtc->calib_ticks_acked = 0;
+++rtc->calib_initializations;
+rtc->clock_init_base_time = sim_timenow_double ();
_rtcn_configure_calibrated_clock (tmr);
return time;
}
-int32 sim_rtcn_calb (int32 ticksper, int32 tmr)
+int32 sim_rtcn_calb_tick (int32 tmr)
+{
+RTC *rtc = &rtcs[tmr];
+
+return sim_rtcn_calb (rtc->hz, tmr);
+}
+
+int32 sim_rtcn_calb (uint32 ticksper, int32 tmr)
{
-uint32 new_rtime, delta_rtime, last_idle_pct;
+uint32 new_rtime, delta_rtime, last_idle_pct, catchup_ticks_curr;
int32 delta_vtime;
double new_gtime;
int32 new_currd;
int32 itmr;
+RTC *rtc;
if (tmr == SIM_INTERNAL_CLK)
tmr = SIM_NTIMERS;
else {
if ((tmr < 0) || (tmr >= SIM_NTIMERS))
return 10000;
}
-if (rtc_hz[tmr] != ticksper) { /* changing tick rate? */
- if (rtc_hz[tmr] == 0)
- rtc_clock_tick_start_time[tmr] = sim_timenow_double ();
- if ((rtc_last_hz[tmr] != 0) &&
- (rtc_last_hz[tmr] != ticksper) &&
+rtc = &rtcs[tmr];
+if (rtc->hz != ticksper) { /* changing tick rate? */
+ uint32 prior_hz = rtc->hz;
+
+ if (rtc->hz == 0)
+ rtc->clock_tick_start_time = sim_timenow_double ();
+ if ((rtc->last_hz != 0) &&
+ (rtc->last_hz != ticksper) &&
(ticksper != 0))
- rtc_currd[tmr] = (int32)(sim_timer_inst_per_sec () / ticksper);
- rtc_last_hz[tmr] = rtc_hz[tmr];
- rtc_hz[tmr] = ticksper;
+ rtc->currd = (int32)(sim_timer_inst_per_sec () / ticksper);
+ rtc->last_hz = rtc->hz;
+ rtc->hz = ticksper;
_rtcn_configure_calibrated_clock (tmr);
if (ticksper != 0) {
- rtc_clock_tick_size[tmr] = 1.0 / ticksper;
- sim_debug (DBG_CAL, &sim_timer_dev, "sim_rtcn_calb(ticksper=%d,tmr=%d) currd=%d\n", ticksper, tmr, rtc_currd[tmr]);
+ RTC *crtc = &rtcs[sim_calb_tmr];
+
+ rtc->clock_tick_size = 1.0 / ticksper;
+ sim_debug (DBG_CAL, &sim_timer_dev, "sim_rtcn_calb(ticksper=%d,tmr=%d) currd=%d, prior_hz=%d\n", ticksper, tmr, rtc->currd, (int)prior_hz);
+
+ if ((tmr != sim_calb_tmr) && rtc->clock_unit && (ticksper > crtc->hz)) {
+ sim_catchup_ticks = TRUE;
+ sim_debug (DBG_CAL, &sim_timer_dev, "sim_rtcn_calb(%d) - forcing catchup ticks for %s ticking at %d, host tick rate %ds\n", tmr, sim_uname (rtc->clock_unit), ticksper, sim_os_tick_hz);
+ _rtcn_tick_catchup_check (rtc, 0);
+ }
}
else
- sim_debug (DBG_CAL, &sim_timer_dev, "sim_rtcn_calb(ticksper=%d,tmr=%d) timer stopped currd was %d\n", ticksper, tmr, rtc_currd[tmr]);
+ sim_debug (DBG_CAL, &sim_timer_dev, "sim_rtcn_calb(ticksper=%d,tmr=%d) timer stopped currd was %d, prior_hz=%d\n", ticksper, tmr, rtc->currd, (int)prior_hz);
}
if (ticksper == 0) /* running? */
return 10000;
-if (sim_clock_unit[tmr] == NULL) { /* Not using TIMER units? */
- rtc_clock_ticks[tmr] += 1;
- rtc_calib_tick_time[tmr] += rtc_clock_tick_size[tmr];
- }
-if (rtc_clock_catchup_pending[tmr]) { /* catchup tick? */
- ++rtc_clock_catchup_ticks[tmr]; /* accumulating which were catchups */
- rtc_clock_catchup_pending[tmr] = FALSE;
- }
-rtc_ticks[tmr] = rtc_ticks[tmr] + 1; /* count ticks */
-if (rtc_ticks[tmr] < ticksper) /* 1 sec yet? */
- return rtc_currd[tmr];
-rtc_ticks[tmr] = 0; /* reset ticks */
-rtc_elapsed[tmr] = rtc_elapsed[tmr] + 1; /* count sec */
-if (!rtc_avail) /* no timer? */
- return rtc_currd[tmr];
+if (rtc->clock_unit == NULL) { /* Not using TIMER units? */
+ rtc->clock_ticks += 1;
+ rtc->calib_tick_time += rtc->clock_tick_size;
+ }
+if (rtc->clock_catchup_pending) { /* catchup tick? */
+ ++rtc->clock_catchup_ticks; /* accumulating which were catchups */
+ ++rtc->clock_catchup_ticks_curr;
+ rtc->clock_catchup_pending = FALSE;
+ }
+rtc->ticks += 1; /* count ticks */
+if (rtc->ticks < ticksper) /* 1 sec yet? */
+ return rtc->currd;
+catchup_ticks_curr = rtc->clock_catchup_ticks_curr;
+rtc->clock_catchup_ticks_curr = 0;
+rtc->ticks = 0; /* reset ticks */
+rtc->elapsed += 1; /* count sec */
+if (!rtc_avail) /* no timer? */
+ return rtc->currd;
if (sim_calb_tmr != tmr) {
- rtc_currd[tmr] = (int32)(sim_timer_inst_per_sec()/ticksper);
- sim_debug (DBG_CAL, &sim_timer_dev, "calibrated tmr=%d against internal system tmr=%d, tickper=%d (result: %d)\n", tmr, sim_calb_tmr, ticksper, rtc_currd[tmr]);
- return rtc_currd[tmr];
+ rtc->currd = (int32)(sim_timer_inst_per_sec()/ticksper);
+ sim_debug (DBG_CAL, &sim_timer_dev, "sim_rtcn_calb(tmr=%d) calibrated against internal system tmr=%d, tickper=%d (result: %d)\n", tmr, sim_calb_tmr, ticksper, rtc->currd);
+ return rtc->currd;
}
-new_rtime = sim_os_msec (); /* wall time */
-++rtc_calibrations[tmr]; /* count calibrations */
+new_rtime = sim_os_msec (); /* wall time */
+if (!sim_signaled_int_char &&
+ ((new_rtime - sim_last_poll_kbd_time) > 500)) {
+ sim_debug (DBG_CAL, &sim_timer_dev, "sim_rtcn_calb(tmr=%d) gratuitious keyboard poll after %d msecs\n", tmr, (int)(new_rtime - sim_last_poll_kbd_time));
+ (void)sim_poll_kbd ();
+ }
+++rtc->calibrations; /* count calibrations */
sim_debug (DBG_TRC, &sim_timer_dev, "sim_rtcn_calb(ticksper=%d, tmr=%d)\n", ticksper, tmr);
-if (new_rtime < rtc_rtime[tmr]) { /* time running backwards? */
+if (new_rtime < rtc->rtime) { /* time running backwards? */
/* This happens when the value returned by sim_os_msec wraps (as an uint32) */
/* Wrapping will happen initially sometime before a simulator has been running */
/* for 49 days approximately every 49 days thereafter. */
- ++rtc_clock_calib_backwards[tmr]; /* Count statistic */
- sim_debug (DBG_CAL, &sim_timer_dev, "time running backwards - OldTime: %u, NewTime: %u, result: %d\n", rtc_rtime[tmr], new_rtime, rtc_currd[tmr]);
- rtc_rtime[tmr] = new_rtime; /* reset wall time */
- return rtc_currd[tmr]; /* can't calibrate */
- }
-delta_rtime = new_rtime - rtc_rtime[tmr]; /* elapsed wtime */
-rtc_rtime[tmr] = new_rtime; /* adv wall time */
-rtc_vtime[tmr] = rtc_vtime[tmr] + 1000; /* adv sim time */
-if (delta_rtime > 30000) { /* gap too big? */
+ ++rtc->clock_calib_backwards; /* Count statistic */
+ sim_debug (DBG_CAL, &sim_timer_dev, "time running backwards - OldTime: %u, NewTime: %u, result: %d\n", rtc->rtime, new_rtime, rtc->currd);
+ rtc->vtime = rtc->rtime = new_rtime; /* reset wall time */
+ rtc->nxintv = 1000;
+ rtc->based = rtc->currd;
+ if (rtc->clock_catchup_eligible) {
+ rtc->clock_catchup_base_time = sim_timenow_double();
+ rtc->calib_tick_time = 0.0;
+ }
+ return rtc->currd; /* can't calibrate */
+ }
+delta_rtime = new_rtime - rtc->rtime; /* elapsed wtime */
+rtc->rtime = new_rtime; /* adv wall time */
+rtc->vtime += 1000; /* adv sim time */
+if (delta_rtime > 30000) { /* gap too big? */
/* This simulator process has somehow been suspended for a significant */
/* amount of time. This will certainly happen if the host system has */
/* slept or hibernated. It also might happen when a simulator */
/* developer stops the simulator at a breakpoint (a process, not simh */
/* breakpoint). To accomodate this, we set the calibration state to */
/* ignore what happened and proceed from here. */
- ++rtc_clock_calib_gap2big[tmr]; /* Count statistic */
- rtc_vtime[tmr] = rtc_rtime[tmr]; /* sync virtual and real time */
- rtc_nxintv[tmr] = 1000; /* reset next interval */
- rtc_gtime[tmr] = sim_gtime(); /* save instruction time */
- sim_debug (DBG_CAL, &sim_timer_dev, "gap too big: delta = %d - result: %d\n", delta_rtime, rtc_currd[tmr]);
- return rtc_currd[tmr]; /* can't calibr */
- }
-if (delta_rtime == 0) /* avoid divide by zero */
- last_idle_pct = 0; /* force calibration */
-else
- last_idle_pct = MIN(100, (uint32)(100.0 * (((double)(rtc_clock_time_idled[tmr] - rtc_clock_time_idled_last[tmr])) / ((double)delta_rtime))));
-rtc_clock_time_idled_last[tmr] = rtc_clock_time_idled[tmr];
-if (last_idle_pct > (100 - sim_idle_calib_pct)) {
- rtc_rtime[tmr] = new_rtime; /* save wall time */
- rtc_vtime[tmr] = rtc_vtime[tmr] + 1000; /* adv sim time */
- rtc_gtime[tmr] = sim_gtime(); /* save instruction time */
- ++rtc_clock_calib_skip_idle[tmr];
- sim_debug (DBG_CAL, &sim_timer_dev, "skipping calibration due to idling (%d%%) - result: %d\n", last_idle_pct, rtc_currd[tmr]);
- return rtc_currd[tmr]; /* avoid calibrating idle checks */
+ ++rtc->clock_calib_gap2big; /* Count statistic */
+ rtc->vtime = rtc->rtime; /* sync virtual and real time */
+ rtc->nxintv = 1000; /* reset next interval */
+ rtc->gtime = sim_gtime(); /* save instruction time */
+ rtc->based = rtc->currd;
+ if (rtc->clock_catchup_eligible)
+ rtc->calib_tick_time += ((double)delta_rtime / 1000.0);/* advance tick time */
+ sim_debug (DBG_CAL, &sim_timer_dev, "gap too big: delta = %d - result: %d\n", delta_rtime, rtc->currd);
+ return rtc->currd; /* can't calibr */
+ }
+last_idle_pct = 0; /* normally force calibration */
+if (tmr != SIM_NTIMERS) {
+ if (delta_rtime != 0) /* avoid divide by zero */
+ last_idle_pct = MIN(100, (uint32)(100.0 * (((double)(rtc->clock_time_idled - rtc->clock_time_idled_last)) / ((double)delta_rtime))));
+ rtc->clock_time_idled_last = rtc->clock_time_idled;
+ if (last_idle_pct > sim_idle_calib_pct) {
+ rtc->rtime = new_rtime; /* save wall time */
+ rtc->vtime += 1000; /* adv sim time */
+ rtc->gtime = sim_gtime(); /* save instruction time */
+ rtc->based = rtc->currd;
+ ++rtc->clock_calib_skip_idle;
+ sim_debug (DBG_CAL, &sim_timer_dev, "skipping calibration due to idling (%d%%) - result: %d\n", last_idle_pct, rtc->currd);
+ return rtc->currd; /* avoid calibrating idle checks */
+ }
}
new_gtime = sim_gtime();
if ((last_idle_pct == 0) && (delta_rtime != 0)) {
- sim_idle_cyc_ms = (uint32)((new_gtime - rtc_gtime[tmr]) / delta_rtime);
+ sim_idle_cyc_ms = (uint32)((new_gtime - rtc->gtime) / delta_rtime);
if ((sim_idle_rate_ms != 0) && (delta_rtime > 1))
- sim_idle_cyc_sleep = (uint32)((new_gtime - rtc_gtime[tmr]) / (delta_rtime / sim_idle_rate_ms));
+ sim_idle_cyc_sleep = (uint32)((new_gtime - rtc->gtime) / (delta_rtime / sim_idle_rate_ms));
}
-if (sim_asynch_timer) {
- /* An asynchronous clock, merely needs to divide the number of */
+if (sim_asynch_timer || (catchup_ticks_curr > 0)) {
+ /* An asynchronous clock or when catchup ticks have */
+ /* occurred, we merely needs to divide the number of */
/* instructions actually executed by the clock rate. */
- new_currd = (int32)((new_gtime - rtc_gtime[tmr])/ticksper);
+ new_currd = (int32)((new_gtime - rtc->gtime)/ticksper);
/* avoid excessive swings in the calibrated result */
- if (new_currd > 10*rtc_currd[tmr]) /* don't swing big too fast */
- new_currd = 10*rtc_currd[tmr];
- else
- if (new_currd < rtc_currd[tmr]/10) /* don't swing small too fast */
- new_currd = rtc_currd[tmr]/10;
- rtc_currd[tmr] = new_currd;
- rtc_gtime[tmr] = new_gtime; /* save instruction time */
- sim_debug (DBG_CAL, &sim_timer_dev, "asynch calibration result: %d\n", rtc_currd[tmr]);
- return rtc_currd[tmr]; /* calibrated result */
- }
-rtc_gtime[tmr] = new_gtime; /* save instruction time */
+ if (new_currd > 10*rtc->currd) /* don't swing big too fast */
+ new_currd = 10*rtc->currd;
+ else {
+ if (new_currd < rtc->currd/10) /* don't swing small too fast */
+ new_currd = rtc->currd/10;
+ }
+ rtc->based = rtc->currd = new_currd;
+ rtc->gtime = new_gtime; /* save instruction time */
+ sim_debug (DBG_CAL, &sim_timer_dev, "sim_rtcn_calb(%s tmr=%d, tickper=%d) catchups=%u, idle=%d%% result: %d\n",
+ sim_asynch_timer ? "asynch" : "catchup", tmr, ticksper, catchup_ticks_curr, last_idle_pct, rtc->currd);
+ return rtc->currd; /* calibrated result */
+ }
+rtc->gtime = new_gtime; /* save instruction time */
/* This self regulating algorithm depends directly on the assumption */
/* that this routine is called back after processing the number of */
/* instructions which was returned the last time it was called. */
-if (delta_rtime == 0) /* gap too small? */
- rtc_based[tmr] = rtc_based[tmr] * ticksper; /* slew wide */
+if (delta_rtime == 0) /* gap too small? */
+ rtc->based = rtc->based * ticksper; /* slew wide */
else
- rtc_based[tmr] = (int32) (((double) rtc_based[tmr] * (double) rtc_nxintv[tmr]) /
+ rtc->based = (int32) (((double) rtc->based * (double) rtc->nxintv) /
((double) delta_rtime));/* new base rate */
-delta_vtime = rtc_vtime[tmr] - rtc_rtime[tmr]; /* gap */
-if (delta_vtime > SIM_TMAX) /* limit gap */
+delta_vtime = rtc->vtime - rtc->rtime; /* gap */
+if (delta_vtime > SIM_TMAX) /* limit gap */
delta_vtime = SIM_TMAX;
-else if (delta_vtime < -SIM_TMAX)
- delta_vtime = -SIM_TMAX;
-rtc_nxintv[tmr] = 1000 + delta_vtime; /* next wtime */
-rtc_currd[tmr] = (int32) (((double) rtc_based[tmr] * (double) rtc_nxintv[tmr]) /
- 1000.0); /* next delay */
-if (rtc_based[tmr] <= 0) /* never negative or zero! */
- rtc_based[tmr] = 1;
-if (rtc_currd[tmr] <= 0) /* never negative or zero! */
- rtc_currd[tmr] = 1;
-sim_debug (DBG_CAL, &sim_timer_dev, "calibrated tmr=%d, tickper=%d (base=%d, nxintv=%u, result: %d)\n", tmr, ticksper, rtc_based[tmr], rtc_nxintv[tmr], rtc_currd[tmr]);
+else {
+ if (delta_vtime < -SIM_TMAX)
+ delta_vtime = -SIM_TMAX;
+ }
+rtc->nxintv = 1000 + delta_vtime; /* next wtime */
+rtc->currd = (int32) (((double) rtc->based * (double) rtc->nxintv) /
+ 1000.0); /* next delay */
+if (rtc->based <= 0) /* never negative or zero! */
+ rtc->based = 1;
+if (rtc->currd <= 0) /* never negative or zero! */
+ rtc->currd = 1;
+sim_debug (DBG_CAL, &sim_timer_dev, "sim_rtcn_calb(tmr=%d, tickper=%d) (delta_rtime=%d, delta_vtime=%d, base=%d, nxintv=%u, catchups=%u, idle=%d%%, result: %d)\n",
+ tmr, ticksper, (int)delta_rtime, (int)delta_vtime, rtc->based, rtc->nxintv, catchup_ticks_curr, last_idle_pct, rtc->currd);
/* Adjust calibration for other timers which depend on this timer's calibration */
-for (itmr=0; itmr<=SIM_NTIMERS; itmr++)
- if ((itmr != tmr) && (rtc_hz[itmr] != 0))
- rtc_currd[itmr] = (rtc_currd[tmr] * ticksper) / rtc_hz[itmr];
-AIO_SET_INTERRUPT_LATENCY(rtc_currd[tmr] * ticksper); /* set interrrupt latency */
-return rtc_currd[tmr];
+for (itmr=0; itmr<=SIM_NTIMERS; itmr++) {
+ RTC *irtc = &rtcs[itmr];
+
+ if ((itmr != tmr) && (irtc->hz != 0))
+ irtc->currd = (rtc->currd * ticksper) / irtc->hz;
+ }
+AIO_SET_INTERRUPT_LATENCY(rtc->currd * ticksper); /* set interrrupt latency */
+return rtc->currd;
}
/* Prior interfaces - default to timer 0 */
int32 sim_rtc_init (int32 time)
{
return sim_rtcn_init (time, 0);
}
-int32 sim_rtc_calb (int32 ticksper)
+int32 sim_rtc_calb (uint32 ticksper)
{
return sim_rtcn_calb (ticksper, 0);
}
/* sim_timer_init - get minimum sleep time available on this host */
@@ -1049,14 +1124,23 @@
{
int tmr;
uint32 clock_start, clock_last, clock_now;
sim_debug (DBG_TRC, &sim_timer_dev, "sim_timer_init()\n");
+/* Clear the event queue before initializing the timer subsystem */
+while (sim_clock_queue != QUEUE_LIST_END)
+ sim_cancel (sim_clock_queue);
for (tmr=0; tmr<=SIM_NTIMERS; tmr++) {
- sim_timer_units[tmr].action = &sim_timer_tick_svc;
- sim_timer_units[tmr].flags = UNIT_DIS | UNIT_IDLE;
- sim_clock_cosched_queue[tmr] = QUEUE_LIST_END;
+ RTC *rtc = &rtcs[tmr];
+
+ rtc->timer_unit = &sim_timer_units[tmr];
+ rtc->timer_unit->action = &sim_timer_tick_svc;
+ rtc->timer_unit->flags = UNIT_DIS | UNIT_IDLE;
+ if (rtc->clock_cosched_queue)
+ while (rtc->clock_cosched_queue != QUEUE_LIST_END)
+ sim_cancel (rtc->clock_cosched_queue);
+ rtc->clock_cosched_queue = QUEUE_LIST_END;
}
sim_stop_unit.action = &sim_timer_stop_svc;
SIM_INTERNAL_UNIT.flags = UNIT_IDLE;
sim_register_internal_device (&sim_timer_dev); /* Register Clock Assist device */
sim_register_internal_device (&sim_throttle_dev); /* Register Throttle Device */
@@ -1064,11 +1148,11 @@
sim_register_clock_unit_tmr (&SIM_INTERNAL_UNIT, SIM_INTERNAL_CLK);
sim_idle_enab = FALSE; /* init idle off */
sim_idle_rate_ms = sim_os_ms_sleep_init (); /* get OS timer rate */
sim_set_rom_delay_factor (sim_get_rom_delay_factor ()); /* initialize ROM delay factor */
-clock_last = clock_start = sim_os_msec ();
+sim_stop_time = clock_last = clock_start = sim_os_msec ();
sim_os_clock_resoluton_ms = 1000;
do {
uint32 clock_diff;
clock_now = sim_os_msec ();
@@ -1106,113 +1190,128 @@
struct timespec now;
time_t time_t_now;
int32 calb_tmr = (sim_calb_tmr == -1) ? sim_calb_tmr_last : sim_calb_tmr;
double inst_per_sec = sim_timer_inst_per_sec ();
-fprintf (st, "Minimum Host Sleep Time: %d ms (%dHz)\n", sim_os_sleep_min_ms, sim_os_tick_hz);
+fprintf (st, "Minimum Host Sleep Time: %d ms (%dHz)\n", sim_os_sleep_min_ms, sim_os_tick_hz);
if (sim_os_sleep_min_ms != sim_os_sleep_inc_ms)
- fprintf (st, "Minimum Host Sleep Incr Time: %d ms\n", sim_os_sleep_inc_ms);
-fprintf (st, "Host Clock Resolution: %d ms\n", sim_os_clock_resoluton_ms);
-fprintf (st, "Execution Rate: %s cycles/sec\n", sim_fmt_numeric (inst_per_sec));
+ fprintf (st, "Minimum Host Sleep Incr Time: %d ms\n", sim_os_sleep_inc_ms);
+fprintf (st, "Host Clock Resolution: %d ms\n", sim_os_clock_resoluton_ms);
+fprintf (st, "Execution Rate: %s %s/sec\n", sim_fmt_numeric (inst_per_sec), sim_vm_interval_units);
if (sim_idle_enab) {
- fprintf (st, "Idling: Enabled\n");
- fprintf (st, "Time before Idling starts: %d seconds\n", sim_idle_stable);
+ fprintf (st, "Idling: Enabled\n");
+ fprintf (st, "Time before Idling starts: %d seconds\n", sim_idle_stable);
}
if (sim_throt_type != SIM_THROT_NONE) {
sim_show_throt (st, NULL, uptr, val, desc);
}
-fprintf (st, "Calibrated Timer: %s\n", (calb_tmr == -1) ? "Undetermined" :
- ((calb_tmr == SIM_NTIMERS) ? "Internal Timer" :
- (sim_clock_unit[calb_tmr] ? sim_uname(sim_clock_unit[calb_tmr]) : "")));
+fprintf (st, "Calibrated Timer: %s\n", (calb_tmr == -1) ? "Undetermined" :
+ ((calb_tmr == SIM_NTIMERS) ? "Internal Timer" :
+ (rtcs[calb_tmr].clock_unit ? sim_uname(rtcs[calb_tmr].clock_unit) : "")));
if (calb_tmr == SIM_NTIMERS)
- fprintf (st, "Catchup Ticks: %s for clocks ticking faster than %d Hz\n", sim_catchup_ticks ? "Enabled" : "Disabled", sim_os_tick_hz);
-if (sim_idle_calib_pct == 0)
- fprintf (st, "Calibration: Always\n");
+ fprintf (st, "Catchup Ticks: %s\n", sim_catchup_ticks ? "Enabled" : "Disabled");
+fprintf (st, "Pre-Calibration Estimated Rate: %s\n", sim_fmt_numeric ((double)sim_precalibrate_ips));
+if (sim_idle_calib_pct == 100)
+ fprintf (st, "Calibration: Always\n");
else
- fprintf (st, "Calibration: Skipped when Idle exceeds %d%%\n", sim_idle_calib_pct);
+ fprintf (st, "Calibration: Skipped when Idle exceeds %d%%\n", sim_idle_calib_pct);
+#if defined(SIM_ASYNCH_CLOCKS)
+fprintf (st, "Asynchronous Clocks: %s\n", sim_asynch_timer ? "Active" : "Available");
+#endif
+if (sim_time_at_sim_prompt != 0.0) {
+ double prompt_time = 0.0;
+ if (!sim_is_running)
+ prompt_time = ((double)(sim_os_msec () - sim_stop_time)) / 1000.0;
+ fprintf (st, "Time at sim> prompt: %s\n", sim_fmt_secs (sim_time_at_sim_prompt + prompt_time));
+ }
+
fprintf (st, "\n");
for (tmr=clocks=0; tmr<=SIM_NTIMERS; ++tmr) {
- if (0 == rtc_initd[tmr])
+ RTC *rtc = &rtcs[tmr];
+
+ if (0 == rtc->initd)
continue;
- if (sim_clock_unit[tmr]) {
+ if (rtc->clock_unit) {
++clocks;
fprintf (st, "%s clock device is %s%s%s\n", sim_name,
(tmr == SIM_NTIMERS) ? "Internal Calibrated Timer(" : "",
- sim_uname(sim_clock_unit[tmr]),
+ sim_uname(rtc->clock_unit),
(tmr == SIM_NTIMERS) ? ")" : "");
}
- fprintf (st, "%s%sTimer %d:\n", sim_asynch_timer ? "Asynchronous " : "", rtc_hz[tmr] ? "Calibrated " : "Uncalibrated ", tmr);
- if (rtc_hz[tmr]) {
- fprintf (st, " Running at: %d Hz\n", rtc_hz[tmr]);
- fprintf (st, " Tick Size: %s\n", sim_fmt_secs (rtc_clock_tick_size[tmr]));
- fprintf (st, " Ticks in current second: %d\n", rtc_ticks[tmr]);
+ fprintf (st, "%s%sTimer %d:\n", sim_asynch_timer ? "Asynchronous " : "", rtc->hz ? "Calibrated " : "Uncalibrated ", tmr);
+ if (rtc->hz) {
+ fprintf (st, " Running at: %d Hz\n", rtc->hz);
+ fprintf (st, " Tick Size: %s\n", sim_fmt_secs (rtc->clock_tick_size));
+ fprintf (st, " Ticks in current second: %d\n", rtc->ticks);
}
- fprintf (st, " Seconds Running: %s (%s)\n", sim_fmt_numeric ((double)rtc_elapsed[tmr]), sim_fmt_secs ((double)rtc_elapsed[tmr]));
+ fprintf (st, " Seconds Running: %s (%s)\n", sim_fmt_numeric ((double)rtc->elapsed), sim_fmt_secs ((double)rtc->elapsed));
if (tmr == calb_tmr) {
- fprintf (st, " Calibration Opportunities: %s\n", sim_fmt_numeric ((double)rtc_calibrations[tmr]));
- if (sim_idle_calib_pct)
- fprintf (st, " Calib Skip Idle Thresh %%: %u\n", sim_idle_calib_pct);
- if (rtc_clock_calib_skip_idle[tmr])
- fprintf (st, " Calibs Skip While Idle: %u\n", rtc_clock_calib_skip_idle[tmr]);
- if (rtc_clock_calib_backwards[tmr])
- fprintf (st, " Calibs Skip Backwards: %u\n", rtc_clock_calib_backwards[tmr]);
- if (rtc_clock_calib_gap2big[tmr])
- fprintf (st, " Calibs Skip Gap Too Big: %u\n", rtc_clock_calib_gap2big[tmr]);
- }
- if (rtc_gtime[tmr])
- fprintf (st, " Instruction Time: %.0f\n", rtc_gtime[tmr]);
+ fprintf (st, " Calibration Opportunities: %s\n", sim_fmt_numeric ((double)rtc->calibrations));
+ if (sim_idle_calib_pct && (sim_idle_calib_pct != 100))
+ fprintf (st, " Calib Skip when Idle >: %u%%\n", sim_idle_calib_pct);
+ if (rtc->clock_calib_skip_idle)
+ fprintf (st, " Calibs Skip While Idle: %s\n", sim_fmt_numeric ((double)rtc->clock_calib_skip_idle));
+ if (rtc->clock_calib_backwards)
+ fprintf (st, " Calibs Skip Backwards: %s\n", sim_fmt_numeric ((double)rtc->clock_calib_backwards));
+ if (rtc->clock_calib_gap2big)
+ fprintf (st, " Calibs Skip Gap Too Big: %s\n", sim_fmt_numeric ((double)rtc->clock_calib_gap2big));
+ }
+ if (rtc->gtime)
+ fprintf (st, " Instruction Time: %.0f\n", rtc->gtime);
if ((!sim_asynch_timer) && (sim_throt_type == SIM_THROT_NONE)) {
- fprintf (st, " Real Time: %u\n", rtc_rtime[tmr]);
- fprintf (st, " Virtual Time: %u\n", rtc_vtime[tmr]);
- fprintf (st, " Next Interval: %s\n", sim_fmt_numeric ((double)rtc_nxintv[tmr]));
- fprintf (st, " Base Tick Delay: %s\n", sim_fmt_numeric ((double)rtc_based[tmr]));
- fprintf (st, " Initial Insts Per Tick: %s\n", sim_fmt_numeric ((double)rtc_initd[tmr]));
- }
- fprintf (st, " Current Insts Per Tick: %s\n", sim_fmt_numeric ((double)rtc_currd[tmr]));
- fprintf (st, " Initializations: %d\n", rtc_calib_initializations[tmr]);
- fprintf (st, " Ticks: %s\n", sim_fmt_numeric ((double)(rtc_clock_ticks[tmr])));
- if (rtc_clock_ticks_tot[tmr]+rtc_clock_ticks[tmr] != rtc_clock_ticks[tmr])
- fprintf (st, " Total Ticks: %s\n", sim_fmt_numeric ((double)(rtc_clock_ticks_tot[tmr]+rtc_clock_ticks[tmr])));
- if (rtc_clock_skew_max[tmr] != 0.0)
- fprintf (st, " Peak Clock Skew: %s%s\n", sim_fmt_secs (fabs(rtc_clock_skew_max[tmr])), (rtc_clock_skew_max[tmr] < 0) ? " fast" : " slow");
- if (rtc_calib_ticks_acked[tmr])
- fprintf (st, " Ticks Acked: %s\n", sim_fmt_numeric ((double)rtc_calib_ticks_acked[tmr]));
- if (rtc_calib_ticks_acked_tot[tmr]+rtc_calib_ticks_acked[tmr] != rtc_calib_ticks_acked[tmr])
- fprintf (st, " Total Ticks Acked: %s\n", sim_fmt_numeric ((double)(rtc_calib_ticks_acked_tot[tmr]+rtc_calib_ticks_acked[tmr])));
- if (rtc_calib_tick_time[tmr])
- fprintf (st, " Tick Time: %s\n", sim_fmt_secs (rtc_calib_tick_time[tmr]));
- if (rtc_calib_tick_time_tot[tmr]+rtc_calib_tick_time[tmr] != rtc_calib_tick_time[tmr])
- fprintf (st, " Total Tick Time: %s\n", sim_fmt_secs (rtc_calib_tick_time_tot[tmr]+rtc_calib_tick_time[tmr]));
- if (rtc_clock_catchup_ticks[tmr])
- fprintf (st, " Catchup Ticks Sched: %s\n", sim_fmt_numeric ((double)rtc_clock_catchup_ticks[tmr]));
- if (rtc_clock_catchup_ticks_tot[tmr]+rtc_clock_catchup_ticks[tmr] != rtc_clock_catchup_ticks[tmr])
- fprintf (st, " Total Catchup Ticks Sched: %s\n", sim_fmt_numeric ((double)(rtc_clock_catchup_ticks_tot[tmr]+rtc_clock_catchup_ticks[tmr])));
- if (rtc_clock_init_base_time[tmr]) {
- _double_to_timespec (&now, rtc_clock_init_base_time[tmr]);
+ fprintf (st, " Real Time: %u\n", rtc->rtime);
+ fprintf (st, " Virtual Time: %u\n", rtc->vtime);
+ fprintf (st, " Next Interval: %s\n", sim_fmt_numeric ((double)rtc->nxintv));
+ fprintf (st, " Base Tick Delay: %s\n", sim_fmt_numeric ((double)rtc->based));
+ fprintf (st, " Initial Insts Per Tick: %s\n", sim_fmt_numeric ((double)rtc->initd));
+ }
+ fprintf (st, " Current Insts Per Tick: %s\n", sim_fmt_numeric ((double)rtc->currd));
+ fprintf (st, " Initializations: %d\n", rtc->calib_initializations);
+ fprintf (st, " Ticks: %s\n", sim_fmt_numeric ((double)(rtc->clock_ticks)));
+ if (rtc->clock_ticks_tot+rtc->clock_ticks != rtc->clock_ticks)
+ fprintf (st, " Total Ticks: %s\n", sim_fmt_numeric ((double)(rtc->clock_ticks_tot+rtc->clock_ticks)));
+ if (rtc->clock_skew_max != 0.0)
+ fprintf (st, " Peak Clock Skew: %s%s\n", sim_fmt_secs (fabs(rtc->clock_skew_max)), (rtc->clock_skew_max < 0) ? " fast" : " slow");
+ if (rtc->calib_ticks_acked)
+ fprintf (st, " Ticks Acked: %s\n", sim_fmt_numeric ((double)rtc->calib_ticks_acked));
+ if (rtc->calib_ticks_acked_tot+rtc->calib_ticks_acked != rtc->calib_ticks_acked)
+ fprintf (st, " Total Ticks Acked: %s\n", sim_fmt_numeric ((double)(rtc->calib_ticks_acked_tot+rtc->calib_ticks_acked)));
+ if (rtc->calib_tick_time)
+ fprintf (st, " Tick Time: %s\n", sim_fmt_secs (rtc->calib_tick_time));
+ if (rtc->calib_tick_time_tot+rtc->calib_tick_time != rtc->calib_tick_time)
+ fprintf (st, " Total Tick Time: %s\n", sim_fmt_secs (rtc->calib_tick_time_tot+rtc->calib_tick_time));
+ if (rtc->clock_catchup_ticks)
+ fprintf (st, " Catchup Ticks Sched: %s\n", sim_fmt_numeric ((double)rtc->clock_catchup_ticks));
+ if (rtc->clock_catchup_ticks_curr)
+ fprintf (st, " Catchup Ticks this second: %s\n", sim_fmt_numeric ((double)rtc->clock_catchup_ticks_curr));
+ if (rtc->clock_catchup_ticks_tot+rtc->clock_catchup_ticks != rtc->clock_catchup_ticks)
+ fprintf (st, " Total Catchup Ticks Sched: %s\n", sim_fmt_numeric ((double)(rtc->clock_catchup_ticks_tot+rtc->clock_catchup_ticks)));
+ if (rtc->clock_init_base_time) {
+ _double_to_timespec (&now, rtc->clock_init_base_time);
time_t_now = (time_t)now.tv_sec;
fprintf (st, " Initialize Base Time: %8.8s.%03d\n", 11+ctime(&time_t_now), (int)(now.tv_nsec/1000000));
}
- if (rtc_clock_tick_start_time[tmr]) {
- _double_to_timespec (&now, rtc_clock_tick_start_time[tmr]);
+ if (rtc->clock_tick_start_time) {
+ _double_to_timespec (&now, rtc->clock_tick_start_time);
time_t_now = (time_t)now.tv_sec;
fprintf (st, " Tick Start Time: %8.8s.%03d\n", 11+ctime(&time_t_now), (int)(now.tv_nsec/1000000));
}
clock_gettime (CLOCK_REALTIME, &now);
time_t_now = (time_t)now.tv_sec;
fprintf (st, " Wall Clock Time Now: %8.8s.%03d\n", 11+ctime(&time_t_now), (int)(now.tv_nsec/1000000));
- if (rtc_clock_catchup_eligible[tmr]) {
- _double_to_timespec (&now, rtc_clock_catchup_base_time[tmr]+rtc_calib_tick_time[tmr]);
+ if (sim_catchup_ticks && rtc->clock_catchup_eligible) {
+ _double_to_timespec (&now, rtc->clock_catchup_base_time+rtc->calib_tick_time);
time_t_now = (time_t)now.tv_sec;
fprintf (st, " Catchup Tick Time: %8.8s.%03d\n", 11+ctime(&time_t_now), (int)(now.tv_nsec/1000000));
- _double_to_timespec (&now, rtc_clock_catchup_base_time[tmr]);
+ _double_to_timespec (&now, rtc->clock_catchup_base_time);
time_t_now = (time_t)now.tv_sec;
fprintf (st, " Catchup Base Time: %8.8s.%03d\n", 11+ctime(&time_t_now), (int)(now.tv_nsec/1000000));
}
- if (rtc_clock_time_idled[tmr])
- fprintf (st, " Total Time Idled: %s\n", sim_fmt_secs (rtc_clock_time_idled[tmr]/1000.0));
+ if (rtc->clock_time_idled)
+ fprintf (st, " Total Time Idled: %s\n", sim_fmt_secs (rtc->clock_time_idled/1000.0));
}
if (clocks == 0)
fprintf (st, "%s clock device is not specified, co-scheduling is unavailable\n", sim_name);
return SCPE_OK;
}
@@ -1247,19 +1346,21 @@
}
}
}
#endif /* SIM_ASYNCH_CLOCKS */
for (tmr=0; tmr<=SIM_NTIMERS; ++tmr) {
- if (sim_clock_unit[tmr] == NULL)
+ RTC *rtc = &rtcs[tmr];
+
+ if (rtc->clock_unit == NULL)
continue;
- if (sim_clock_cosched_queue[tmr] != QUEUE_LIST_END) {
+ if (rtc->clock_cosched_queue != QUEUE_LIST_END) {
int32 accum;
fprintf (st, "%s #%d clock (%s) co-schedule event queue status\n",
- sim_name, tmr, sim_uname(sim_clock_unit[tmr]));
+ sim_name, tmr, sim_uname(rtc->clock_unit));
accum = 0;
- for (uptr = sim_clock_cosched_queue[tmr]; uptr != QUEUE_LIST_END; uptr = uptr->next) {
+ for (uptr = rtc->clock_cosched_queue; uptr != QUEUE_LIST_END; uptr = uptr->next) {
if ((dptr = find_dev_from_unit (uptr)) != NULL) {
fprintf (st, " %s", sim_dname (dptr));
if (dptr->numunits > 1)
fprintf (st, " unit %d", (int32) (uptr - dptr->units));
}
@@ -1285,28 +1386,28 @@
REG sim_timer_reg[] = {
{ DRDATAD (IDLE_CYC_MS, sim_idle_cyc_ms, 32, "Cycles Per Millisecond"), PV_RSPC|REG_RO},
{ DRDATAD (IDLE_CYC_SLEEP, sim_idle_cyc_sleep, 32, "Cycles Per Minimum Sleep"), PV_RSPC|REG_RO},
{ DRDATAD (IDLE_STABLE, sim_idle_stable, 32, "IDLE stability delay"), PV_RSPC},
{ DRDATAD (ROM_DELAY, sim_rom_delay, 32, "ROM memory reference delay"), PV_RSPC|REG_RO},
- { DRDATAD (TICK_RATE_0, rtc_hz[0], 32, "Timer 0 Ticks Per Second") },
- { DRDATAD (TICK_SIZE_0, rtc_currd[0], 32, "Timer 0 Tick Size") },
- { DRDATAD (TICK_RATE_1, rtc_hz[1], 32, "Timer 1 Ticks Per Second") },
- { DRDATAD (TICK_SIZE_1, rtc_currd[1], 32, "Timer 1 Tick Size") },
- { DRDATAD (TICK_RATE_2, rtc_hz[2], 32, "Timer 2 Ticks Per Second") },
- { DRDATAD (TICK_SIZE_2, rtc_currd[2], 32, "Timer 2 Tick Size") },
- { DRDATAD (TICK_RATE_3, rtc_hz[3], 32, "Timer 3 Ticks Per Second") },
- { DRDATAD (TICK_SIZE_3, rtc_currd[3], 32, "Timer 3 Tick Size") },
- { DRDATAD (TICK_RATE_4, rtc_hz[4], 32, "Timer 4 Ticks Per Second") },
- { DRDATAD (TICK_SIZE_4, rtc_currd[4], 32, "Timer 4 Tick Size") },
- { DRDATAD (TICK_RATE_5, rtc_hz[5], 32, "Timer 5 Ticks Per Second") },
- { DRDATAD (TICK_SIZE_5, rtc_currd[5], 32, "Timer 5 Tick Size") },
- { DRDATAD (TICK_RATE_6, rtc_hz[6], 32, "Timer 6 Ticks Per Second") },
- { DRDATAD (TICK_SIZE_6, rtc_currd[6], 32, "Timer 6 Tick Size") },
- { DRDATAD (TICK_RATE_7, rtc_hz[7], 32, "Timer 7 Ticks Per Second") },
- { DRDATAD (TICK_SIZE_7, rtc_currd[7], 32, "Timer 7 Tick Size") },
+ { DRDATAD (TICK_RATE_0, rtcs[0].hz, 32, "Timer 0 Ticks Per Second") },
+ { DRDATAD (TICK_SIZE_0, rtcs[0].currd, 32, "Timer 0 Tick Size") },
+ { DRDATAD (TICK_RATE_1, rtcs[1].hz, 32, "Timer 1 Ticks Per Second") },
+ { DRDATAD (TICK_SIZE_1, rtcs[1].currd, 32, "Timer 1 Tick Size") },
+ { DRDATAD (TICK_RATE_2, rtcs[2].hz, 32, "Timer 2 Ticks Per Second") },
+ { DRDATAD (TICK_SIZE_2, rtcs[2].currd, 32, "Timer 2 Tick Size") },
+ { DRDATAD (TICK_RATE_3, rtcs[3].hz, 32, "Timer 3 Ticks Per Second") },
+ { DRDATAD (TICK_SIZE_3, rtcs[3].currd, 32, "Timer 3 Tick Size") },
+ { DRDATAD (TICK_RATE_4, rtcs[4].hz, 32, "Timer 4 Ticks Per Second") },
+ { DRDATAD (TICK_SIZE_4, rtcs[4].currd, 32, "Timer 4 Tick Size") },
+ { DRDATAD (TICK_RATE_5, rtcs[5].hz, 32, "Timer 5 Ticks Per Second") },
+ { DRDATAD (TICK_SIZE_5, rtcs[5].currd, 32, "Timer 5 Tick Size") },
+ { DRDATAD (TICK_RATE_6, rtcs[6].hz, 32, "Timer 6 Ticks Per Second") },
+ { DRDATAD (TICK_SIZE_6, rtcs[6].currd, 32, "Timer 6 Tick Size") },
+ { DRDATAD (TICK_RATE_7, rtcs[7].hz, 32, "Timer 7 Ticks Per Second") },
+ { DRDATAD (TICK_SIZE_7, rtcs[7].currd, 32, "Timer 7 Tick Size") },
{ DRDATAD (INTERNAL_TICK_RATE,sim_int_clk_tps, 32, "Internal Timer Ticks Per Second") },
- { DRDATAD (INTERNAL_TICK_SIZE,rtc_currd[SIM_NTIMERS],32, "Internal Timer Tick Size") },
+ { DRDATAD (INTERNAL_TICK_SIZE,rtcs[SIM_NTIMERS].currd,32, "Internal Timer Tick Size") },
{ NULL }
};
REG sim_throttle_reg[] = {
{ DRDATAD (THROT_MS_START, sim_throt_ms_start, 32, "Throttle measurement start time"), PV_RSPC|REG_RO},
@@ -1330,12 +1431,13 @@
if (flag) {
if (!sim_catchup_ticks)
sim_catchup_ticks = TRUE;
}
else {
- if (sim_catchup_ticks)
+ if (sim_catchup_ticks) {
sim_catchup_ticks = FALSE;
+ }
}
return SCPE_OK;
}
t_stat sim_timer_show_catchup (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
@@ -1347,29 +1449,32 @@
/* Set idle calibration threshold */
t_stat sim_timer_set_idle_pct (int32 flag, CONST char *cptr)
{
t_stat r = SCPE_OK;
-int32 newpct;
-char gbuf[CBUFSIZE];
if (cptr == NULL)
return SCPE_ARG;
-cptr = get_glyph_nc (cptr, gbuf, 0); /* get argument */
-if (isdigit (gbuf[0]))
- newpct = (int32) get_uint (cptr, 10, 100, &r);
-else {
- if (MATCH_CMD (gbuf, "ALWAYS") == 0)
- newpct = 0;
- else
- r = SCPE_ARG;
- }
-if ((r != SCPE_OK) || (newpct == (int32)(sim_idle_calib_pct)))
- return r;
-if (newpct == 0)
- return SCPE_ARG;
-sim_idle_calib_pct = (uint32)newpct;
+if (1) {
+ int32 newpct;
+ char gbuf[CBUFSIZE];
+
+ cptr = get_glyph (cptr, gbuf, 0); /* get argument */
+ if (isdigit (gbuf[0]))
+ newpct = (int32) get_uint (gbuf, 10, 100, &r);
+ else {
+ if (MATCH_CMD (gbuf, "ALWAYS") == 0)
+ newpct = 100;
+ else
+ r = SCPE_ARG;
+ }
+ if ((r != SCPE_OK) || (newpct == (int32)(sim_idle_calib_pct)))
+ return r;
+ if (newpct == 0)
+ return SCPE_ARG;
+ sim_idle_calib_pct = (uint32)newpct;
+ }
return SCPE_OK;
}
/* Set stop time */
@@ -1518,38 +1623,41 @@
{
uint32 w_ms, w_idle, act_ms;
int32 act_cyc;
static t_bool in_nowait = FALSE;
double cyc_since_idle;
+RTC *rtc = &rtcs[tmr];
-if (rtc_clock_catchup_pending[tmr]) { /* Catchup clock tick pending? */
- sim_debug (DBG_CAL, &sim_timer_dev, "sim_idle(tmr=%d, sin_cyc=%d) - accelerating pending catch-up tick before idling %s\n", tmr, sin_cyc, sim_uname (sim_clock_unit[tmr]));
+if (rtc->hz == 0) /* specified timer is not running? */
+ tmr = sim_calb_tmr; /* use calibrated timer instead */
+rtc = &rtcs[tmr];
+if (rtc->clock_catchup_pending) { /* Catchup clock tick pending due to ack? */
+ sim_debug (DBG_TIK, &sim_timer_dev, "sim_idle(tmr=%d, sin_cyc=%d) - accelerating pending catch-up tick before idling %s\n", tmr, sin_cyc, sim_uname (rtc->clock_unit));
sim_activate_abs (&sim_timer_units[tmr], 0);
sim_interval -= sin_cyc;
return FALSE;
+ }
+if (_rtcn_tick_catchup_check (rtc, -1)) { /* Check for slow clock tick? */
+ sim_interval -= sin_cyc;
+ return FALSE;
}
if ((!sim_idle_enab) || /* idling disabled */
((sim_clock_queue == QUEUE_LIST_END) && /* or clock queue empty? */
(!sim_asynch_timer))|| /* and not asynch? */
((sim_clock_queue != QUEUE_LIST_END) && /* or clock queue not empty */
((sim_clock_queue->flags & UNIT_IDLE) == 0))|| /* and event not idle-able? */
- (rtc_elapsed[tmr] < sim_idle_stable)) { /* or timer not stable? */
- sim_debug (DBG_IDL, &sim_timer_dev, "Can't idle: %s - elapsed: %d.%03d\n", !sim_idle_enab ? "idle disabled" :
- ((rtc_elapsed[tmr] < sim_idle_stable) ? "not stable" :
+ (rtc->elapsed < sim_idle_stable)) { /* or calibrated timer not stable? */
+ sim_debug (DBG_IDL, &sim_timer_dev, "Can't idle: %s - elapsed: %d and %d/%d\n", !sim_idle_enab ? "idle disabled" :
+ ((rtc->elapsed < sim_idle_stable) ? "not stable" :
((sim_clock_queue != QUEUE_LIST_END) ? sim_uname (sim_clock_queue) :
- "")), rtc_elapsed[tmr], rtc_ticks[tmr]);
- sim_interval -= sin_cyc;
- return FALSE;
- }
-if (_rtcn_tick_catchup_check(tmr, 0)) {
- sim_debug (DBG_CAL, &sim_timer_dev, "sim_idle(tmr=%d, sin_cyc=%d) - rescheduling catchup tick for %s\n", tmr, sin_cyc, sim_uname (sim_clock_unit[tmr]));
+ "")), rtc->elapsed, rtc->ticks, rtc->hz);
sim_interval -= sin_cyc;
return FALSE;
}
/*
When a simulator is in an instruction path (or under other conditions
- which would indicate idling), the countdown of sim_interval will not
+ which would indicate idling), the countdown of sim_interval may not
be happening at a pace which is consistent with the rate it happens
when not in the 'idle capable' state. The consequence of this is that
the clock calibration may produce calibrated results which vary much
more than they do when not in the idle able state. Sim_idle also uses
the calibrated tick size to approximate an adjustment to sim_interval
@@ -1557,11 +1665,11 @@
the actual idle time, so consistent calibrated numbers produce better
adjustments.
To negate this effect, we accumulate the time actually idled here.
sim_rtcn_calb compares the accumulated idle time during the most recent
- second and if it exceeds the percentage defined by and sim_idle_calib_pct
+ second and if it exceeds the percentage defined by sim_idle_calib_pct
calibration is suppressed. Thus recalibration only happens if things
didn't idle too much.
we also check check sim_idle_enab above so that all simulators can avoid
directly checking sim_idle_enab before calling sim_idle so that all of
@@ -1568,13 +1676,13 @@
the bookkeeping on sim_idle_idled is done here in sim_timer where it
means something, while not idling when it isn't enabled.
*/
sim_debug (DBG_TRC, &sim_timer_dev, "sim_idle(tmr=%d, sin_cyc=%d)\n", tmr, sin_cyc);
if (sim_idle_cyc_ms == 0) {
- sim_idle_cyc_ms = (rtc_currd[tmr] * rtc_hz[tmr]) / 1000;/* cycles per msec */
+ sim_idle_cyc_ms = (rtc->currd * rtc->hz) / 1000;/* cycles per msec */
if (sim_idle_rate_ms != 0)
- sim_idle_cyc_sleep = (rtc_currd[tmr] * rtc_hz[tmr]) / (1000 / sim_idle_rate_ms);/* cycles per sleep */
+ sim_idle_cyc_sleep = (rtc->currd * rtc->hz) / (1000 / sim_idle_rate_ms);/* cycles per minimum sleep */
}
if ((sim_idle_rate_ms == 0) || (sim_idle_cyc_ms == 0)) {/* not possible? */
sim_interval -= sin_cyc;
sim_debug (DBG_IDL, &sim_timer_dev, "not possible idle_rate_ms=%d - cyc/ms=%d\n", sim_idle_rate_ms, sim_idle_cyc_ms);
return FALSE;
@@ -1583,40 +1691,42 @@
/* When the host system has a clock tick which is less frequent than the */
/* simulated system's clock, idling will cause delays which will miss */
/* simulated clock ticks. To accomodate this, and still allow idling, if */
/* the simulator acknowledges the processing of clock ticks, then catchup */
/* ticks can be used to make up for missed ticks. */
-if (rtc_clock_catchup_eligible[tmr])
- w_idle = (sim_interval * 1000) / rtc_currd[tmr]; /* 1000 * pending fraction of tick */
+if (rtc->clock_catchup_eligible)
+ w_idle = (sim_interval * 1000) / rtc->currd; /* 1000 * pending fraction of tick */
else
w_idle = (w_ms * 1000) / sim_idle_rate_ms; /* 1000 * intervals to wait */
-if (w_idle < 500) { /* shorter than 1/2 the interval? */
- sim_interval -= sin_cyc;
+if ((w_idle < 500) || (w_ms == 0)) { /* shorter than 1/2 the interval or */
+ sim_interval -= sin_cyc; /* minimal sleep time? */
if (!in_nowait)
sim_debug (DBG_IDL, &sim_timer_dev, "no wait, too short: %d usecs\n", w_idle);
in_nowait = TRUE;
return FALSE;
}
+if (w_ms > 1000) /* too long a wait (runaway calibration) */
+ sim_debug (DBG_TIK, &sim_timer_dev, "waiting too long: w_ms=%d usecs, w_idle=%d usecs, sim_interval=%d, rtc->currd=%d\n", w_ms, w_idle, sim_interval, rtc->currd);
in_nowait = FALSE;
if (sim_clock_queue == QUEUE_LIST_END)
- sim_debug (DBG_IDL, &sim_timer_dev, "sleeping for %d ms - pending event in %d instructions\n", w_ms, sim_interval);
+ sim_debug (DBG_IDL, &sim_timer_dev, "sleeping for %d ms - pending event in %d %s\n", w_ms, sim_interval, sim_vm_interval_units);
else
- sim_debug (DBG_IDL, &sim_timer_dev, "sleeping for %d ms - pending event on %s in %d instructions\n", w_ms, sim_uname(sim_clock_queue), sim_interval);
+ sim_debug (DBG_IDL, &sim_timer_dev, "sleeping for %d ms - pending event on %s in %d %s\n", w_ms, sim_uname(sim_clock_queue), sim_interval, sim_vm_interval_units);
cyc_since_idle = sim_gtime() - sim_idle_end_time; /* time since prior idle */
act_ms = sim_idle_ms_sleep (w_ms); /* wait */
-rtc_clock_time_idled[tmr] += act_ms;
+rtc->clock_time_idled += act_ms;
act_cyc = act_ms * sim_idle_cyc_ms;
if (cyc_since_idle > sim_idle_cyc_sleep)
act_cyc -= sim_idle_cyc_sleep / 2; /* account for half an interval's worth of cycles */
else
act_cyc -= (int32)cyc_since_idle; /* acount for cycles executed */
sim_interval = sim_interval - act_cyc; /* count down sim_interval to reflect idle period */
sim_idle_end_time = sim_gtime(); /* save idle completed time */
if (sim_clock_queue == QUEUE_LIST_END)
- sim_debug (DBG_IDL, &sim_timer_dev, "slept for %d ms - pending event in %d instructions\n", act_ms, sim_interval);
+ sim_debug (DBG_IDL, &sim_timer_dev, "slept for %d ms - pending event in %d %s\n", act_ms, sim_interval, sim_vm_interval_units);
else
- sim_debug (DBG_IDL, &sim_timer_dev, "slept for %d ms - pending event on %s in %d instructions\n", act_ms, sim_uname(sim_clock_queue), sim_interval);
+ sim_debug (DBG_IDL, &sim_timer_dev, "slept for %d ms - pending event on %s in %d %s\n", act_ms, sim_uname(sim_clock_queue), sim_interval, sim_vm_interval_units);
return TRUE;
}
/* Set idling - implicitly disables throttling */
@@ -1722,11 +1832,11 @@
}
}
if (sim_throt_type == SIM_THROT_SPC) /* Set initial value while correct one is determined */
sim_throt_cps = (int32)((1000.0 * sim_throt_val) / (double)sim_throt_sleep_time);
else
- sim_throt_cps = SIM_INITIAL_IPS;
+ sim_throt_cps = sim_precalibrate_ips;
return SCPE_OK;
}
t_stat sim_show_throt (FILE *st, DEVICE *dnotused, UNIT *unotused, int32 flag, CONST char *cptr)
{
@@ -1734,33 +1844,33 @@
fprintf (st, "Throttling: Not Available\n");
else {
switch (sim_throt_type) {
case SIM_THROT_MCYC:
- fprintf (st, "Throttle: %d megacycles\n", sim_throt_val);
+ fprintf (st, "Throttle: %d mega%s\n", sim_throt_val, sim_vm_interval_units);
if (sim_throt_wait)
- fprintf (st, "Throttling by sleeping for: %d ms every %d cycles\n", sim_throt_sleep_time, sim_throt_wait);
+ fprintf (st, "Throttling by sleeping for: %d ms every %d %s\n", sim_throt_sleep_time, sim_throt_wait, sim_vm_interval_units);
break;
case SIM_THROT_KCYC:
- fprintf (st, "Throttle: %d kilocycles\n", sim_throt_val);
+ fprintf (st, "Throttle: %d kilo%s\n", sim_throt_val, sim_vm_interval_units);
if (sim_throt_wait)
- fprintf (st, "Throttling by sleeping for: %d ms every %d cycles\n", sim_throt_sleep_time, sim_throt_wait);
+ fprintf (st, "Throttling by sleeping for: %d ms every %d %s\n", sim_throt_sleep_time, sim_throt_wait, sim_vm_interval_units);
break;
case SIM_THROT_PCT:
if (sim_throt_wait) {
- fprintf (st, "Throttle: %d%% of %s cycles per second\n", sim_throt_val, sim_fmt_numeric (sim_throt_peak_cps));
- fprintf (st, "Throttling by sleeping for: %d ms every %d cycles\n", sim_throt_sleep_time, sim_throt_wait);
+ fprintf (st, "Throttle: %d%% of %s %s per second\n", sim_throt_val, sim_fmt_numeric (sim_throt_peak_cps), sim_vm_interval_units);
+ fprintf (st, "Throttling by sleeping for: %d ms every %d %s\n", sim_throt_sleep_time, sim_throt_wait, sim_vm_interval_units);
}
else
fprintf (st, "Throttle: %d%%\n", sim_throt_val);
break;
case SIM_THROT_SPC:
fprintf (st, "Throttle: %d/%d\n", sim_throt_val, sim_throt_sleep_time);
- fprintf (st, "Throttling by sleeping for: %d ms every %d cycles\n", sim_throt_sleep_time, sim_throt_val);
+ fprintf (st, "Throttling by sleeping for: %d ms every %d %s\n", sim_throt_sleep_time, sim_throt_val, sim_vm_interval_units);
break;
default:
fprintf (st, "Throttling: Disabled\n");
break;
@@ -1808,25 +1918,28 @@
t_stat sim_throt_svc (UNIT *uptr)
{
int32 tmr;
uint32 delta_ms;
double a_cps, d_cps, delta_inst;
+RTC *rtc = NULL;
+if (sim_calb_tmr != -1)
+ rtc = &rtcs[sim_calb_tmr];
switch (sim_throt_state) {
case SIM_THROT_STATE_INIT: /* take initial reading */
- if ((sim_calb_tmr != -1) && (rtc_hz[sim_calb_tmr] != 0)) {
- if (rtc_calibrations[sim_calb_tmr] < sim_throt_delay) {
+ if ((sim_calb_tmr != -1) && (rtc->hz != 0)) {
+ if (rtc->calibrations < sim_throt_delay) {
sim_throt_ms_start = sim_os_msec ();
sim_throt_inst_start = sim_gtime ();
- sim_debug (DBG_THR, &sim_timer_dev, "sim_throt_svc(INIT) Deferring until stable (%d more seconds)\n", (int)(sim_throt_delay - rtc_calibrations[sim_calb_tmr]));
- return sim_activate (uptr, rtc_hz[sim_calb_tmr]*rtc_currd[sim_calb_tmr]);
+ sim_debug (DBG_THR, &sim_timer_dev, "sim_throt_svc(INIT) Deferring until stable (%d more seconds)\n", (int)(sim_throt_delay - rtc->calibrations));
+ return sim_activate (uptr, rtc->hz * rtc->currd);
}
sim_debug (DBG_THR, &sim_timer_dev, "sim_throt_svc(INIT) Computing Throttling values based on the last second's execution rate\n");
sim_throt_state = SIM_THROT_STATE_TIME;
- if (sim_throt_peak_cps < (double)(rtc_hz[sim_calb_tmr] * rtc_currd[sim_calb_tmr]))
- sim_throt_peak_cps = (double)rtc_hz[sim_calb_tmr] * rtc_currd[sim_calb_tmr];
+ if (sim_throt_peak_cps < (double)(rtc->hz * rtc->currd))
+ sim_throt_peak_cps = (double)rtc->hz * rtc->currd;
return sim_throt_svc (uptr);
}
else
sim_debug (DBG_THR, &sim_timer_dev, "sim_throt_svc(INIT) Calibrated timer not available. Falling back to legacy method\n");
sim_idle_ms_sleep (sim_idle_rate_ms); /* start on a tick boundary to calibrate */
@@ -1863,12 +1976,12 @@
sim_throt_state = SIM_THROT_STATE_INIT; /* fails in 32b! */
sim_printf ("Can't throttle. Host CPU is too fast with a minimum sleep time of %d ms\n", sim_idle_rate_ms);
sim_set_throt (0, NULL); /* disable throttling */
return SCPE_OK;
}
- sim_debug (DBG_THR, &sim_timer_dev, "sim_throt_svc() Not enough time. %d ms executing %.f instructions.\n",
- (int)delta_ms, delta_inst);
+ sim_debug (DBG_THR, &sim_timer_dev, "sim_throt_svc() Not enough time. %d ms executing %.f %s.\n",
+ (int)delta_ms, delta_inst, sim_vm_interval_units);
sim_throt_wait = (int32)(delta_inst * SIM_THROT_WMUL);
sim_throt_inst_start = sim_gtime();
sim_idle_ms_sleep (sim_idle_rate_ms); /* start on a tick boundart to calibrate */
sim_throt_ms_start = sim_os_msec ();
}
@@ -1884,12 +1997,12 @@
if (d_cps > a_cps) {
sim_debug (DBG_THR, &sim_timer_dev, "sim_throt_svc() CPU too slow. Values a_cps = %f, d_cps = %f\n",
a_cps, d_cps);
sim_throt_state = SIM_THROT_STATE_INIT;
sim_printf ("*********** WARNING ***********\n");
- sim_printf ("Host CPU is too slow to simulate %s instructions per second\n", sim_fmt_numeric(d_cps));
- sim_printf ("Host CPU can only simulate %s instructions per second\n", sim_fmt_numeric(sim_throt_peak_cps));
+ sim_printf ("Host CPU is too slow to simulate %s %s per second\n", sim_fmt_numeric(d_cps), sim_vm_interval_units);
+ sim_printf ("Host CPU can only simulate %s %s per second\n", sim_fmt_numeric(sim_throt_peak_cps), sim_vm_interval_units);
sim_printf ("Throttling disabled.\n");
sim_set_throt (0, NULL);
return SCPE_OK;
}
while (1) {
@@ -1909,19 +2022,20 @@
a_cps, d_cps, sim_throt_wait, sim_throt_sleep_time);
sim_throt_cps = d_cps; /* save the desired rate */
/* Run through all timers and adjust the calibration for each */
/* one that is running to reflect the throttle rate */
for (tmr=0; tmr<=SIM_NTIMERS; tmr++) {
- if (rtc_hz[tmr]) { /* running? */
- rtc_currd[tmr] = (int32)(sim_throt_cps / rtc_hz[tmr]);/* use throttle calibration */
- rtc_ticks[tmr] = rtc_hz[tmr] - 1; /* force clock calibration on next tick */
- rtc_rtime[tmr] = sim_throt_ms_start - 1000 + 1000/rtc_hz[tmr];/* adjust calibration parameters to reflect throttled rate */
- rtc_gtime[tmr] = sim_throt_inst_start - sim_throt_cps + sim_throt_cps/rtc_hz[tmr];
- rtc_nxintv[tmr] = 1000;
- rtc_based[tmr] = rtc_currd[tmr];
- if (sim_clock_unit[tmr])
- sim_activate_abs (sim_clock_unit[tmr], rtc_currd[tmr]);/* reschedule next tick */
+ rtc = &rtcs[tmr];
+ if (rtc->hz) { /* running? */
+ rtc->currd = (int32)(sim_throt_cps / rtc->hz);/* use throttle calibration */
+ rtc->ticks = rtc->hz - 1; /* force clock calibration on next tick */
+ rtc->rtime = sim_throt_ms_start - 1000 + 1000/rtc->hz;/* adjust calibration parameters to reflect throttled rate */
+ rtc->gtime = sim_throt_inst_start - sim_throt_cps + sim_throt_cps/rtc->hz;
+ rtc->nxintv = 1000;
+ rtc->based = rtc->currd;
+ if (rtc->clock_unit)
+ sim_activate_abs (rtc->clock_unit, rtc->currd);/* reschedule next tick */
}
}
}
break;
@@ -1988,56 +2102,57 @@
/* Clock assist activites */
t_stat sim_timer_tick_svc (UNIT *uptr)
{
int32 tmr = (int32)(uptr-sim_timer_units);
t_stat stat;
+RTC *rtc = &rtcs[tmr];
-rtc_clock_ticks[tmr] += 1;
-rtc_calib_tick_time[tmr] += rtc_clock_tick_size[tmr];
+rtc->clock_ticks += 1;
+rtc->calib_tick_time += rtc->clock_tick_size;
/*
* Some devices may depend on executing during the same instruction or
* immediately after the clock tick event. To satisfy this, we directly
* run the clock event here and if it completes successfully, schedule any
* currently coschedule units to run now. Ticks should never return a
* non-success status, while co-schedule activities might, so they are
* queued to run from sim_process_event
*/
-sim_debug (DBG_QUE, &sim_timer_dev, "sim_timer_tick_svc(tmr=%d) - scheduling %s - cosched interval: %d\n", tmr, sim_uname (sim_clock_unit[tmr]), sim_cosched_interval[tmr]);
-if (sim_clock_unit[tmr]->action == NULL)
+sim_debug (DBG_QUE, &sim_timer_dev, "sim_timer_tick_svc(tmr=%d) - scheduling %s - cosched interval: %d\n", tmr, sim_uname (rtc->clock_unit), rtc->cosched_interval);
+if (rtc->clock_unit->action == NULL)
return SCPE_IERR;
-stat = sim_clock_unit[tmr]->action (sim_clock_unit[tmr]);
---sim_cosched_interval[tmr]; /* Countdown ticks */
-if (sim_clock_cosched_queue[tmr] != QUEUE_LIST_END)
- sim_clock_cosched_queue[tmr]->time = sim_cosched_interval[tmr];
+stat = rtc->clock_unit->action (rtc->clock_unit);
+--rtc->cosched_interval; /* Countdown ticks */
+if (rtc->clock_cosched_queue != QUEUE_LIST_END)
+ rtc->clock_cosched_queue->time = rtc->cosched_interval;
if ((stat == SCPE_OK) &&
- (sim_cosched_interval[tmr] <= 0) &&
- (sim_clock_cosched_queue[tmr] != QUEUE_LIST_END)) {
- UNIT *sptr = sim_clock_cosched_queue[tmr];
+ (rtc->cosched_interval <= 0) &&
+ (rtc->clock_cosched_queue != QUEUE_LIST_END)) {
+ UNIT *sptr = rtc->clock_cosched_queue;
UNIT *cptr = QUEUE_LIST_END;
- if (rtc_clock_catchup_eligible[tmr]) { /* calibration started? */
+ if (rtc->clock_catchup_eligible) { /* calibration started? */
struct timespec now;
double skew;
clock_gettime(CLOCK_REALTIME, &now);
- skew = (_timespec_to_double(&now) - (rtc_calib_tick_time[tmr]+rtc_clock_catchup_base_time[tmr]));
+ skew = (_timespec_to_double(&now) - (rtc->calib_tick_time+rtc->clock_catchup_base_time));
- if (fabs(skew) > fabs(rtc_clock_skew_max[tmr]))
- rtc_clock_skew_max[tmr] = skew;
+ if (fabs(skew) > fabs(rtc->clock_skew_max))
+ rtc->clock_skew_max = skew;
}
/* Gather any queued events which are scheduled for right now */
do {
- cptr = sim_clock_cosched_queue[tmr];
- sim_clock_cosched_queue[tmr] = cptr->next;
- if (sim_clock_cosched_queue[tmr] != QUEUE_LIST_END) {
- sim_clock_cosched_queue[tmr]->time += sim_cosched_interval[tmr];
- sim_cosched_interval[tmr] = sim_clock_cosched_queue[tmr]->time;
+ cptr = rtc->clock_cosched_queue;
+ rtc->clock_cosched_queue = cptr->next;
+ if (rtc->clock_cosched_queue != QUEUE_LIST_END) {
+ rtc->clock_cosched_queue->time += rtc->cosched_interval;
+ rtc->cosched_interval = rtc->clock_cosched_queue->time;
}
else
- sim_cosched_interval[tmr] = 0;
- } while ((sim_cosched_interval[tmr] <= 0) &&
- (sim_clock_cosched_queue[tmr] != QUEUE_LIST_END));
+ rtc->cosched_interval = 0;
+ } while ((rtc->cosched_interval <= 0) &&
+ (rtc->clock_cosched_queue != QUEUE_LIST_END));
if (cptr != QUEUE_LIST_END)
cptr->next = QUEUE_LIST_END;
/* Now dispatch that list (in order). */
while (sptr != QUEUE_LIST_END) {
cptr = sptr;
@@ -2094,64 +2209,95 @@
* system has accepted the most recent clock tick interrupt.
* 2) immediately when the simulator calls sim_idle
*
* catchup ticks are only scheduled (eligible to happen) under these
* conditions after at least one tick has been acknowledged.
+ *
+ * The clock tick UNIT that will be scheduled to run for catchup ticks
+ * must be specified with sim_rtcn_init_unit().
*/
/* _rtcn_tick_catchup_check - idle simulator until next event or for specified interval
Inputs:
- tmr = calibrated timer to check/schedule
+ RTC = calibrated timer to check/schedule
time = instruction delay for next tick
Returns TRUE if a catchup tick has been scheduled
*/
-static t_bool _rtcn_tick_catchup_check (int32 tmr, int32 time)
+static t_bool _rtcn_tick_catchup_check (RTC *rtc, int32 time)
{
-if ((!sim_catchup_ticks) ||
- ((tmr < 0) || (tmr >= SIM_NTIMERS)))
+int32 tmr;
+t_bool bReturn = FALSE;
+
+if (!sim_catchup_ticks)
return FALSE;
-if ((rtc_hz[tmr] > sim_os_tick_hz) && /* faster than host tick */
- (!rtc_clock_catchup_eligible[tmr]) && /* not eligible yet? */
+if (time == -1) {
+ for (tmr=0; tmr<=SIM_NTIMERS; tmr++) {
+ rtc = &rtcs[tmr];
+ if ((rtc->hz > 0) && rtc->clock_catchup_eligible)
+ {
+ double tnow = sim_timenow_double();
+
+ if (tnow > (rtc->clock_catchup_base_time + (rtc->calib_tick_time + rtc->clock_tick_size))) {
+ if (!rtc->clock_catchup_pending) {
+ sim_debug (DBG_TIK, &sim_timer_dev, "_rtcn_tick_catchup_check(%d) - scheduling catchup tick %d for %s which is behind %s\n", time, 1 + rtc->ticks, sim_uname (rtc->clock_unit), sim_fmt_secs (tnow - (rtc->clock_catchup_base_time + (rtc->calib_tick_time + rtc->clock_tick_size))));
+ rtc->clock_catchup_pending = TRUE;
+ sim_activate_abs (rtc->timer_unit, 0);
+ bReturn = TRUE;
+ }
+ else
+ sim_debug (DBG_TIK, &sim_timer_dev, "_rtcn_tick_catchup_check(%d) - already pending catchup tick %d for %s which is behind %s\n", time, 1 + rtc->ticks, sim_uname (rtc->clock_unit), sim_fmt_secs (tnow - (rtc->clock_catchup_base_time + (rtc->calib_tick_time + rtc->clock_tick_size))));
+ }
+ }
+ }
+ }
+if ((!rtc->clock_catchup_eligible) && /* not eligible yet? */
(time != -1)) { /* called from ack? */
- rtc_clock_catchup_base_time[tmr] = sim_timenow_double();
- rtc_clock_ticks_tot[tmr] += rtc_clock_ticks[tmr];
- rtc_clock_ticks[tmr] = 0;
- rtc_calib_tick_time_tot[tmr] += rtc_calib_tick_time[tmr];
- rtc_calib_tick_time[tmr] = 0.0;
- rtc_clock_catchup_ticks_tot[tmr] += rtc_clock_catchup_ticks[tmr];
- rtc_clock_catchup_ticks[tmr] = 0;
- rtc_calib_ticks_acked_tot[tmr] += rtc_calib_ticks_acked[tmr];
- rtc_calib_ticks_acked[tmr] = 0;
- rtc_clock_catchup_eligible[tmr] = TRUE;
- sim_debug (DBG_QUE, &sim_timer_dev, "_rtcn_tick_catchup_check() - Enabling catchup ticks for %s\n", sim_uname (sim_clock_unit[tmr]));
- return TRUE;
- }
-if ((rtc_hz[tmr] > 0) &&
- rtc_clock_catchup_eligible[tmr])
+ rtc->clock_catchup_base_time = sim_timenow_double();
+ rtc->clock_ticks_tot += rtc->clock_ticks;
+ rtc->clock_ticks = 0;
+ rtc->calib_tick_time_tot += rtc->calib_tick_time;
+ rtc->calib_tick_time = 0.0;
+ rtc->clock_catchup_ticks_tot += rtc->clock_catchup_ticks;
+ rtc->clock_catchup_ticks = 0;
+ rtc->calib_ticks_acked_tot += rtc->calib_ticks_acked;
+ rtc->calib_ticks_acked = 0;
+ rtc->clock_catchup_eligible = TRUE;
+ sim_debug (DBG_QUE, &sim_timer_dev, "_rtcn_tick_catchup_check() - Enabling catchup ticks for %s\n", sim_uname (rtc->clock_unit));
+ bReturn = TRUE;
+ }
+if ((rtc->hz > 0) &&
+ rtc->clock_catchup_eligible)
{
double tnow = sim_timenow_double();
- if (tnow > (rtc_clock_catchup_base_time[tmr] + (rtc_calib_tick_time[tmr] + rtc_clock_tick_size[tmr]))) {
- sim_debug (DBG_QUE, &sim_timer_dev, "_rtcn_tick_catchup_check(%d) - scheduling catchup tick for %s which is behind %s\n", time, sim_uname (sim_clock_unit[tmr]), sim_fmt_secs (tnow > (rtc_clock_catchup_base_time[tmr] + (rtc_calib_tick_time[tmr] + rtc_clock_tick_size[tmr]))));
- rtc_clock_catchup_pending[tmr] = TRUE;
- sim_activate_abs (&sim_timer_units[tmr], (time < 0) ? 0 : time);
+ if (tnow > (rtc->clock_catchup_base_time + (rtc->calib_tick_time + rtc->clock_tick_size))) {
+ if (!rtc->clock_catchup_pending) {
+ sim_debug (DBG_TIK, &sim_timer_dev, "_rtcn_tick_catchup_check(%d) - scheduling catchup tick %d for %s which is behind %s\n", time, 1 + rtc->ticks, sim_uname (rtc->clock_unit), sim_fmt_secs (tnow - (rtc->clock_catchup_base_time + (rtc->calib_tick_time + rtc->clock_tick_size))));
+ rtc->clock_catchup_pending = TRUE;
+ sim_activate_abs (rtc->timer_unit, (time < 0) ? 0 : time);
+ }
+ else
+ sim_debug (DBG_TIK, &sim_timer_dev, "_rtcn_tick_catchup_check(%d) - already pending catchup tick %d for %s which is behind %s\n", time, 1 + rtc->ticks, sim_uname (rtc->clock_unit), sim_fmt_secs (tnow - (rtc->clock_catchup_base_time + (rtc->calib_tick_time + rtc->clock_tick_size))));
return TRUE;
}
}
-return FALSE;
+return bReturn;
}
t_stat sim_rtcn_tick_ack (uint32 time, int32 tmr)
{
-if ((tmr < 0) || (tmr >= SIM_NTIMERS))
+RTC *rtc;
+
+if ((tmr < 0) || (tmr > SIM_NTIMERS))
return SCPE_TIMER;
-sim_debug (DBG_ACK, &sim_timer_dev, "sim_rtcn_tick_ack - for %s\n", sim_uname (sim_clock_unit[tmr]));
-_rtcn_tick_catchup_check (tmr, (int32)time);
-++rtc_calib_ticks_acked[tmr];
+rtc = &rtcs[tmr];
+sim_debug (DBG_ACK, &sim_timer_dev, "sim_rtcn_tick_ack - for %s\n", sim_uname (rtc->clock_unit));
+_rtcn_tick_catchup_check (rtc, (int32)time);
+++rtc->calib_ticks_acked;
return SCPE_OK;
}
static double _timespec_to_double (struct timespec *time)
@@ -2280,21 +2426,22 @@
}
#endif /* defined(SIM_ASYNCH_CLOCKS) */
/*
- In the event that there are no active clock devices, no instruction
- rate calibration will be performed. This is more likely on simpler
- simulators which don't have a full spectrum of standard devices or
- possibly when a clock device exists but its use is optional.
-
- Additonally, when a host system has a natural clock tick (or minimal
- sleep time) which is greater than the tick size that a simulator
- wants to run a clock at, we run this clock at the rate implied by
- the host system's minimal sleep time or 50Hz.
-
- To solve this we merely run an internal clock at 10Hz.
+ In the event that there are no active calibrated clock devices,
+ no instruction rate calibration will be performed. This is more
+ likely on simpler simulators which don't have a full spectrum of
+ standard devices or possibly when a clock device exists but its
+ use is optional.
+
+ Additonally, when a host system has a natural clock tick (
+ or minimal sleep time) which is greater than the tick size that
+ a simulator wants to run a clock at, we run this clock at the
+ rate implied by the host system's minimal sleep time or 50Hz.
+
+ To solve this we merely run an internal clock at 100Hz.
*/
static t_stat sim_timer_clock_tick_svc (UNIT *uptr)
{
sim_debug(DBG_INT, &sim_timer_dev, "sim_timer_clock_tick_svc()\n");
@@ -2310,90 +2457,108 @@
faster than the host system's clock. This is optimal so that accurate
time measurements are taken. If the simulated system doesn't have a
clock with an appropriate tick rate, an internal clock is run that meets
this requirement, OR when asynch clocks are enabled, the internal clock
is always run.
+
+ Some simulators have clocks that have dynamically programmable tick
+ rates. Such a clock is only a reliable candidate to be the calibrated
+ clock if it uses a single tick rate rather than changing the tick rate
+ on the fly. Generally most systems like this, under normal conditions
+ don't change their tick rates unless they're running something that is
+ examining the behavior of the clock system (like a diagnostic). Under
+ these conditions this clock is removed from the potential selection as
+ "the" calibrated clock all others are relative to and if necessary, an
+ internal calibrated clock is selected.
*/
static void _rtcn_configure_calibrated_clock (int32 newtmr)
{
int32 tmr;
+RTC *rtc, *crtc;
/* Look for a timer running slower or the same as the host system clock */
sim_int_clk_tps = MIN(CLK_TPS, sim_os_tick_hz);
for (tmr=0; tmrhz) && /* is calibrated AND */
+ (rtc->hz <= (uint32)sim_os_tick_hz) && /* slower than OS tick rate AND */
+ (rtc->clock_unit) && /* clock has been registered AND */
+ ((rtc->last_hz == 0) || /* first calibration call OR */
+ (rtc->last_hz == rtc->hz))) /* subsequent calibration call with an unchanged tick rate */
break;
}
if (tmr == SIM_NTIMERS) { /* None found? */
if ((tmr != newtmr) && (!sim_is_active (&SIM_INTERNAL_UNIT))) {
if ((sim_calb_tmr != SIM_NTIMERS) &&/* not internal timer? */
- (sim_calb_tmr != -1) && /* previously active? */
- (!rtc_hz[sim_calb_tmr])) { /* now stopped? */
- sim_debug (DBG_CAL, &sim_timer_dev, "_rtcn_configure_calibrated_clock(newtmr=%d) - Cleaning up stopped timer %s support\n", newtmr, sim_uname(sim_clock_unit[sim_calb_tmr]));
- /* Migrate any coscheduled devices to the standard queue */
- /* with appropriate usecs_remaining reflecting their currently */
- /* scheduled firing time. sim_process_event() will coschedule */
- /* appropriately. */
- /* temporarily restore prior hz to get correct remaining time */
- rtc_hz[sim_calb_tmr] = rtc_last_hz[sim_calb_tmr];
- while (sim_clock_cosched_queue[sim_calb_tmr] != QUEUE_LIST_END) {
- UNIT *uptr = sim_clock_cosched_queue[sim_calb_tmr];
- double usecs_remaining = sim_timer_activate_time_usecs (uptr) - 1;
-
- _sim_coschedule_cancel (uptr);
- _sim_activate (uptr, 1);
- uptr->usecs_remaining = usecs_remaining;
- }
- rtc_hz[sim_calb_tmr] = 0; /* back to 0 */
- if (sim_clock_unit[sim_calb_tmr])
- sim_cancel (sim_clock_unit[sim_calb_tmr]);
- sim_cancel (&sim_timer_units[sim_calb_tmr]);
+ (sim_calb_tmr != -1)) { /* previously active? */
+ crtc = &rtcs[sim_calb_tmr];
+ if (!crtc->hz) { /* now stopped? */
+ sim_debug (DBG_CAL, &sim_timer_dev, "_rtcn_configure_calibrated_clock(newtmr=%d) - Cleaning up stopped timer %s support\n", newtmr, sim_uname(crtc->clock_unit));
+ /* Migrate any coscheduled devices to the standard queue */
+ /* with appropriate usecs_remaining reflecting their currently */
+ /* scheduled firing time. sim_process_event() will coschedule */
+ /* appropriately. */
+ /* temporarily restore prior hz to get correct remaining time */
+ crtc->hz = crtc->last_hz;
+ while (crtc->clock_cosched_queue != QUEUE_LIST_END) {
+ UNIT *uptr = crtc->clock_cosched_queue;
+ double usecs_remaining = sim_timer_activate_time_usecs (uptr) - 1;
+
+ _sim_coschedule_cancel (uptr);
+ _sim_activate (uptr, 1);
+ uptr->usecs_remaining = usecs_remaining;
+ }
+ crtc->hz = 0; /* back to 0 */
+ if (crtc->clock_unit)
+ sim_cancel (crtc->clock_unit);
+ sim_cancel (crtc->timer_unit);
+ }
}
/* Start the internal timer */
sim_calb_tmr = SIM_NTIMERS;
sim_debug (DBG_CAL|DBG_INT, &sim_timer_dev, "_rtcn_configure_calibrated_clock(newtmr=%d) - Starting Internal Calibrated Timer at %dHz\n", newtmr, sim_int_clk_tps);
SIM_INTERNAL_UNIT.action = &sim_timer_clock_tick_svc;
SIM_INTERNAL_UNIT.flags = UNIT_IDLE;
sim_register_internal_device (&sim_int_timer_dev); /* Register Internal timer device */
- sim_rtcn_init_unit (&SIM_INTERNAL_UNIT, (CLK_INIT*CLK_TPS)/sim_int_clk_tps, SIM_INTERNAL_CLK);
+ sim_rtcn_init_unit_ticks (&SIM_INTERNAL_UNIT, (int32)((CLK_INIT*CLK_TPS)/sim_int_clk_tps), SIM_INTERNAL_CLK, sim_int_clk_tps);
SIM_INTERNAL_UNIT.action (&SIM_INTERNAL_UNIT); /* Force tick to activate timer */
}
return;
}
if ((tmr == newtmr) &&
(sim_calb_tmr == newtmr)) /* already set? */
return;
-if (sim_calb_tmr == SIM_NTIMERS) { /* was old the internal timer? */
- sim_debug (DBG_CAL|DBG_INT, &sim_timer_dev, "_rtcn_configure_calibrated_clock(newtmr=%d) - Stopping Internal Calibrated Timer, New Timer = %d (%dHz)\n", newtmr, tmr, rtc_hz[tmr]);
- rtc_initd[SIM_NTIMERS] = 0;
- rtc_hz[SIM_NTIMERS] = 0;
+if (sim_calb_tmr == SIM_NTIMERS) { /* was old the internal timer? */
+ sim_debug (DBG_CAL|DBG_INT, &sim_timer_dev, "_rtcn_configure_calibrated_clock(newtmr=%d) - Stopping Internal Calibrated Timer, New Timer = %d (%dHz)\n", newtmr, tmr, rtc->hz);
+ rtcs[SIM_NTIMERS].initd = 0;
+ rtcs[SIM_NTIMERS].hz = 0;
sim_register_clock_unit_tmr (NULL, SIM_INTERNAL_CLK);
sim_cancel (&SIM_INTERNAL_UNIT);
sim_cancel (&sim_timer_units[SIM_NTIMERS]);
}
else {
- if ((sim_calb_tmr != -1) &&
- (rtc_hz[sim_calb_tmr] == 0)) {
- /* Migrate any coscheduled devices to the standard queue */
- /* with appropriate usecs_remaining reflecting their currently */
- /* scheduled firing time. sim_process_event() will coschedule */
- /* appropriately. */
- /* temporarily restore prior hz to get correct remaining time */
- rtc_hz[sim_calb_tmr] = rtc_last_hz[sim_calb_tmr];
- while (sim_clock_cosched_queue[tmr] != QUEUE_LIST_END) {
- UNIT *uptr = sim_clock_cosched_queue[tmr];
- double usecs_remaining = sim_timer_activate_time_usecs (uptr) - 1;
-
- _sim_coschedule_cancel (uptr);
- _sim_activate (uptr, 1);
- uptr->usecs_remaining = usecs_remaining;
- }
- rtc_hz[sim_calb_tmr] = 0; /* back to 0 */
- }
- sim_debug (DBG_CAL|DBG_INT, &sim_timer_dev, "_rtcn_configure_calibrated_clock(newtmr=%d) - Changing Calibrated Timer from %d (%dHz) to %d (%dHz)\n", newtmr, sim_calb_tmr, rtc_hz[sim_calb_tmr], tmr, rtc_hz[tmr]);
+ if (sim_calb_tmr != -1) {
+ crtc = &rtcs[sim_calb_tmr];
+ if (crtc->hz == 0) {
+ /* Migrate any coscheduled devices to the standard queue */
+ /* with appropriate usecs_remaining reflecting their currently */
+ /* scheduled firing time. sim_process_event() will coschedule */
+ /* appropriately. */
+ /* temporarily restore prior hz to get correct remaining time */
+ crtc->hz = crtc->last_hz;
+ while (crtc->clock_cosched_queue != QUEUE_LIST_END) {
+ UNIT *uptr = crtc->clock_cosched_queue;
+ double usecs_remaining = sim_timer_activate_time_usecs (uptr) - 1;
+
+ _sim_coschedule_cancel (uptr);
+ _sim_activate (uptr, 1);
+ uptr->usecs_remaining = usecs_remaining;
+ }
+ crtc->hz = 0; /* back to 0 */
+ }
+ sim_debug (DBG_CAL|DBG_INT, &sim_timer_dev, "_rtcn_configure_calibrated_clock(newtmr=%d) - Changing Calibrated Timer from %d (%dHz) to %d (%dHz)\n", newtmr, sim_calb_tmr, crtc->last_hz, tmr, rtc->hz);
+ }
sim_calb_tmr = tmr;
}
sim_calb_tmr = tmr;
}
@@ -2412,28 +2577,40 @@
return SCPE_OK;
}
void sim_start_timer_services (void)
{
-/*
- * If we're quickly running again after being stopped for less than
- * the time of one calibrated clock tick, then don't force a complete
- * recalibration of any timers that may have been previously running.
- */
-if ((sim_calb_tmr_last != -1) &&
- ((sim_timenow_double () - sim_stop_time) < rtc_clock_tick_size[sim_calb_tmr_last])) {
- int32 clock_time = sim_activate_time (sim_clock_unit[sim_calb_tmr_last]);
-
- sim_calb_tmr = sim_calb_tmr_last;
- sim_cancel (sim_clock_unit[sim_calb_tmr]);
- sim_activate (&sim_timer_units[sim_calb_tmr], clock_time);
- sim_debug (DBG_TRC, &sim_timer_dev, "sim_start_timer_services() - restarting with previously calibrated timer %d (%s) at %d\n", sim_calb_tmr, sim_uname (sim_clock_unit[sim_calb_tmr]), clock_time);
+int32 tmr;
+uint32 sim_prompt_time = (sim_gtime () > 0) ? (sim_os_msec () - sim_stop_time) : 0;
+int32 registered_units = 0;
+
+sim_time_at_sim_prompt += (((double)sim_prompt_time) / 1000.0);
+for (tmr=0; tmr<=SIM_NTIMERS; tmr++) {
+ RTC *rtc = &rtcs[tmr];
+
+ if (rtc->initd) { /* calibrated clock initialized? */
+ rtc->rtime += sim_prompt_time;
+ rtc->vtime += sim_prompt_time;
+ sim_debug (DBG_CAL, &sim_timer_dev, "sim_start_timer_services(tmr=%d) - adjusting calibration real time by %d ms\n", tmr, (int)sim_prompt_time);
+ if (rtc->clock_catchup_eligible)
+ rtc->calib_tick_time += (((double)sim_prompt_time) / 1000.0);
+ if (rtc->clock_unit)
+ ++registered_units;
+ }
+ }
+if (registered_units == 1)
+ sim_catchup_ticks = FALSE;
+if (sim_calb_tmr == -1) {
+ sim_debug (DBG_CAL, &sim_timer_dev, "sim_start_timer_services() - starting from scratch\n");
+ _rtcn_configure_calibrated_clock (sim_calb_tmr);
}
else {
- sim_debug (DBG_TRC, &sim_timer_dev, "sim_start_timer_services() - starting from scratch\n");
- sim_rtcn_init_all (); /* re-init clocks */
- _rtcn_configure_calibrated_clock (sim_calb_tmr);
+ if (sim_calb_tmr == SIM_NTIMERS) {
+ sim_debug (DBG_CAL, &sim_timer_dev, "sim_start_timer_services() - restarting internal timer after %d %s\n",
+ sim_internal_timer_time, sim_vm_interval_units);
+ sim_activate (&SIM_INTERNAL_UNIT, sim_internal_timer_time);
+ }
}
if (sim_timer_stop_time > sim_gtime())
sim_activate_abs (&sim_stop_unit, (int32)(sim_timer_stop_time - sim_gtime()));
#if defined(SIM_ASYNCH_CLOCKS)
pthread_mutex_lock (&sim_timer_lock);
@@ -2455,55 +2632,57 @@
void sim_stop_timer_services (void)
{
int tmr;
-sim_debug (DBG_TRC, &sim_timer_dev, "sim_stop_timer_services(sim_interval=%d)\n", sim_interval);
+sim_debug (DBG_TRC, &sim_timer_dev, "sim_stop_timer_services(sim_interval=%d, sim_calb_tmr=%d)\n", sim_interval, sim_calb_tmr);
if (sim_interval < 0)
sim_interval = 0; /* No catching up after stopping */
for (tmr=0; tmr<=SIM_NTIMERS; tmr++) {
int32 accum;
+ RTC *rtc = &rtcs[tmr];
- if (sim_clock_unit[tmr]) {
- int32 clock_time = _sim_activate_time (&sim_timer_units[tmr]);
+ if (rtc->clock_unit) {
+ int32 clock_time = _sim_activate_time (rtc->timer_unit);
/* Stop clock assist unit and make sure the clock unit has a tick queued */
- if (sim_is_active (&sim_timer_units[tmr])) {
- sim_cancel (&sim_timer_units[tmr]);
- sim_debug (DBG_QUE, &sim_timer_dev, "sim_stop_timer_services() - tmr=%d scheduling %s after %d\n", tmr, sim_uname (sim_clock_unit[tmr]), clock_time);
- _sim_activate (sim_clock_unit[tmr], clock_time);
+ if (sim_is_active (rtc->timer_unit)) {
+ sim_cancel (rtc->timer_unit);
+ sim_debug (DBG_QUE, &sim_timer_dev, "sim_stop_timer_services() - tmr=%d scheduling %s after %d\n", tmr, sim_uname (rtc->clock_unit), clock_time);
+ _sim_activate (rtc->clock_unit, clock_time);
}
/* Move coscheduled units to the standard event queue */
/* scheduled to fire at the same time as the related */
/* clock unit is to fire with excess time reflected in */
/* the unit usecs_remaining value */
- accum = sim_cosched_interval[tmr];
- while (sim_clock_cosched_queue[tmr] != QUEUE_LIST_END) {
- UNIT *cptr = sim_clock_cosched_queue[tmr];
+ accum = rtc->cosched_interval;
+ while (rtc->clock_cosched_queue != QUEUE_LIST_END) {
+ UNIT *cptr = rtc->clock_cosched_queue;
double usecs_remaining = cptr->usecs_remaining;
- sim_clock_cosched_queue[tmr] = cptr->next;
+ rtc->clock_cosched_queue = cptr->next;
cptr->next = NULL;
cptr->cancel = NULL;
accum += cptr->time;
cptr->usecs_remaining = 0.0;
_sim_activate (cptr, clock_time);
- cptr->usecs_remaining = usecs_remaining + floor(1000000.0 * (accum - ((accum > 0) ? 1 : 0)) * rtc_clock_tick_size[tmr]);
+ cptr->usecs_remaining = usecs_remaining + floor(1000000.0 * (accum - ((accum > 0) ? 1 : 0)) * rtc->clock_tick_size);
sim_debug (DBG_QUE, &sim_timer_dev, "sim_stop_timer_services() - tmr=%d scheduling %s after %d and %.0f usecs\n", tmr, sim_uname (cptr), clock_time, cptr->usecs_remaining);
}
- sim_cosched_interval[tmr] = 0;
+ rtc->cosched_interval = 0;
}
}
+if (sim_calb_tmr == SIM_NTIMERS)
+ sim_internal_timer_time = sim_activate_time (&SIM_INTERNAL_UNIT) - 1;
sim_cancel (&SIM_INTERNAL_UNIT); /* Make sure Internal Timer is stopped */
sim_cancel (&sim_timer_units[SIM_NTIMERS]);
sim_calb_tmr_last = sim_calb_tmr; /* Save calibrated timer value for display */
sim_inst_per_sec_last = sim_timer_inst_per_sec (); /* Save execution rate for display */
-sim_calb_tmr = -1;
-sim_stop_time = sim_timenow_double (); /* record when execution stopped */
+sim_stop_time = sim_os_msec (); /* record when execution stopped */
#if defined(SIM_ASYNCH_CLOCKS)
pthread_mutex_lock (&sim_timer_lock);
if (sim_timer_thread_running) {
sim_debug (DBG_TRC, &sim_timer_dev, "sim_stop_timer_services() - stopping\n");
pthread_cond_signal (&sim_timer_wake);
@@ -2549,16 +2728,18 @@
to avoid overflow if/when strange timing delays might produce unexpected results */
double sim_timer_inst_per_sec (void)
{
double inst_per_sec = sim_inst_per_sec_last;
+RTC *rtc;
if (sim_calb_tmr == -1)
return inst_per_sec;
-inst_per_sec = ((double)rtc_currd[sim_calb_tmr])*rtc_hz[sim_calb_tmr];
+rtc = &rtcs[sim_calb_tmr];
+inst_per_sec = ((double)rtc->currd) * rtc->hz;
if (inst_per_sec == 0.0)
- inst_per_sec = ((double)rtc_currd[sim_calb_tmr])*sim_int_clk_tps;
+ inst_per_sec = ((double)rtc->currd) * sim_int_clk_tps;
return inst_per_sec;
}
t_stat sim_timer_activate (UNIT *uptr, int32 interval)
{
@@ -2570,18 +2751,22 @@
{
UNIT *ouptr = uptr;
int inst_delay, tmr;
double inst_delay_d, inst_per_usec;
t_stat stat;
+RTC *crtc;
AIO_VALIDATE(uptr);
/* If this is a clock unit, we need to schedule the related timer unit instead */
-for (tmr=0; tmr<=SIM_NTIMERS; tmr++)
- if (sim_clock_unit[tmr] == uptr) {
- uptr = &sim_timer_units[tmr];
+for (tmr=0; tmr<=SIM_NTIMERS; tmr++) {
+ RTC *rtc = &rtcs[tmr];
+
+ if (rtc->clock_unit == uptr) {
+ uptr = rtc->timer_unit;
break;
}
+ }
if (sim_is_active (uptr)) /* already active? */
return SCPE_OK;
if (usec_delay < 0.0) {
sim_debug (DBG_QUE, &sim_timer_dev, "sim_timer_activate_after(%s, %.0f usecs) - surprising usec value\n",
sim_uname(uptr), usec_delay);
@@ -2606,39 +2791,42 @@
inst_delay = (int32)inst_delay_d;
if ((inst_delay == 0) && (usec_delay != 0))
inst_delay_d = inst_delay = 1; /* Minimum non-zero delay is 1 instruction */
if (uptr->usecs_remaining != 0.0) /* No calibrated timer yet, wait one cycle */
inst_delay_d = inst_delay = 1; /* Minimum non-zero delay is 1 instruction */
-if ((sim_calb_tmr != -1) && (rtc_hz[sim_calb_tmr])) { /* Calibrated Timer available? */
- int32 inst_til_tick = sim_activate_time (&sim_timer_units[sim_calb_tmr]) - 1;
- int32 ticks_til_calib = rtc_hz[sim_calb_tmr] - rtc_ticks[sim_calb_tmr];
- double usecs_per_tick = floor (1000000.0 / rtc_hz[sim_calb_tmr]);
- int32 inst_til_calib = inst_til_tick + ((ticks_til_calib - 1) * rtc_currd[sim_calb_tmr]);
- uint32 usecs_til_calib = (uint32)ceil(inst_til_calib / inst_per_usec);
-
- if ((uptr != &sim_timer_units[sim_calb_tmr]) && /* Not scheduling calibrated timer */
- (inst_til_tick > 0)) { /* and tick not pending? */
- if (inst_delay_d > (double)inst_til_calib) { /* long wait? */
- stat = sim_clock_coschedule_tmr (uptr, sim_calb_tmr, ticks_til_calib - 1);
- uptr->usecs_remaining = (stat == SCPE_OK) ? usec_delay - usecs_til_calib : 0.0;
- sim_debug (DBG_TIM, &sim_timer_dev, "sim_timer_activate_after(%s, %.0f usecs) - coscheduling with with calibrated timer(%d), ticks=%d, usecs_remaining=%.0f usecs, inst_til_tick=%d, ticks_til_calib=%d, usecs_til_calib=%u\n",
- sim_uname(uptr), usec_delay, sim_calb_tmr, ticks_til_calib, uptr->usecs_remaining, inst_til_tick, ticks_til_calib, usecs_til_calib);
- sim_debug (DBG_CHK, &sim_timer_dev, "sim_timer_activate_after(%s, %.0f usecs) - result = %.0f usecs, %.0f usecs\n",
- sim_uname(uptr), usec_delay, sim_timer_activate_time_usecs (ouptr), sim_timer_activate_time_usecs (uptr));
- return stat;
- }
- if ((usec_delay > (2 * usecs_per_tick)) &&
- (ticks_til_calib > 1)) { /* long wait? */
- double usecs_til_tick = floor (inst_til_tick / inst_per_usec);
-
- stat = sim_clock_coschedule_tmr (uptr, sim_calb_tmr, 0);
- uptr->usecs_remaining = (stat == SCPE_OK) ? usec_delay - usecs_til_tick : 0.0;
- sim_debug (DBG_TIM, &sim_timer_dev, "sim_timer_activate_after(%s, %.0f usecs) - coscheduling with with calibrated timer(%d), ticks=%d, usecs_remaining=%.0f usecs, inst_til_tick=%d, usecs_til_tick=%.0f\n",
- sim_uname(uptr), usec_delay, sim_calb_tmr, 0, uptr->usecs_remaining, inst_til_tick, usecs_til_tick);
- sim_debug (DBG_CHK, &sim_timer_dev, "sim_timer_activate_after(%s, %.0f usecs) - result = %.0f usecs, %.0f usecs\n",
- sim_uname(uptr), usec_delay, sim_timer_activate_time_usecs (ouptr), sim_timer_activate_time_usecs (uptr));
- return stat;
+if (sim_calb_tmr != -1) {
+ crtc = &rtcs[sim_calb_tmr];
+ if (crtc->hz) { /* Calibrated Timer available? */
+ int32 inst_til_tick = sim_activate_time (crtc->timer_unit) - 1;
+ int32 ticks_til_calib = crtc->hz - crtc->ticks;
+ double usecs_per_tick = floor (1000000.0 / crtc->hz);
+ int32 inst_til_calib = inst_til_tick + ((ticks_til_calib - 1) * crtc->currd);
+ uint32 usecs_til_calib = (uint32)ceil(inst_til_calib / inst_per_usec);
+
+ if ((uptr != crtc->timer_unit) && /* Not scheduling calibrated timer */
+ (inst_til_tick > 0)) { /* and tick not pending? */
+ if (inst_delay_d > (double)inst_til_calib) { /* long wait? */
+ stat = sim_clock_coschedule_tmr (uptr, sim_calb_tmr, ticks_til_calib - 1);
+ uptr->usecs_remaining = (stat == SCPE_OK) ? usec_delay - usecs_til_calib : 0.0;
+ sim_debug (DBG_TIM, &sim_timer_dev, "sim_timer_activate_after(%s, %.0f usecs) - coscheduling with with calibrated timer(%d), ticks=%d, usecs_remaining=%.0f usecs, inst_til_tick=%d, ticks_til_calib=%d, usecs_til_calib=%u\n",
+ sim_uname(uptr), usec_delay, sim_calb_tmr, ticks_til_calib, uptr->usecs_remaining, inst_til_tick, ticks_til_calib, usecs_til_calib);
+ sim_debug (DBG_CHK, &sim_timer_dev, "sim_timer_activate_after(%s, %.0f usecs) - result = %.0f usecs, %.0f usecs\n",
+ sim_uname(uptr), usec_delay, sim_timer_activate_time_usecs (ouptr), sim_timer_activate_time_usecs (uptr));
+ return stat;
+ }
+ if ((usec_delay > (2 * usecs_per_tick)) &&
+ (ticks_til_calib > 1)) { /* long wait? */
+ double usecs_til_tick = floor (inst_til_tick / inst_per_usec);
+
+ stat = sim_clock_coschedule_tmr (uptr, sim_calb_tmr, 0);
+ uptr->usecs_remaining = (stat == SCPE_OK) ? usec_delay - usecs_til_tick : 0.0;
+ sim_debug (DBG_TIM, &sim_timer_dev, "sim_timer_activate_after(%s, %.0f usecs) - coscheduling with with calibrated timer(%d), ticks=%d, usecs_remaining=%.0f usecs, inst_til_tick=%d, usecs_til_tick=%.0f\n",
+ sim_uname(uptr), usec_delay, sim_calb_tmr, 0, uptr->usecs_remaining, inst_til_tick, usecs_til_tick);
+ sim_debug (DBG_CHK, &sim_timer_dev, "sim_timer_activate_after(%s, %.0f usecs) - result = %.0f usecs, %.0f usecs\n",
+ sim_uname(uptr), usec_delay, sim_timer_activate_time_usecs (ouptr), sim_timer_activate_time_usecs (uptr));
+ return stat;
+ }
}
}
}
/*
* We're here to schedule if:
@@ -2665,12 +2853,14 @@
uptr->a_due_time = d_now + (usec_delay / 1000000.0);
uptr->a_due_gtime = sim_gtime () + (sim_timer_inst_per_sec () * (usec_delay / 1000000.0));
uptr->cancel = &_sim_wallclock_cancel; /* bind cleanup method */
uptr->a_is_active = &_sim_wallclock_is_active;
if (tmr <= SIM_NTIMERS) { /* Timer Unit? */
- sim_clock_unit[tmr]->cancel = &_sim_wallclock_cancel;
- sim_clock_unit[tmr]->a_is_active = &_sim_wallclock_is_active;
+ RTC *rtc = &rtcs[tmr];
+
+ rtc->clock_unit->cancel = &_sim_wallclock_cancel;
+ rtc->clock_unit->a_is_active = &_sim_wallclock_is_active;
}
sim_debug (DBG_TIM, &sim_timer_dev, "sim_timer_activate_after(%s, %.0f usecs) - queueing wallclock addition at %.6f\n",
sim_uname(uptr), usec_delay, uptr->a_due_time);
@@ -2714,53 +2904,58 @@
/* Clock coscheduling routines */
t_stat sim_register_clock_unit_tmr (UNIT *uptr, int32 tmr)
{
+RTC *rtc;
+
if (tmr == SIM_INTERNAL_CLK)
tmr = SIM_NTIMERS;
else {
if ((tmr < 0) || (tmr > SIM_NTIMERS))
return SCPE_IERR;
}
+rtc = &rtcs[tmr];
if (NULL == uptr) { /* deregistering? */
/* Migrate any coscheduled devices to the standard queue */
/* they will fire and subsequently requeue themselves */
- while (sim_clock_cosched_queue[tmr] != QUEUE_LIST_END) {
- UNIT *uptr = sim_clock_cosched_queue[tmr];
+ while (rtc->clock_cosched_queue != QUEUE_LIST_END) {
+ UNIT *uptr = rtc->clock_cosched_queue;
double usecs_remaining = sim_timer_activate_time_usecs (uptr);
_sim_coschedule_cancel (uptr);
_sim_activate (uptr, 1);
uptr->usecs_remaining = usecs_remaining;
}
- if (sim_clock_unit[tmr]) {
- sim_cancel (sim_clock_unit[tmr]);
- sim_clock_unit[tmr]->dynflags &= ~UNIT_TMR_UNIT;
+ if (rtc->clock_unit) {
+ sim_cancel (rtc->clock_unit);
+ rtc->clock_unit->dynflags &= ~UNIT_TMR_UNIT;
}
- sim_clock_unit[tmr] = NULL;
- sim_cancel (&sim_timer_units[tmr]);
+ rtc->clock_unit = NULL;
+ sim_cancel (rtc->timer_unit);
return SCPE_OK;
}
-if (NULL == sim_clock_unit[tmr])
- sim_clock_cosched_queue[tmr] = QUEUE_LIST_END;
-sim_clock_unit[tmr] = uptr;
+if (rtc->clock_unit == NULL)
+ rtc->clock_cosched_queue = QUEUE_LIST_END;
+rtc->clock_unit = uptr;
uptr->dynflags |= UNIT_TMR_UNIT;
-sim_timer_units[tmr].flags = ((tmr == SIM_NTIMERS) ? 0 : UNIT_DIS) |
- (sim_clock_unit[tmr] ? UNIT_IDLE : 0);
+rtc->timer_unit->flags = ((tmr == SIM_NTIMERS) ? 0 : UNIT_DIS) |
+ (rtc->clock_unit ? UNIT_IDLE : 0);
return SCPE_OK;
}
/* Default timer is 0, otherwise use a calibrated one if it exists */
int32 sim_rtcn_calibrated_tmr (void)
{
-return ((rtc_currd[0] && rtc_hz[0]) ? 0 : ((sim_calb_tmr != -1) ? sim_calb_tmr : 0));
+return ((rtcs[0].currd && rtcs[0].hz) ? 0 : ((sim_calb_tmr != -1) ? sim_calb_tmr : 0));
}
int32 sim_rtcn_tick_size (int32 tmr)
{
-return (rtc_currd[tmr]) ? rtc_currd[tmr] : 10000;
+RTC *rtc = &rtcs[tmr];
+
+return (rtc->currd) ? rtc->currd : 10000;
}
t_stat sim_register_clock_unit (UNIT *uptr)
{
return sim_register_clock_unit_tmr (uptr, 0);
@@ -2784,10 +2979,12 @@
/* ticks - 0 means on the next tick, 1 means the second tick, etc. */
t_stat sim_clock_coschedule_tmr (UNIT *uptr, int32 tmr, int32 ticks)
{
+RTC *rtc;
+
if (ticks < 0)
return SCPE_ARG;
if (sim_is_active (uptr)) {
sim_debug (DBG_TIM, &sim_timer_dev, "sim_clock_coschedule_tmr(%s, tmr=%d, ticks=%d) - already active\n", sim_uname (uptr), tmr, ticks);
return SCPE_OK;
@@ -2796,43 +2993,44 @@
tmr = SIM_NTIMERS;
else {
if ((tmr < 0) || (tmr > SIM_NTIMERS))
return sim_activate (uptr, MAX(1, ticks) * 10000);
}
-if ((NULL == sim_clock_unit[tmr]) || (rtc_hz[tmr] == 0)) {
- sim_debug (DBG_TIM, &sim_timer_dev, "sim_clock_coschedule_tmr(%s, tmr=%d, ticks=%d) - no clock activating after %d instructions\n", sim_uname (uptr), tmr, ticks, ticks * (rtc_currd[tmr] ? rtc_currd[tmr] : rtc_currd[sim_rtcn_calibrated_tmr ()]));
- return sim_activate (uptr, ticks * (rtc_currd[tmr] ? rtc_currd[tmr] : rtc_currd[sim_rtcn_calibrated_tmr ()]));
+rtc = &rtcs[tmr];
+if ((NULL == rtc->clock_unit) || (rtc->hz == 0)) {
+ sim_debug (DBG_TIM, &sim_timer_dev, "sim_clock_coschedule_tmr(%s, tmr=%d, ticks=%d) - no clock activating after %d %s\n", sim_uname (uptr), tmr, ticks, ticks * (rtc->currd ? rtc->currd : rtcs[sim_rtcn_calibrated_tmr ()].currd), sim_vm_interval_units);
+ return sim_activate (uptr, ticks * (rtc->currd ? rtc->currd : rtcs[sim_rtcn_calibrated_tmr ()].currd));
}
else {
UNIT *cptr, *prvptr;
int32 accum;
- if (sim_clock_cosched_queue[tmr] != QUEUE_LIST_END)
- sim_clock_cosched_queue[tmr]->time = sim_cosched_interval[tmr];
+ if (rtc->clock_cosched_queue != QUEUE_LIST_END)
+ rtc->clock_cosched_queue->time = rtc->cosched_interval;
prvptr = NULL;
accum = 0;
- for (cptr = sim_clock_cosched_queue[tmr]; cptr != QUEUE_LIST_END; cptr = cptr->next) {
+ for (cptr = rtc->clock_cosched_queue; cptr != QUEUE_LIST_END; cptr = cptr->next) {
if (ticks < (accum + cptr->time))
break;
accum += cptr->time;
prvptr = cptr;
}
if (prvptr == NULL) {
- cptr = uptr->next = sim_clock_cosched_queue[tmr];
- sim_clock_cosched_queue[tmr] = uptr;
+ cptr = uptr->next = rtc->clock_cosched_queue;
+ rtc->clock_cosched_queue = uptr;
}
else {
cptr = uptr->next = prvptr->next;
prvptr->next = uptr;
}
uptr->time = ticks - accum;
if (cptr != QUEUE_LIST_END)
cptr->time = cptr->time - uptr->time;
uptr->cancel = &_sim_coschedule_cancel; /* bind cleanup method */
- if (uptr == sim_clock_cosched_queue[tmr])
- sim_cosched_interval[tmr] = sim_clock_cosched_queue[tmr]->time;
- sim_debug (DBG_QUE, &sim_timer_dev, "sim_clock_coschedule_tmr(%s, tmr=%d, ticks=%d, hz=%d) - queueing for clock co-schedule, interval now: %d\n", sim_uname (uptr), tmr, ticks, rtc_hz[tmr], sim_cosched_interval[tmr]);
+ if (uptr == rtc->clock_cosched_queue)
+ rtc->cosched_interval = rtc->clock_cosched_queue->time;
+ sim_debug (DBG_QUE, &sim_timer_dev, "sim_clock_coschedule_tmr(%s, tmr=%d, ticks=%d, hz=%d) - queueing for clock co-schedule, interval now: %d\n", sim_uname (uptr), tmr, ticks, rtc->hz, rtc->cosched_interval);
}
return SCPE_OK;
}
t_stat sim_clock_coschedule_tmr_abs (UNIT *uptr, int32 tmr, int32 ticks)
@@ -2848,19 +3046,21 @@
if (uptr->next) { /* On a queue? */
int tmr;
UNIT *nptr;
for (tmr=0; tmr<=SIM_NTIMERS; tmr++) {
- if (sim_clock_unit[tmr]) {
- if (uptr == sim_clock_cosched_queue[tmr]) {
- nptr = sim_clock_cosched_queue[tmr] = uptr->next;
+ RTC *rtc = &rtcs[tmr];
+
+ if (rtc->clock_unit) {
+ if (uptr == rtc->clock_cosched_queue) {
+ nptr = rtc->clock_cosched_queue = uptr->next;
uptr->next = NULL;
}
else {
UNIT *cptr;
- for (cptr = sim_clock_cosched_queue[tmr];
+ for (cptr = rtc->clock_cosched_queue;
(cptr != QUEUE_LIST_END);
cptr = cptr->next) {
if (cptr->next == uptr) {
nptr = cptr->next = (uptr)->next;
uptr->next = NULL;
@@ -2887,11 +3087,13 @@
int32 tmr;
if (!(uptr->dynflags & UNIT_TMR_UNIT))
return FALSE;
for (tmr=0; tmr<=SIM_NTIMERS; tmr++) {
- if (sim_clock_unit[tmr] == uptr)
+ RTC *rtc = &rtcs[tmr];
+
+ if (rtc->clock_unit == uptr)
return sim_is_active (&sim_timer_units[tmr]);
}
return FALSE;
}
@@ -2900,11 +3102,13 @@
int32 tmr;
if (!(uptr->dynflags & UNIT_TMR_UNIT))
return SCPE_IERR;
for (tmr=0; tmr<=SIM_NTIMERS; tmr++) {
- if (sim_clock_unit[tmr] == uptr)
+ RTC *rtc = &rtcs[tmr];
+
+ if (rtc->clock_unit == uptr)
return sim_cancel (&sim_timer_units[tmr]);
}
return SCPE_IERR;
}
@@ -2915,15 +3119,18 @@
t_bool b_return = FALSE;
AIO_UPDATE_QUEUE;
pthread_mutex_lock (&sim_timer_lock);
/* If this is a clock unit, we need to cancel both this and the related timer unit */
-for (tmr=0; tmr<=SIM_NTIMERS; tmr++)
- if (sim_clock_unit[tmr] == uptr) {
+for (tmr=0; tmr<=SIM_NTIMERS; tmr++) {
+ RTC *rtc = &rtcs[tmr];
+
+ if (rtc->clock_unit == uptr) {
uptr = &sim_timer_units[tmr];
break;
}
+ }
if (uptr->a_next) {
UNIT *cptr;
if (uptr == sim_wallclock_entry) { /* Pending on the queue? */
sim_wallclock_entry = NULL;
@@ -2953,12 +3160,14 @@
if (uptr->a_next == NULL) { /* Was canceled? */
uptr->a_due_time = uptr->a_due_gtime = uptr->a_usec_delay = 0;
uptr->cancel = NULL;
uptr->a_is_active = NULL;
if (tmr <= SIM_NTIMERS) { /* Timer Unit? */
- sim_clock_unit[tmr]->cancel = NULL;
- sim_clock_unit[tmr]->a_is_active = NULL;
+ RTC *rtc = &rtcs[tmr];
+
+ rtc->clock_unit->cancel = NULL;
+ rtc->clock_unit->a_is_active = NULL;
}
b_return = TRUE;
}
}
pthread_mutex_unlock (&sim_timer_lock);
@@ -2970,13 +3179,16 @@
int32 tmr;
if (uptr->a_next)
return TRUE;
/* If this is a clock unit, we need to examine the related timer unit instead */
-for (tmr=0; tmr<=SIM_NTIMERS; tmr++)
- if (sim_clock_unit[tmr] == uptr)
+for (tmr=0; tmr<=SIM_NTIMERS; tmr++) {
+ RTC *rtc = &rtcs[tmr];
+
+ if (rtc->clock_unit == uptr)
return (sim_timer_units[tmr].a_next != NULL);
+ }
return FALSE;
}
#endif /* defined(SIM_ASYNCH_CLOCKS) */
int32 _sim_timer_activate_time (UNIT *uptr)
@@ -3017,20 +3229,22 @@
#endif /* defined(SIM_ASYNCH_CLOCKS) */
if (uptr->cancel == &_sim_coschedule_cancel) {
for (tmr=0; tmr<=SIM_NTIMERS; tmr++) {
int32 accum = 0;
+ RTC *rtc = &rtcs[tmr];
+
- for (cptr = sim_clock_cosched_queue[tmr]; cptr != QUEUE_LIST_END; cptr = cptr->next) {
- if (cptr == sim_clock_cosched_queue[tmr]) {
- if (sim_cosched_interval[tmr] > 0)
- accum += sim_cosched_interval[tmr];
+ for (cptr = rtc->clock_cosched_queue; cptr != QUEUE_LIST_END; cptr = cptr->next) {
+ if (cptr == rtc->clock_cosched_queue) {
+ if (rtc->cosched_interval > 0)
+ accum += rtc->cosched_interval;
}
else
accum += cptr->time;
if (cptr == uptr)
- return (rtc_currd[tmr] * accum) + sim_activate_time (&sim_timer_units[tmr]);
+ return (rtc->currd * accum) + sim_activate_time (&sim_timer_units[tmr]);
}
}
}
for (tmr=0; tmr<=SIM_NTIMERS; tmr++) {
if ((uptr == &sim_timer_units[tmr]) && (uptr->next)){
@@ -3046,11 +3260,13 @@
int32 tmr;
double result = -1.0;
/* If this is a clock unit, we need to return the related clock assist unit instead */
for (tmr=0; tmr<=SIM_NTIMERS; tmr++) {
- if (sim_clock_unit[tmr] == uptr) {
+ RTC *rtc = &rtcs[tmr];
+
+ if (rtc->clock_unit == uptr) {
uptr = &sim_timer_units[tmr];
break;
}
}
@@ -3092,29 +3308,32 @@
#endif /* defined(SIM_ASYNCH_CLOCKS) */
if (uptr->cancel == &_sim_coschedule_cancel) {
for (tmr=0; tmr<=SIM_NTIMERS; tmr++) {
int32 accum = 0;
+ RTC *rtc = &rtcs[tmr];
- for (cptr = sim_clock_cosched_queue[tmr]; cptr != QUEUE_LIST_END; cptr = cptr->next) {
- if (cptr == sim_clock_cosched_queue[tmr]) {
- if (sim_cosched_interval[tmr] > 0)
- accum += sim_cosched_interval[tmr];
+ for (cptr = rtc->clock_cosched_queue; cptr != QUEUE_LIST_END; cptr = cptr->next) {
+ if (cptr == rtc->clock_cosched_queue) {
+ if (rtc->cosched_interval > 0)
+ accum += rtc->cosched_interval;
}
else
accum += cptr->time;
if (cptr == uptr) {
- result = uptr->usecs_remaining + ceil(1000000.0 * ((rtc_currd[tmr] * accum) + sim_activate_time (&sim_timer_units[tmr]) - 1) / sim_timer_inst_per_sec ());
- sim_debug (DBG_QUE, &sim_timer_dev, "sim_timer_activate_time_usecs(%s) coscheduled - %.0f usecs, inst_per_sec=%.0f, tmr=%d, ticksize=%d, ticks=%d, inst_til_tick=%d, usecs_remaining=%.0f\n", sim_uname (uptr), result, sim_timer_inst_per_sec (), tmr, rtc_currd[tmr], accum, sim_activate_time (&sim_timer_units[tmr]) - 1, uptr->usecs_remaining);
+ result = uptr->usecs_remaining + ceil(1000000.0 * ((rtc->currd * accum) + sim_activate_time (&sim_timer_units[tmr]) - 1) / sim_timer_inst_per_sec ());
+ sim_debug (DBG_QUE, &sim_timer_dev, "sim_timer_activate_time_usecs(%s) coscheduled - %.0f usecs, inst_per_sec=%.0f, tmr=%d, ticksize=%d, ticks=%d, inst_til_tick=%d, usecs_remaining=%.0f\n", sim_uname (uptr), result, sim_timer_inst_per_sec (), tmr, rtc->currd, accum, sim_activate_time (&sim_timer_units[tmr]) - 1, uptr->usecs_remaining);
return result;
}
}
}
}
for (tmr=0; tmr<=SIM_NTIMERS; tmr++) {
- if ((uptr == sim_clock_unit[tmr]) && (uptr->next)) {
- result = sim_clock_unit[tmr]->usecs_remaining + (1000000.0 * (sim_activate_time (&sim_timer_units[tmr]) - 1)) / sim_timer_inst_per_sec ();
+ RTC *rtc = &rtcs[tmr];
+
+ if ((uptr == rtc->clock_unit) && (uptr->next)) {
+ result = rtc->clock_unit->usecs_remaining + (1000000.0 * (sim_activate_time (&sim_timer_units[tmr]) - 1)) / sim_timer_inst_per_sec ();
sim_debug (DBG_QUE, &sim_timer_dev, "sim_timer_activate_time_usecs(%s) clock - %.0f usecs, inst_per_sec=%.0f, usecs_remaining=%.0f\n", sim_uname (uptr), result, sim_timer_inst_per_sec (), uptr->usecs_remaining);
return result;
}
if ((uptr == &sim_timer_units[tmr]) && (uptr->next)){
result = uptr->usecs_remaining + (1000000.0 * (sim_activate_time (uptr) - 1)) / sim_timer_inst_per_sec ();
@@ -3197,5 +3416,64 @@
void sim_set_rom_delay_factor (uint32 delay)
{
sim_rom_delay = delay;
}
+
+/* sim_timer_precalibrate_execution_rate
+ *
+ * The point of this routine is to run a bunch of simulator provided
+ * instructions that don't do anything, but run in an effective loop.
+ * That loop is run for some 5 million instructions and based on
+ * the time those 5 million instructions take to execute the effective
+ * execution rate. That rate is used to avoid the initial 3 to 5
+ * seconds that normal clock calibration takes.
+ *
+ */
+void sim_timer_precalibrate_execution_rate (void)
+{
+const char **cmd = sim_clock_precalibrate_commands;
+uint32 start, end;
+int32 saved_switches = sim_switches;
+int32 tmr;
+UNIT precalib_unit = { UDATA (&sim_timer_stop_svc, 0, 0) };
+
+if (cmd == NULL)
+ return;
+sim_run_boot_prep (RU_GO);
+while (sim_clock_queue != QUEUE_LIST_END)
+ sim_cancel (sim_clock_queue);
+while (*cmd)
+ exdep_cmd (EX_D, *(cmd++));
+sim_switches = saved_switches;
+sim_cancel (&SIM_INTERNAL_UNIT);
+sim_activate (&precalib_unit, sim_precalibrate_ips);
+start = sim_os_msec();
+sim_instr();
+end = sim_os_msec();
+sim_precalibrate_ips = (int32)(1000.0 * (sim_precalibrate_ips / (double)(end - start)));
+
+for (tmr=0; tmr<=SIM_NTIMERS; tmr++) {
+ RTC *rtc = &rtcs[tmr];
+
+ if (rtc->hz)
+ rtc->initd = rtc->currd = (int32)(((double)sim_precalibrate_ips) / rtc->hz);
+ }
+reset_all_p (0);
+sim_run_boot_prep (RU_GO);
+for (tmr=0; tmr<=SIM_NTIMERS; tmr++) {
+ RTC *rtc = &rtcs[tmr];
+
+ if (rtc->calib_initializations)
+ rtc->calib_initializations = 1;
+ }
+sim_inst_per_sec_last = sim_precalibrate_ips;
+sim_idle_stable = 0;
+}
+
+double
+sim_host_speed_factor (void)
+{
+if (sim_precalibrate_ips > sim_vm_initial_ips)
+ return 1.0;
+return (double)sim_vm_initial_ips / (double)sim_precalibrate_ips;
+}
Index: src/SIMH/sim_timer.h
==================================================================
--- src/SIMH/sim_timer.h
+++ src/SIMH/sim_timer.h
@@ -74,11 +74,11 @@
#define SIM_NTIMERS 8 /* # timers */
#define SIM_TMAX 500 /* max timer makeup */
-#define SIM_INITIAL_IPS 500000 /* uncalibrated assumption */
+#define SIM_INITIAL_IPS 5000000 /* uncalibrated assumption */
/* about instructions per second */
#define SIM_IDLE_CAL 10 /* ms to calibrate */
#define SIM_IDLE_STMIN 2 /* min sec for stability */
#define SIM_IDLE_STDFLT 20 /* dft sec for stability */
@@ -106,16 +106,18 @@
t_bool sim_timer_init (void);
void sim_timespec_diff (struct timespec *diff, struct timespec *min, struct timespec *sub);
double sim_timenow_double (void);
int32 sim_rtcn_init (int32 time, int32 tmr);
int32 sim_rtcn_init_unit (UNIT *uptr, int32 time, int32 tmr);
+int32 sim_rtcn_init_unit_ticks (UNIT *uptr, int32 time, int32 tmr, int32 ticksper);
void sim_rtcn_get_time (struct timespec *now, int tmr);
t_stat sim_rtcn_tick_ack (uint32 time, int32 tmr);
void sim_rtcn_init_all (void);
-int32 sim_rtcn_calb (int32 ticksper, int32 tmr);
+int32 sim_rtcn_calb (uint32 ticksper, int32 tmr);
+int32 sim_rtcn_calb_tick (int32 tmr);
int32 sim_rtc_init (int32 time);
-int32 sim_rtc_calb (int32 ticksper);
+int32 sim_rtc_calb (uint32 ticksper);
t_stat sim_set_timers (int32 arg, CONST char *cptr);
t_stat sim_show_timers (FILE* st, DEVICE *dptr, UNIT* uptr, int32 val, CONST char* desc);
t_stat sim_show_clock_queues (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, CONST char *cptr);
t_bool sim_idle (uint32 tmr, int sin_cyc);
t_stat sim_set_throt (int32 arg, CONST char *cptr);
@@ -143,10 +145,11 @@
t_stat sim_clock_coschedule (UNIT *uptr, int32 interval);
t_stat sim_clock_coschedule_abs (UNIT *uptr, int32 interval);
t_stat sim_clock_coschedule_tmr (UNIT *uptr, int32 tmr, int32 ticks);
t_stat sim_clock_coschedule_tmr_abs (UNIT *uptr, int32 tmr, int32 ticks);
double sim_timer_inst_per_sec (void);
+void sim_timer_precalibrate_execution_rate (void);
int32 sim_rtcn_tick_size (int32 tmr);
int32 sim_rtcn_calibrated_tmr (void);
t_bool sim_timer_idle_capable (uint32 *host_ms_sleep_1, uint32 *host_tick_ms);
#define PRIORITY_BELOW_NORMAL -1
#define PRIORITY_NORMAL 0
@@ -153,10 +156,11 @@
#define PRIORITY_ABOVE_NORMAL 1
t_stat sim_os_set_thread_priority (int below_normal_above);
uint32 sim_get_rom_delay_factor (void);
void sim_set_rom_delay_factor (uint32 delay);
int32 sim_rom_read_with_delay (int32 val);
+double sim_host_speed_factor (void);
extern t_bool sim_idle_enab; /* idle enabled flag */
extern volatile t_bool sim_idle_wait; /* idle waiting flag */
extern t_bool sim_asynch_timer;
extern DEVICE sim_timer_dev;
Index: src/SIMH/sim_tmxr.c
==================================================================
--- src/SIMH/sim_tmxr.c
+++ src/SIMH/sim_tmxr.c
@@ -301,15 +301,10 @@
dropped for 500ms and raised again (thus hanging up a modem on that
serial port).
sim> set MUX disconnect=2
- A line which is connected to a serial port can be manually closed by
- adding the -C switch to a disconnect command.
-
- sim> set -C MUX disconnect=2
-
Full Modem Control serial port support.
This library supports devices which wish to emulate full modem
control/signalling for serial ports. Any device emulation which wishes
to support this functionality for attached serial ports must call
@@ -1003,10 +998,11 @@
int32 ringing = -1;
char *address;
char msg[512];
uint32 poll_time = sim_os_msec ();
+memset (msg, 0, sizeof (msg));
if (mp->last_poll_time == 0) { /* first poll initializations */
UNIT *uptr = mp->uptr;
if (!uptr) /* Attached ? */
return -1; /* No connections are possinle! */
@@ -1040,11 +1036,12 @@
sim_cancel (mp->ldsc[i].o_uptr);
}
}
}
-if ((poll_time - mp->last_poll_time) < mp->poll_interval*1000)
+if (sim_is_running &&
+ ((poll_time - mp->last_poll_time) < mp->poll_interval*1000))
return -1; /* too soon to try */
srand((unsigned int)poll_time);
tmxr_debug_trace (mp, "tmxr_poll_conn()");
@@ -1061,11 +1058,11 @@
}
else
newsock = sim_accept_conn_ex (mp->master, &address, (mp->packet ? SIM_SOCK_OPT_NODELAY : 0));/* poll connect */
if (newsock != INVALID_SOCKET) { /* got a live one? */
- sprintf (msg, "tmxr_poll_conn() - Connection from %s", address);
+ snprintf (msg, sizeof (msg) - 1, "tmxr_poll_conn() - Connection from %s", address);
tmxr_debug_connect (mp, msg);
op = mp->lnorder; /* get line connection order list pointer */
i = mp->lines; /* play it safe in case lines == 0 */
++mp->sessions; /* count the new session */
@@ -1197,11 +1194,11 @@
lp->connecting = 0;
lp->ipad = (char *)realloc (lp->ipad, 1+strlen (lp->destination));
strcpy (lp->ipad, lp->destination);
lp->cnms = sim_os_msec ();
sim_getnames_sock (lp->sock, &sockname, &peername);
- sprintf (msg, "tmxr_poll_conn() - Outgoing Line Connection to %s (%s->%s) established", lp->destination, sockname, peername);
+ snprintf (msg, sizeof (msg) -1, "tmxr_poll_conn() - Outgoing Line Connection to %s (%s->%s) established", lp->destination, sockname, peername);
tmxr_debug_connect_line (lp, msg);
free (sockname);
free (peername);
if (!lp->notelnet) {
sim_write_sock (lp->sock, (char *)mantra, sizeof(mantra));
@@ -1209,11 +1206,11 @@
lp->telnet_sent_opts = (uint8 *)realloc (lp->telnet_sent_opts, 256);
memset (lp->telnet_sent_opts, 0, 256);
}
return i;
case -1: /* failed connection */
- sprintf (msg, "tmxr_poll_conn() - Outgoing Line Connection to %s failed", lp->destination);
+ snprintf (msg, sizeof (msg) -1, "tmxr_poll_conn() - Outgoing Line Connection to %s failed", lp->destination);
tmxr_debug_connect_line (lp, msg);
tmxr_reset_ln (lp); /* retry */
break;
}
}
@@ -1222,29 +1219,29 @@
if (lp->master) { /* Check for a pending Telnet/tcp connection */
while (INVALID_SOCKET != (newsock = sim_accept_conn_ex (lp->master, &address, (lp->packet ? SIM_SOCK_OPT_NODELAY : 0)))) {/* got a live one? */
char *sockname, *peername;
sim_getnames_sock (newsock, &sockname, &peername);
- sprintf (msg, "tmxr_poll_conn() - Incoming Line Connection from %s (%s->%s)", address, peername, sockname);
+ snprintf (msg, sizeof (msg) -1, "tmxr_poll_conn() - Incoming Line Connection from %s (%s->%s)", address, peername, sockname);
tmxr_debug_connect_line (lp, msg);
free (sockname);
free (peername);
++mp->sessions; /* count the new session */
if (lp->destination) { /* Virtual Null Modem Cable? */
- char host[CBUFSIZE];
+ char host[sizeof(msg) - 64];
if (sim_parse_addr (lp->destination, host, sizeof(host), NULL, NULL, 0, NULL, address)) {
tmxr_msg (newsock, "Rejecting connection from unexpected source\r\n");
- sprintf (msg, "tmxr_poll_conn() - Rejecting line connection from: %s, Expected: %s", address, host);
+ snprintf (msg, sizeof (msg) -1, "tmxr_poll_conn() - Rejecting line connection from: %s, Expected: %s", address, host);
tmxr_debug_connect_line (lp, msg);
sim_close_sock (newsock);
free (address);
continue; /* Try for another connection */
}
if (lp->connecting) {
- sprintf (msg, "tmxr_poll_conn() - aborting outgoing line connection attempt to: %s", lp->destination);
+ snprintf (msg, sizeof (msg) -1, "tmxr_poll_conn() - aborting outgoing line connection attempt to: %s", lp->destination);
tmxr_debug_connect_line (lp, msg);
sim_close_sock (lp->connecting); /* abort our as yet unconnnected socket */
lp->connecting = 0;
}
}
@@ -1284,13 +1281,14 @@
/* Check for needed outgoing connection initiation */
if (lp->destination && (!lp->sock) && (!lp->connecting) && (!lp->serport) &&
(!lp->modem_control || (lp->modembits & TMXR_MDM_DTR))) {
- sprintf (msg, "tmxr_poll_conn() - establishing outgoing connection to: %s", lp->destination);
+ snprintf (msg, sizeof (msg) - 1, "tmxr_poll_conn() - establishing outgoing connection to: %s", lp->destination);
tmxr_debug_connect_line (lp, msg);
- lp->connecting = sim_connect_sock_ex (lp->datagram ? lp->port : NULL, lp->destination, "localhost", NULL, (lp->datagram ? SIM_SOCK_OPT_DATAGRAM : 0) | (lp->mp->packet ? SIM_SOCK_OPT_NODELAY : 0));
+ lp->connecting = sim_connect_sock_ex (lp->datagram ? lp->port : NULL, lp->destination, "localhost", NULL, (lp->datagram ? SIM_SOCK_OPT_DATAGRAM : 0) |
+ (lp->mp->packet ? SIM_SOCK_OPT_NODELAY : 0));
}
}
return ringing; /* no new connections made */
@@ -1356,11 +1354,12 @@
lp->connecting = 0;
}
if ((!lp->modem_control) || (lp->modembits & TMXR_MDM_DTR)) {
sprintf (msg, "tmxr_reset_ln_ex() - connecting to %s", lp->destination);
tmxr_debug_connect_line (lp, msg);
- lp->connecting = sim_connect_sock_ex (lp->datagram ? lp->port : NULL, lp->destination, "localhost", NULL, (lp->datagram ? SIM_SOCK_OPT_DATAGRAM : 0) | (lp->packet ? SIM_SOCK_OPT_NODELAY : 0));
+ lp->connecting = sim_connect_sock_ex (lp->datagram ? lp->port : NULL, lp->destination, "localhost", NULL, (lp->datagram ? SIM_SOCK_OPT_DATAGRAM : 0) |
+ (lp->packet ? SIM_SOCK_OPT_NODELAY : 0));
}
}
tmxr_init_line (lp); /* initialize line state */
return SCPE_OK;
}
@@ -1646,11 +1645,12 @@
TMXR_MDM_DTR)) {
char msg[512];
sprintf (msg, "tmxr_set_get_modem_bits() - establishing outgoing connection to: %s", lp->destination);
tmxr_debug_connect_line (lp, msg);
- lp->connecting = sim_connect_sock_ex (lp->datagram ? lp->port : NULL, lp->destination, "localhost", NULL, (lp->datagram ? SIM_SOCK_OPT_DATAGRAM : 0) | (lp->packet ? SIM_SOCK_OPT_NODELAY : 0));
+ lp->connecting = sim_connect_sock_ex (lp->datagram ? lp->port : NULL, lp->destination, "localhost", NULL, (lp->datagram ? SIM_SOCK_OPT_DATAGRAM : 0) |
+ (lp->packet ? SIM_SOCK_OPT_NODELAY : 0));
}
}
}
return SCPE_OK;
}
@@ -2742,12 +2742,14 @@
if (0 == MATCH_CMD (cptr, "NOTELNET"))
listennotelnet = TRUE;
else
if (0 == MATCH_CMD (cptr, "TELNET"))
listennotelnet = FALSE;
- else
- return sim_messagef (SCPE_ARG, "Invalid Specifier: %s\n", tptr);
+ else {
+ if (*tptr)
+ return sim_messagef (SCPE_ARG, "Invalid Specifier: %s\n", tptr);
+ }
}
cptr = init_cptr;
}
cptr = get_glyph_nc (cptr, port, ';');
sock = sim_master_sock (port, &r); /* make master socket to validate port */
@@ -2799,11 +2801,12 @@
else
notelnet = FALSE;
else
return sim_messagef (SCPE_ARG, "Unexpected specifier: %s\n", eptr);
}
- sock = sim_connect_sock_ex (NULL, hostport, "localhost", NULL, (datagram ? SIM_SOCK_OPT_DATAGRAM : 0) | (packet ? SIM_SOCK_OPT_NODELAY : 0));
+ sock = sim_connect_sock_ex (NULL, hostport, "localhost", NULL, (datagram ? SIM_SOCK_OPT_DATAGRAM : 0) |
+ (packet ? SIM_SOCK_OPT_NODELAY : 0));
if (sock != INVALID_SOCKET)
sim_close_sock (sock);
else
return sim_messagef (SCPE_ARG, "Invalid destination: %s\n", hostport);
}
@@ -2951,11 +2954,12 @@
}
else
return sim_messagef (SCPE_ARG, "Missing listen port for Datagram socket\n");
}
lp->packet = packet;
- sock = sim_connect_sock_ex (datagram ? listen : NULL, hostport, "localhost", NULL, (datagram ? SIM_SOCK_OPT_DATAGRAM : 0) | (packet ? SIM_SOCK_OPT_NODELAY : 0));
+ sock = sim_connect_sock_ex (datagram ? listen : NULL, hostport, "localhost", NULL, (datagram ? SIM_SOCK_OPT_DATAGRAM : 0) |
+ (packet ? SIM_SOCK_OPT_NODELAY : 0));
if (sock != INVALID_SOCKET) {
_mux_detach_line (lp, FALSE, TRUE);
lp->destination = (char *)malloc(1+strlen(hostport));
strcpy (lp->destination, hostport);
lp->mp = mp;
@@ -3066,11 +3070,12 @@
strcpy (lp->port, listen); /* save port */
}
else
return sim_messagef (SCPE_ARG, "Missing listen port for Datagram socket\n");
}
- sock = sim_connect_sock_ex (datagram ? listen : NULL, hostport, "localhost", NULL, (datagram ? SIM_SOCK_OPT_DATAGRAM : 0) | (packet ? SIM_SOCK_OPT_NODELAY : 0));
+ sock = sim_connect_sock_ex (datagram ? listen : NULL, hostport, "localhost", NULL, (datagram ? SIM_SOCK_OPT_DATAGRAM : 0) |
+ (packet ? SIM_SOCK_OPT_NODELAY : 0));
if (sock != INVALID_SOCKET) {
_mux_detach_line (lp, FALSE, TRUE);
lp->destination = (char *)malloc(1+strlen(hostport));
strcpy (lp->destination, hostport);
if (!lp->modem_control || (lp->modembits & TMXR_MDM_DTR)) {
@@ -4400,10 +4405,13 @@
fprintf (st, "The %s device is a full modem control device and therefore is capable of\n", dptr->name);
fprintf (st, "passing port configuration information and modem signals.\n");
}
fprintf (st, "A Telnet listening port can be configured with:\n\n");
fprintf (st, " sim> ATTACH %s {interface:}port\n\n", dptr->name);
+ fprintf (st, "The -U switch can be specified on the attach command that specifies\n");
+ fprintf (st, "a listening port. This will allow a listening port to be reused if\n");
+ fprintf (st, "some prior connections haven't completely shutdown.\n\n");
fprintf (st, "Line buffering can be enabled for the %s device with:\n\n", dptr->name);
fprintf (st, " sim> ATTACH %s Buffer{=bufsize}\n\n", dptr->name);
fprintf (st, "Line buffering can be disabled for the %s device with:\n\n", dptr->name);
fprintf (st, " sim> ATTACH %s NoBuffer\n\n", dptr->name);
fprintf (st, "The default buffer size is 32k bytes, the max buffer size is 1024k bytes\n\n");
@@ -4426,10 +4434,13 @@
fprintf (st, "multiplexer line with:\n\n");
fprintf (st, " sim> ATTACH %s Line=n,Modem\n", dptr->name);
fprintf (st, " sim> ATTACH %s Line=n,NoModem\n\n", dptr->name);
fprintf (st, "A Telnet listening port can be configured with:\n\n");
fprintf (st, " sim> ATTACH %s {interface:}port\n\n", dptr->name);
+ fprintf (st, "The -U switch can be specified on the attach command that specifies\n");
+ fprintf (st, "a listening port. This will allow a listening port to be reused if\n");
+ fprintf (st, "some prior connections haven't completely shutdown.\n\n");
if (mux)
fprintf (st, "Line buffering for all %d lines on the %s device can be configured with:\n\n", mux->lines, dptr->name);
else
fprintf (st, "Line buffering for all lines on the %s device can be configured with:\n\n", dptr->name);
fprintf (st, " sim> ATTACH %s Buffer{=bufsize}\n\n", dptr->name);
@@ -4606,13 +4617,16 @@
fprintf (st, "A specific line on the %s device can be disconnected with:\n\n", dptr->name);
fprintf (st, " sim> SET %s %s=n\n\n", dptr->name, mptr->mstring);
fprintf (st, "This will cause a telnet connection to be closed, but a serial port will\n");
fprintf (st, "normally have DTR dropped for 500ms and raised again (thus hanging up a\n");
fprintf (st, "modem on that serial port).\n\n");
- fprintf (st, "A line which is connected to a serial port can be manually closed by\n");
- fprintf (st, "adding the -C switch to a %s command.\n\n", mptr->mstring);
- fprintf (st, " sim> SET -C %s %s=n\n\n", dptr->name, mptr->mstring);
+ fprintf (st, "Any lines connected to serial port can be manually closed by unplugging\n");
+ fprintf (st, "the serial cable from the host computer. Dynamically adding or removing\n");
+ fprintf (st, "a serial port from a mux while the simulated operating system is running\n");
+ fprintf (st, "is guaranteed to have an inconsistent state between the running OS and\n");
+ fprintf (st, "the simulated port state. Restart the simulator without the serial port\n");
+ fprintf (st, "attached.\n\n");
}
}
return SCPE_OK;
}
@@ -4843,12 +4857,11 @@
points to the string "3", then line 3 will be disconnected.
If the line was connected to a tcp session, the socket associated with the
line will be closed. If the line was connected to a serial port, the port
will NOT be closed, but DTR will be dropped. After a 500ms delay DTR will
- be raised again. If the sim_switches -C flag is set, then a serial port
- connection will be closed.
+ be raised again.
Implementation notes:
1. This function is usually called as an MTAB processing routine.
*/
@@ -4870,12 +4883,15 @@
return status; /* report it */
if ((lp->sock) || (lp->serport)) { /* connection active? */
if (!lp->notelnet)
tmxr_linemsg (lp, "\r\nOperator disconnected line\r\n\n");/* report closure */
- if (lp->serport && (sim_switches & SWMASK ('C')))
- return tmxr_detach_ln (lp);
+ if (lp->serport && (sim_switches & SWMASK ('C'))) {
+ sim_messagef (SCPE_OK, "If you really feel the need to disconnect this serial port, unplug the cable\n");
+ sim_messagef (SCPE_OK, "from the serial port on your system. Alternatively, you should restart the\n");
+ sim_messagef (SCPE_OK, "simulator without attaching the serial port in your configuration.\n");
+ }
return tmxr_reset_ln_ex (lp, FALSE); /* drop the line */
}
return SCPE_OK;
}
@@ -5424,5 +5440,60 @@
sim_debug (dbits, dptr, " txnexttime=%.0f (%.0f usecs)", lp->txnexttime, ((lp->txnexttime - sim_gtime ()) / sim_timer_inst_per_sec ()) * 1000000.0);
sim_debug (dbits, dptr, "\n");
}
}
}
+
+/* Testing of sim_sock and tmxr */
+
+#include
+
+t_stat tmxr_sock_test (DEVICE *dptr)
+{
+char cmd[CBUFSIZE], host[CBUFSIZE], port[CBUFSIZE];
+int line;
+TMXR *tmxr;
+TMLN *ln;
+int32 tmp1, tmp2;
+t_stat stat = SCPE_OK;
+SOCKET sock_mux = INVALID_SOCKET;
+SOCKET sock_line = INVALID_SOCKET;
+SIM_TEST_INIT;
+
+sim_printf ("Testing %s:\n", dptr->name);
+SIM_TEST(sim_parse_addr ("", NULL, 0, "localhost", NULL, 0, "1234", NULL) != -1);
+SIM_TEST(sim_parse_addr ("", host, 0, "localhost", NULL, 0, "1234", NULL) != -1);
+SIM_TEST(sim_parse_addr ("", host, sizeof(host), "localhost", port, 0, "1234", NULL) != -1);
+SIM_TEST((sim_parse_addr ("", host, sizeof(host), "localhost", port, sizeof(port), "1234", NULL) == -1) || (strcmp(host, "localhost")) || (strcmp(port,"1234")));
+SIM_TEST((sim_parse_addr ("localhost:6666", host, sizeof(host), "localhost", port, sizeof(port), "1234", NULL) == -1) || (strcmp(host, "localhost")) || (strcmp(port,"6666")));
+SIM_TEST(sim_parse_addr ("localhost:66666", host, sizeof(host), "localhost", port, sizeof(port), "1234", NULL) != -1);
+SIM_TEST((sim_parse_addr ("localhost:telnet", host, sizeof(host), "localhost", port, sizeof(port), "1234", NULL) == -1) || (strcmp(host, "localhost")) || (strcmp(port,"telnet")));
+SIM_TEST((sim_parse_addr ("telnet", host, sizeof(host), "localhost", port, sizeof(port), "1234", NULL) == -1) || (strcmp(host, "localhost")) || (strcmp(port,"telnet")));
+dptr->dctrl = 0xFFFFFFFF;
+dptr->dctrl &= ~TMXR_DBG_TRC;
+sprintf (cmd, "%s -u localhost:65500;notelnet", dptr->name);
+SIM_TEST(attach_cmd (0, cmd));
+tmxr = (TMXR *)dptr->units->tmxr;
+ln = &tmxr->ldsc[tmxr->lines - 1];
+SIM_TEST(detach_cmd (0, dptr->name));
+if (tmxr->lines > 1) {
+ tmxr->modem_control = FALSE;
+ for (line=0; line < tmxr->lines; line++)
+ tmxr->ldsc[line].modem_control = FALSE;
+ snprintf (cmd + strlen (cmd), sizeof (cmd) - strlen (cmd), ",Line=%d,localhost:65501", tmxr->lines - 1);
+ snprintf (cmd + strlen (cmd), sizeof (cmd) - strlen (cmd), ",Line=0,connect=localhost:65500");
+ SIM_TEST(attach_cmd (0, cmd));
+ sock_line = sim_connect_sock_ex (NULL, "localhost:65501", NULL, NULL, 0);
+ sim_os_ms_sleep (100);
+ SIM_TEST((((tmp1 = tmxr_poll_conn (tmxr)) == tmxr->lines - 1) || (tmp1 == 1)) ? SCPE_OK : SCPE_IERR);
+ sock_mux = sim_connect_sock ("", "localhost", "65500");
+ sim_os_ms_sleep (100);
+ SIM_TEST(((tmp2 = tmxr_poll_conn (tmxr)) == 0) || (tmp2 == 2) ? SCPE_OK : SCPE_IERR);
+ show_cmd (0, "MUX");
+ sim_close_sock (sock_mux);
+ sock_mux = INVALID_SOCKET;
+ sim_close_sock (sock_line);
+ sock_line = INVALID_SOCKET;
+ SIM_TEST(detach_cmd (0, dptr->name));
+ }
+return stat;
+}
Index: src/SIMH/sim_tmxr.h
==================================================================
--- src/SIMH/sim_tmxr.h
+++ src/SIMH/sim_tmxr.h
@@ -305,10 +305,11 @@
t_stat tmxr_locate_line (const char *dev_line, TMLN **lp);
const char *tmxr_send_line_name (const SEND *snd);
const char *tmxr_expect_line_name (const EXPECT *exp);
t_stat tmxr_startup (void);
t_stat tmxr_shutdown (void);
+t_stat tmxr_sock_test (DEVICE *dptr);
t_stat tmxr_start_poll (void);
t_stat tmxr_stop_poll (void);
void _tmxr_debug (uint32 dbits, TMLN *lp, const char *msg, char *buf, int bufsize);
#define tmxr_debug(dbits, lp, msg, buf, bufsize) do {if (sim_deb && (lp)->mp && (lp)->mp->dptr && ((dbits) & (lp)->mp->dptr->dctrl)) _tmxr_debug (dbits, lp, msg, buf, bufsize); } while (0)
#define tmxr_debug_msg(dbits, lp, msg) do {if (sim_deb && (lp)->mp && (lp)->mp->dptr && ((dbits) & (lp)->mp->dptr->dctrl)) sim_debug (dbits, (lp)->mp->dptr, "%s", msg); } while (0)
Index: src/SIMH/sim_video.c
==================================================================
--- src/SIMH/sim_video.c
+++ src/SIMH/sim_video.c
@@ -35,16 +35,55 @@
int32 vid_cursor_y;
t_bool vid_mouse_b1 = FALSE;
t_bool vid_mouse_b2 = FALSE;
t_bool vid_mouse_b3 = FALSE;
static VID_QUIT_CALLBACK vid_quit_callback = NULL;
+static VID_GAMEPAD_CALLBACK motion_callback[10];
+static VID_GAMEPAD_CALLBACK button_callback[10];
+static int vid_gamepad_inited = 0;
+static int vid_gamepad_ok = 0; /* Or else just joysticks. */
t_stat vid_register_quit_callback (VID_QUIT_CALLBACK callback)
{
vid_quit_callback = callback;
return SCPE_OK;
}
+
+static t_stat register_callback (void **array, int n, void *callback)
+{
+ int i, j = -1;
+
+ if (!vid_gamepad_inited) {
+ return SCPE_NOATT;
+ }
+
+ for (i = 0; i < n; i++) {
+ if (array[i] == callback)
+ return SCPE_ALATT;
+ if (array[i] == NULL)
+ j = i;
+ }
+
+ if (j != -1) {
+ array[j] = callback;
+ return SCPE_OK;
+ }
+
+ return SCPE_NXM;
+}
+
+t_stat vid_register_gamepad_motion_callback (VID_GAMEPAD_CALLBACK callback)
+{
+ int n = sizeof (motion_callback) / sizeof (callback);
+ return register_callback ((void **)motion_callback, n, (void *)callback);
+}
+
+t_stat vid_register_gamepad_button_callback (VID_GAMEPAD_CALLBACK callback)
+{
+ int n = sizeof (button_callback) / sizeof (callback);
+ return register_callback ((void **)button_callback, n, (void *)callback);
+}
t_stat vid_show (FILE* st, DEVICE *dptr, UNIT* uptr, int32 val, CONST char* desc)
{
return vid_show_video (st, uptr, val, desc);
}
@@ -102,10 +141,11 @@
* This code is free software, available under zlib/libpng license.
* http://www.libpng.org/pub/png/src/libpng-LICENSE.txt
*/
#include
#include
+#include
#define SUCCESS 0
#define ERROR -1
#define USE_ROW_POINTERS
@@ -506,10 +546,73 @@
return SCPE_OPENERR;
}
return SCPE_OK;
}
#endif
+
+static t_stat vid_init_controllers (void)
+{
+ SDL_Joystick *y;
+ SDL_version ver;
+ int i, n;
+
+ if (vid_gamepad_inited)
+ return SCPE_OK;
+
+ /* Chech that the SDL_GameControllerFromInstanceID function is
+ available at run time. */
+ SDL_GetVersion(&ver);
+ vid_gamepad_ok = (ver.major > 2 ||
+ (ver.major == 2 && (ver.minor > 0 || ver.patch >= 4)));
+
+ if (vid_gamepad_ok)
+ SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER);
+ else
+ SDL_InitSubSystem(SDL_INIT_JOYSTICK);
+
+ if (SDL_JoystickEventState (SDL_ENABLE) < 0) {
+ if (vid_gamepad_ok)
+ SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
+ else
+ SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
+ return SCPE_IOERR;
+ }
+
+ if (vid_gamepad_ok && SDL_GameControllerEventState (SDL_ENABLE) < 0) {
+ if (vid_gamepad_ok)
+ SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
+ else
+ SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
+ return SCPE_IOERR;
+ }
+
+ n = SDL_NumJoysticks();
+
+ for (i = 0; i < n; i++) {
+ if (vid_gamepad_ok && SDL_IsGameController (i)) {
+ SDL_GameController *x = SDL_GameControllerOpen (i);
+ if (x != NULL) {
+ sim_debug (SIM_VID_DBG_VIDEO, vid_dev,
+ "Game controller: %s\n", SDL_GameControllerNameForIndex(i));
+ }
+ }
+ else {
+ y = SDL_JoystickOpen (i);
+ if (y != NULL) {
+ sim_debug (SIM_VID_DBG_VIDEO, vid_dev,
+ "Joystick: %s\n", SDL_JoystickNameForIndex(i));
+ sim_debug (SIM_VID_DBG_VIDEO, vid_dev,
+ "Number of axes: %d, buttons: %d\n",
+ SDL_JoystickNumAxes(y),
+ SDL_JoystickNumButtons(y));
+ }
+ }
+ }
+
+ vid_gamepad_inited = 1;
+ return SCPE_OK;
+}
t_stat vid_open (DEVICE *dptr, const char *title, uint32 width, uint32 height, int flags)
{
if (!vid_active) {
int wait_count = 0;
@@ -535,13 +638,21 @@
vid_mouse_events.count = 0;
vid_mouse_events.sem = SDL_CreateSemaphore (1);
vid_dev = dptr;
+ memset (motion_callback, 0, sizeof motion_callback);
+ memset (button_callback, 0, sizeof button_callback);
+
stat = vid_create_window ();
if (stat != SCPE_OK)
return stat;
+
+ if (vid_init_controllers () != SCPE_OK) {
+ sim_debug (SIM_VID_DBG_VIDEO, vid_dev,
+ "vid_open() - Failed initializing game controllers\n");
+ }
sim_debug (SIM_VID_DBG_VIDEO|SIM_VID_DBG_KEY|SIM_VID_DBG_MOUSE, vid_dev, "vid_open() - Success\n");
}
return SCPE_OK;
}
@@ -550,10 +661,18 @@
{
if (vid_active) {
SDL_Event user_event;
int status;
+ vid_gamepad_inited = 0;
+ memset (motion_callback, 0, sizeof motion_callback);
+ memset (button_callback, 0, sizeof button_callback);
+ if (vid_gamepad_ok)
+ SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
+ else
+ SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
+
vid_active = FALSE;
if (vid_ready) {
sim_debug (SIM_VID_DBG_VIDEO|SIM_VID_DBG_KEY|SIM_VID_DBG_MOUSE, vid_dev, "vid_close()\n");
user_event.type = SDL_USEREVENT;
user_event.user.code = EVENT_CLOSE;
@@ -1145,10 +1264,65 @@
default:
return SIM_KEY_UNKNOWN;
}
}
+
+void vid_joy_motion (SDL_JoyAxisEvent *event)
+{
+ int n = sizeof motion_callback / sizeof (VID_GAMEPAD_CALLBACK);
+ int i;
+
+ for (i = 0; i < n; i++) {
+ if (motion_callback[i]) {
+ motion_callback[i](event->which, event->axis, event->value);
+ }
+ }
+}
+
+void vid_joy_button (SDL_JoyButtonEvent *event)
+{
+ int n = sizeof button_callback / sizeof (VID_GAMEPAD_CALLBACK);
+ int i;
+
+ for (i = 0; i < n; i++) {
+ if (button_callback[i]) {
+ button_callback[i](event->which, event->button, event->state);
+ }
+ }
+}
+
+void vid_controller_motion (SDL_ControllerAxisEvent *event)
+{
+ SDL_JoyAxisEvent e;
+ e.which = event->which;
+ e.axis = event->axis;
+ e.value = event->value;
+ vid_joy_motion (&e);
+}
+
+void vid_controller_button (SDL_ControllerButtonEvent *event)
+{
+ /* SDL_GameControllerFromInstanceID is only available from SDL
+ version 2.0.4, so check the version at compile time. The
+ version is also checked at run time. */
+#if (SDL_MAJOR_VERSION > 2) || (SDL_MAJOR_VERSION == 2 && \
+ (SDL_MINOR_VERSION > 0) || (SDL_PATCHLEVEL >= 4))
+
+ SDL_JoyButtonEvent e;
+ SDL_GameControllerButtonBind b;
+ SDL_GameController *c;
+ SDL_GameControllerButton button = (SDL_GameControllerButton)event->button;
+
+ c = SDL_GameControllerFromInstanceID (event->which);
+ b = SDL_GameControllerGetBindForButton (c, button);
+ e.which = event->which;
+ e.button = b.value.button;
+ e.state = event->state;
+ vid_joy_button (&e);
+#endif
+}
void vid_key (SDL_KeyboardEvent *event)
{
SIM_KEY_EVENT ev;
@@ -1688,21 +1862,40 @@
if (status == 1) {
switch (event.type) {
case SDL_KEYDOWN:
case SDL_KEYUP:
- vid_key ((SDL_KeyboardEvent*)&event);
+ vid_key (&event.key);
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
- vid_mouse_button ((SDL_MouseButtonEvent*)&event);
+ vid_mouse_button (&event.button);
break;
case SDL_MOUSEMOTION:
- vid_mouse_move ((SDL_MouseMotionEvent*)&event);
+ vid_mouse_move (&event.motion);
+ break;
+
+ case SDL_JOYAXISMOTION:
+ vid_joy_motion (&event.jaxis);
+ break;
+
+ case SDL_JOYBUTTONUP:
+ case SDL_JOYBUTTONDOWN:
+ vid_joy_button (&event.jbutton);
+ break;
+
+ case SDL_CONTROLLERAXISMOTION:
+ vid_controller_motion (&event.caxis);
+ break;
+
+ case SDL_CONTROLLERBUTTONUP:
+ case SDL_CONTROLLERBUTTONDOWN:
+ vid_controller_button (&event.cbutton);
break;
+
#if SDL_MAJOR_VERSION != 1
case SDL_WINDOWEVENT:
if (event.window.windowID == vid_windowID) {
sim_debug (SIM_VID_DBG_VIDEO|SIM_VID_DBG_KEY|SIM_VID_DBG_MOUSE|SIM_VID_DBG_CURSOR, vid_dev, "vid_thread() - Window Event: %d - %s\n", event.window.event, windoweventtypes[event.window.event]);
switch (event.window.event) {
@@ -1833,11 +2026,11 @@
return 0;
}
const char *vid_version(void)
{
-static char SDLVersion[80];
+static char SDLVersion[160];
SDL_version compiled, running;
#if SDL_MAJOR_VERSION == 1
const SDL_version *ver = SDL_Linked_Version();
running.major = ver->major;
@@ -1846,19 +2039,42 @@
#else
SDL_GetVersion(&running);
#endif
SDL_VERSION(&compiled);
+SDLVersion[sizeof (SDLVersion) - 1] = '\0';
if ((compiled.major == running.major) &&
(compiled.minor == running.minor) &&
(compiled.patch == running.patch))
- sprintf(SDLVersion, "SDL Version %d.%d.%d",
+ snprintf(SDLVersion, sizeof (SDLVersion) - 1, "SDL Version %d.%d.%d",
compiled.major, compiled.minor, compiled.patch);
else
- sprintf(SDLVersion, "SDL Version (Compiled: %d.%d.%d, Runtime: %d.%d.%d)",
+ snprintf(SDLVersion, sizeof (SDLVersion) - 1, "SDL Version (Compiled: %d.%d.%d, Runtime: %d.%d.%d)",
compiled.major, compiled.minor, compiled.patch,
running.major, running.minor, running.patch);
+#if defined (HAVE_LIBPNG)
+if (1) {
+ png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+
+ if (strcmp (PNG_LIBPNG_VER_STRING, png_get_libpng_ver (png)))
+ snprintf(&SDLVersion[strlen (SDLVersion)], sizeof (SDLVersion) - (strlen (SDLVersion) + 1),
+ ", PNG Version (Compiled: %s, Runtime: %s)",
+ PNG_LIBPNG_VER_STRING, png_get_libpng_ver (png));
+ else
+ snprintf(&SDLVersion[strlen (SDLVersion)], sizeof (SDLVersion) - (strlen (SDLVersion) + 1),
+ ", PNG Version %s", PNG_LIBPNG_VER_STRING);
+ png_destroy_read_struct(&png, NULL, NULL);
+#if defined (ZLIB_VERSION)
+ if (strcmp (ZLIB_VERSION, zlibVersion ()))
+ snprintf(&SDLVersion[strlen (SDLVersion)], sizeof (SDLVersion) - (strlen (SDLVersion) + 1),
+ ", zlib: (Compiled: %s, Runtime: %s)", ZLIB_VERSION, zlibVersion ());
+ else
+ snprintf(&SDLVersion[strlen (SDLVersion)], sizeof (SDLVersion) - (strlen (SDLVersion) + 1),
+ ", zlib: %s", ZLIB_VERSION);
+#endif
+ }
+#endif
return (const char *)SDLVersion;
}
t_stat vid_set_release_key (FILE* st, UNIT* uptr, int32 val, CONST void* desc)
{
@@ -2157,11 +2373,11 @@
if (!vid_active) {
sim_printf ("No video display is active\n");
return SCPE_UDIS | SCPE_NOMESSAGE;
}
fullname = (char *)malloc (strlen(filename) + 5);
-if (!filename)
+if (!fullname)
return SCPE_MEM;
#if SDL_MAJOR_VERSION == 1
#if defined(HAVE_LIBPNG)
if (!match_ext (filename, "bmp")) {
sprintf (fullname, "%s%s", filename, match_ext (filename, "png") ? "" : ".png");
Index: src/SIMH/sim_video.h
==================================================================
--- src/SIMH/sim_video.h
+++ src/SIMH/sim_video.h
@@ -178,10 +178,13 @@
t_stat vid_open (DEVICE *dptr, const char *title, uint32 width, uint32 height, int flags);
#define SIM_VID_INPUTCAPTURED 1 /* Mouse and Keyboard input captured (calling */
/* code responsible for cursor display in video) */
typedef void (*VID_QUIT_CALLBACK)(void);
t_stat vid_register_quit_callback (VID_QUIT_CALLBACK callback);
+typedef void (*VID_GAMEPAD_CALLBACK)(int, int, int);
+t_stat vid_register_gamepad_motion_callback (VID_GAMEPAD_CALLBACK);
+t_stat vid_register_gamepad_button_callback (VID_GAMEPAD_CALLBACK);
t_stat vid_close (void);
t_stat vid_poll_kb (SIM_KEY_EVENT *ev);
t_stat vid_poll_mouse (SIM_MOUSE_EVENT *ev);
uint32 vid_map_rgb (uint8 r, uint8 g, uint8 b);
void vid_draw (int32 x, int32 y, int32 w, int32 h, uint32 *buf);
DELETED tools/test-os8-send-file
Index: tools/test-os8-send-file
==================================================================
--- tools/test-os8-send-file
+++ /dev/null
@@ -1,127 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-########################################################################
-# test-os8-send-file - Repeatedly sends random files through class simh
-# method os8_send_file() and pulls it back through os8_get_file(),
-# then checks that the file is unchanged.
-#
-# Copyright © 2017 by Warren Young.
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM,
-# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
-# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
-# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-#
-# Except as contained in this notice, the names of the authors above
-# shall not be used in advertising or otherwise to promote the sale,
-# use or other dealings in this Software without prior written
-# authorization from those authors.
-########################################################################
-
-# Bring in just the basics so we can bring in our local modules
-import os
-import sys
-sys.path.insert (0, os.path.dirname (__file__) + '/../lib')
-sys.path.insert (0, os.getcwd () + '/lib')
-
-# Our local modules
-from pidp8i import *
-from simh import *
-
-# Other core modules we need
-import filecmp
-import os.path
-import random
-import tempfile
-
-
-#### gen_file ##########################################################
-# Generate a random text file. In order that the process be lossless
-# through the txt2ptp/ptp2txt filters and the SIMH + OS/8 terminal
-# handling, we use only printable ASCII plus CR+LF characters. Returns
-# the name of the generated file.
-
-def gen_file ():
- f = tempfile.NamedTemporaryFile (delete = False, suffix = '.tmp')
- for i in range (0, random.randint (10, 4000)):
- if random.randint (0, 10) != 0:
- # Normal case: write some number of printable ASCII characters
- # on this line.
- for j in range (0, random.randint (1, 79)):
- f.write (chr (random.randint (32, 126)))
- # else: Every now and then, just write a blank line
-
- f.write ('\r\n')
-
- f.close ()
- return f.name
-
-
-#### main ##############################################################
-
-def main ():
- # Create the SIMH child instance and tell it where to send log output
- try:
- s = simh (dirs.build)
- except (RuntimeError) as e:
- print "Could not start simulator: " + e.message + '!'
- exit (1)
- s.set_logfile (os.fdopen (sys.stdout.fileno (), 'w', 0))
-
- # Find and boot the built OS/8 bin disk
- rk = os.path.join (dirs.os8mo, 'v3d.rk05')
- if not os.path.isfile (rk):
- print "Could not find " + rk + "; OS/8 media not yet built?"
- exit (1)
- print "Booting " + rk + "..."
- s.send_cmd ("att rk0 " + rk)
- s.send_cmd ("boot rk0")
-
- # Setup
- random.seed ()
-
- # Transfer several random files through. Beware increasing the range
- # too far: max is 99999 due to the file name length limit of OS/8 due
- # to the temporary file naming scheme we use in the loop.
- for i in range (0, 1000):
- # Build another temp file
- ifn = gen_file ()
- of = tempfile.NamedTemporaryFile (suffix = '.out', delete = False)
- of.close ()
-
- # Send it
- ofn = of.name
- tfn = 'T%05d.TX' % i
- s.os8_send_file (ifn, tfn)
- s.os8_get_file (tfn, ofn)
-
- # Did it change?
- if filecmp.cmp (ifn, ofn):
- print ifn + ' transferred successfully.'
- s.os8_send_cmd ('\\.', 'DEL ' + tfn)
- os.remove (ifn)
- os.remove (ofn)
- elif os.path.getsize (ofn) == 0:
- print "\nDifferences found: output is empty!\n"
- else:
- print "\nDifferences found:\n--------------------------------"
- os.system ('diff -wu "' + ifn + '" "' + ofn + '"')
- print 'Left ' + tfn + ' inside OS/8.'
-
-
-if __name__ == "__main__":
- main()
ADDED tools/test-os8-send-file.in
Index: tools/test-os8-send-file.in
==================================================================
--- /dev/null
+++ tools/test-os8-send-file.in
@@ -0,0 +1,127 @@
+#!/usr/bin/env @PYCMD@
+# -*- coding: utf-8 -*-
+########################################################################
+# test-os8-send-file - Repeatedly sends random files through class simh
+# method os8_send_file() and pulls it back through os8_get_file(),
+# then checks that the file is unchanged.
+#
+# Copyright © 2017-2019 by Warren Young.
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM,
+# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
+# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
+# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+# Except as contained in this notice, the names of the authors above
+# shall not be used in advertising or otherwise to promote the sale,
+# use or other dealings in this Software without prior written
+# authorization from those authors.
+########################################################################
+
+# Bring in just the basics so we can bring in our local modules
+import os
+import sys
+sys.path.insert (0, os.path.dirname (__file__) + '/../lib')
+sys.path.insert (0, os.getcwd () + '/lib')
+
+# Our local modules
+from pidp8i import *
+from simh import *
+
+# Other core modules we need
+import filecmp
+import os.path
+import random
+import tempfile
+
+
+#### gen_file ##########################################################
+# Generate a random text file. In order that the process be lossless
+# through the txt2ptp/ptp2txt filters and the SIMH + OS/8 terminal
+# handling, we use only printable ASCII plus CR+LF characters. Returns
+# the name of the generated file.
+
+def gen_file ():
+ f = tempfile.NamedTemporaryFile (delete = False, suffix = '.tmp')
+ for i in range (0, random.randint (10, 4000)):
+ if random.randint (0, 10) != 0:
+ # Normal case: write some number of printable ASCII characters
+ # on this line.
+ for j in range (0, random.randint (1, 79)):
+ f.write (chr (random.randint (32, 126)))
+ # else: Every now and then, just write a blank line
+
+ f.write ('\r\n')
+
+ f.close ()
+ return f.name
+
+
+#### main ##############################################################
+
+def main ():
+ # Create the SIMH child instance and tell it where to send log output
+ try:
+ s = simh (dirs.build)
+ except (RuntimeError) as e:
+ print "Could not start simulator: " + e.message + '!'
+ exit (1)
+ s.set_logfile (os.fdopen (sys.stdout.fileno (), 'wb', 0))
+
+ # Find and boot the built OS/8 bin disk
+ rk = os.path.join (dirs.os8mo, 'v3d.rk05')
+ if not os.path.isfile (rk):
+ print "Could not find " + rk + "; OS/8 media not yet built?"
+ exit (1)
+ print "Booting " + rk + "..."
+ s.send_cmd ("att rk0 " + rk)
+ s.send_cmd ("boot rk0")
+
+ # Setup
+ random.seed ()
+
+ # Transfer several random files through. Beware increasing the range
+ # too far: max is 99999 due to the file name length limit of OS/8 due
+ # to the temporary file naming scheme we use in the loop.
+ for i in range (0, 1000):
+ # Build another temp file
+ ifn = gen_file ()
+ of = tempfile.NamedTemporaryFile (suffix = '.out', delete = False)
+ of.close ()
+
+ # Send it
+ ofn = of.name
+ tfn = 'T%05d.TX' % i
+ s.os8_send_file (ifn, tfn)
+ s.os8_get_file (tfn, ofn)
+
+ # Did it change?
+ if filecmp.cmp (ifn, ofn):
+ print ifn + ' transferred successfully.'
+ s.os8_send_cmd ('\\.', 'DEL ' + tfn)
+ os.remove (ifn)
+ os.remove (ofn)
+ elif os.path.getsize (ofn) == 0:
+ print "\nDifferences found: output is empty!\n"
+ else:
+ print "\nDifferences found:\n--------------------------------"
+ os.system ('diff -wu "' + ifn + '" "' + ofn + '"')
+ print 'Left ' + tfn + ' inside OS/8.'
+
+
+if __name__ == "__main__":
+ main()